From d5d188eebf7826c6d36dbeab46e468e8fc42b953 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 17 Jul 2024 09:51:03 -0400 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=94=90=20fix:=20Enhance=20Message=20&?= =?UTF-8?q?=20Image=20Access=20Security=20=20(#3363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: slight refactor * fix: prevent message updates unless explicitly owned * refactor: rethrow errors, update deleteMessagesSince (not used), add basic tests * fix: Add path normalization and validation to image request middleware * fix: image validation path security --- api/app/clients/BaseClient.js | 8 +- api/models/Message.js | 453 +++++++++++------- api/models/Message.spec.js | 239 +++++++++ api/server/controllers/AskController.js | 6 +- api/server/controllers/EditController.js | 4 +- api/server/controllers/assistants/chatV1.js | 11 +- api/server/controllers/assistants/chatV2.js | 11 +- api/server/middleware/abortMiddleware.js | 4 +- api/server/middleware/denyRequest.js | 4 +- api/server/middleware/validateImageRequest.js | 6 +- api/server/routes/ask/askChatGPTBrowser.js | 10 +- api/server/routes/ask/bingAI.js | 12 +- api/server/routes/ask/gptPlugins.js | 6 +- api/server/routes/edit/gptPlugins.js | 8 +- api/server/routes/messages.js | 28 +- api/server/services/Runs/StreamRunManager.js | 2 +- api/server/utils/streamResponse.js | 12 +- 17 files changed, 595 insertions(+), 229 deletions(-) create mode 100644 api/models/Message.spec.js diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index c7b4f977c8..31932a1887 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -598,7 +598,11 @@ class BaseClient { * @param {string | null} user */ async saveMessageToDatabase(message, endpointOptions, user = null) { - const savedMessage = await saveMessage({ + if (this.user && user !== this.user) { + throw new Error('User mismatch.'); + } + + const savedMessage = await saveMessage(this.options.req, { ...message, endpoint: this.options.endpoint, unfinished: false, @@ -619,7 +623,7 @@ class BaseClient { } async updateMessageInDatabase(message) { - await updateMessage(message); + await updateMessage(this.options.req, message); } /** diff --git a/api/models/Message.js b/api/models/Message.js index b9c82ca36b..460c693439 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -4,11 +4,37 @@ const logger = require('~/config/winston'); const idSchema = z.string().uuid(); -module.exports = { - Message, - - async saveMessage({ - user, +/** + * Saves a message in the database. + * + * @async + * @function saveMessage + * @param {Express.Request} req - The request object containing user information. + * @param {Object} params - The message data object. + * @param {string} params.endpoint - The endpoint where the message originated. + * @param {string} params.iconURL - The URL of the sender's icon. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.newMessageId - The new unique identifier for the message (if applicable). + * @param {string} params.conversationId - The identifier of the conversation. + * @param {string} [params.parentMessageId] - The identifier of the parent message, if any. + * @param {string} params.sender - The identifier of the sender. + * @param {string} params.text - The text content of the message. + * @param {boolean} params.isCreatedByUser - Indicates if the message was created by the user. + * @param {string} [params.error] - Any error associated with the message. + * @param {boolean} [params.unfinished] - Indicates if the message is unfinished. + * @param {Object[]} [params.files] - An array of files associated with the message. + * @param {boolean} [params.isEdited] - Indicates if the message was edited. + * @param {string} [params.finish_reason] - Reason for finishing the message. + * @param {number} [params.tokenCount] - The number of tokens in the message. + * @param {string} [params.plugin] - Plugin associated with the message. + * @param {Object[]} [params.plugins] - An array of plugins associated with the message. + * @param {string} [params.model] - The model used to generate the message. + * @returns {Promise} The updated or newly inserted message document. + * @throws {Error} If there is an error in saving the message. + */ +async function saveMessage( + req, + { endpoint, iconURL, messageId, @@ -27,178 +53,271 @@ module.exports = { plugin, plugins, model, - }) { - try { - const validConvoId = idSchema.safeParse(conversationId); - if (!validConvoId.success) { - return; - } + }, +) { + try { + if (!req || !req.user || !req.user.id) { + throw new Error('User not authenticated'); + } - const update = { - user, - iconURL, - endpoint, - messageId: newMessageId || messageId, - conversationId, - parentMessageId, - sender, - text, - isCreatedByUser, - isEdited, - finish_reason, - error, - unfinished, - tokenCount, - plugin, - plugins, - model, - }; + const validConvoId = idSchema.safeParse(conversationId); + if (!validConvoId.success) { + throw new Error('Invalid conversation ID'); + } - if (files) { - update.files = files; - } + const update = { + user: req.user.id, + iconURL, + endpoint, + messageId: newMessageId || messageId, + conversationId, + parentMessageId, + sender, + text, + isCreatedByUser, + isEdited, + finish_reason, + error, + unfinished, + tokenCount, + plugin, + plugins, + model, + }; - const message = await Message.findOneAndUpdate({ messageId }, update, { + if (files) { + update.files = files; + } + + const message = await Message.findOneAndUpdate({ messageId, user: req.user.id }, update, { + upsert: true, + new: true, + }); + + return message.toObject(); + } catch (err) { + logger.error('Error saving message:', err); + throw err; + } +} + +/** + * Saves multiple messages in the database in bulk. + * + * @async + * @function bulkSaveMessages + * @param {Object[]} messages - An array of message objects to save. + * @returns {Promise} The result of the bulk write operation. + * @throws {Error} If there is an error in saving messages in bulk. + */ +async function bulkSaveMessages(messages) { + try { + const bulkOps = messages.map((message) => ({ + updateOne: { + filter: { messageId: message.messageId }, + update: message, upsert: true, + }, + })); + + const result = await Message.bulkWrite(bulkOps); + return result; + } catch (err) { + logger.error('Error saving messages in bulk:', err); + throw err; + } +} + +/** + * Records a message in the database. + * + * @async + * @function recordMessage + * @param {Object} params - The message data object. + * @param {string} params.user - The identifier of the user. + * @param {string} params.endpoint - The endpoint where the message originated. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.conversationId - The identifier of the conversation. + * @param {string} [params.parentMessageId] - The identifier of the parent message, if any. + * @param {Partial} rest - Any additional properties from the TMessage typedef not explicitly listed. + * @returns {Promise} The updated or newly inserted message document. + * @throws {Error} If there is an error in saving the message. + */ +async function recordMessage({ + user, + endpoint, + messageId, + conversationId, + parentMessageId, + ...rest +}) { + try { + // No parsing of convoId as may use threadId + const message = { + user, + endpoint, + messageId, + conversationId, + parentMessageId, + ...rest, + }; + + return await Message.findOneAndUpdate({ user, messageId }, message, { + upsert: true, + new: true, + }); + } catch (err) { + logger.error('Error saving message:', err); + throw err; + } +} + +/** + * Updates the text of a message. + * + * @async + * @function updateMessageText + * @param {Object} params - The update data object. + * @param {Object} req - The request object. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.text - The new text content of the message. + * @returns {Promise} + * @throws {Error} If there is an error in updating the message text. + */ +async function updateMessageText(req, { messageId, text }) { + try { + await Message.updateOne({ messageId, user: req.user.id }, { text }); + } catch (err) { + logger.error('Error updating message text:', err); + throw err; + } +} + +/** + * Updates a message. + * + * @async + * @function updateMessage + * @param {Object} message - The message object containing update data. + * @param {Object} req - The request object. + * @param {string} message.messageId - The unique identifier for the message. + * @param {string} [message.text] - The new text content of the message. + * @param {Object[]} [message.files] - The files associated with the message. + * @param {boolean} [message.isCreatedByUser] - Indicates if the message was created by the user. + * @param {string} [message.sender] - The identifier of the sender. + * @param {number} [message.tokenCount] - The number of tokens in the message. + * @returns {Promise} The updated message document. + * @throws {Error} If there is an error in updating the message or if the message is not found. + */ +async function updateMessage(req, message) { + try { + const { messageId, ...update } = message; + update.isEdited = true; + const updatedMessage = await Message.findOneAndUpdate( + { messageId, user: req.user.id }, + update, + { new: true, + }, + ); + + if (!updatedMessage) { + throw new Error('Message not found or user not authorized.'); + } + + return { + messageId: updatedMessage.messageId, + conversationId: updatedMessage.conversationId, + parentMessageId: updatedMessage.parentMessageId, + sender: updatedMessage.sender, + text: updatedMessage.text, + isCreatedByUser: updatedMessage.isCreatedByUser, + tokenCount: updatedMessage.tokenCount, + isEdited: true, + }; + } catch (err) { + logger.error('Error updating message:', err); + throw err; + } +} + +/** + * Deletes messages in a conversation since a specific message. + * + * @async + * @function deleteMessagesSince + * @param {Object} params - The parameters object. + * @param {Object} req - The request object. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.conversationId - The identifier of the conversation. + * @returns {Promise} The number of deleted messages. + * @throws {Error} If there is an error in deleting messages. + */ +async function deleteMessagesSince(req, { messageId, conversationId }) { + try { + const message = await Message.findOne({ messageId, user: req.user.id }).lean(); + + if (message) { + const query = Message.find({ conversationId, user: req.user.id }); + return await query.deleteMany({ + createdAt: { $gt: message.createdAt }, }); - - return message.toObject(); - } catch (err) { - logger.error('Error saving message:', err); - throw new Error('Failed to save message.'); } - }, + return undefined; + } catch (err) { + logger.error('Error deleting messages:', err); + throw err; + } +} - async bulkSaveMessages(messages) { - try { - const bulkOps = messages.map((message) => ({ - updateOne: { - filter: { messageId: message.messageId }, - update: message, - upsert: true, - }, - })); - - const result = await Message.bulkWrite(bulkOps); - return result; - } catch (err) { - logger.error('Error saving messages in bulk:', err); - throw new Error('Failed to save messages in bulk.'); +/** + * Retrieves messages from the database. + * @async + * @function getMessages + * @param {Record} filter - The filter criteria. + * @param {string | undefined} [select] - The fields to select. + * @returns {Promise} The messages that match the filter criteria. + * @throws {Error} If there is an error in retrieving messages. + */ +async function getMessages(filter, select) { + try { + if (select) { + return await Message.find(filter).select(select).sort({ createdAt: 1 }).lean(); } - }, - /** - * Records a message in the database. - * - * @async - * @function recordMessage - * @param {Object} params - The message data object. - * @param {string} params.user - The identifier of the user. - * @param {string} params.endpoint - The endpoint where the message originated. - * @param {string} params.messageId - The unique identifier for the message. - * @param {string} params.conversationId - The identifier of the conversation. - * @param {string} [params.parentMessageId] - The identifier of the parent message, if any. - * @param {Partial} rest - Any additional properties from the TMessage typedef not explicitly listed. - * @returns {Promise} The updated or newly inserted message document. - * @throws {Error} If there is an error in saving the message. - */ - async recordMessage({ user, endpoint, messageId, conversationId, parentMessageId, ...rest }) { - try { - // No parsing of convoId as may use threadId - const message = { - user, - endpoint, - messageId, - conversationId, - parentMessageId, - ...rest, - }; + return await Message.find(filter).sort({ createdAt: 1 }).lean(); + } catch (err) { + logger.error('Error getting messages:', err); + throw err; + } +} - return await Message.findOneAndUpdate({ user, messageId }, message, { - upsert: true, - new: true, - }); - } catch (err) { - logger.error('Error saving message:', err); - throw new Error('Failed to save message.'); - } - }, - async updateMessageText({ messageId, text }) { - try { - await Message.updateOne({ messageId }, { text }); - } catch (err) { - logger.error('Error updating message text:', err); - throw new Error('Failed to update message text.'); - } - }, - async updateMessage(message) { - try { - const { messageId, ...update } = message; - update.isEdited = true; - const updatedMessage = await Message.findOneAndUpdate({ messageId }, update, { - new: true, - }); +/** + * Deletes messages from the database. + * + * @async + * @function deleteMessages + * @param {Object} filter - The filter criteria to find messages to delete. + * @returns {Promise} The number of deleted messages. + * @throws {Error} If there is an error in deleting messages. + */ +async function deleteMessages(filter) { + try { + return await Message.deleteMany(filter); + } catch (err) { + logger.error('Error deleting messages:', err); + throw err; + } +} - if (!updatedMessage) { - throw new Error('Message not found.'); - } - - return { - messageId: updatedMessage.messageId, - conversationId: updatedMessage.conversationId, - parentMessageId: updatedMessage.parentMessageId, - sender: updatedMessage.sender, - text: updatedMessage.text, - isCreatedByUser: updatedMessage.isCreatedByUser, - tokenCount: updatedMessage.tokenCount, - isEdited: true, - }; - } catch (err) { - logger.error('Error updating message:', err); - throw new Error('Failed to update message.'); - } - }, - async deleteMessagesSince({ messageId, conversationId }) { - try { - const message = await Message.findOne({ messageId }).lean(); - - if (message) { - return await Message.find({ conversationId }).deleteMany({ - createdAt: { $gt: message.createdAt }, - }); - } - } catch (err) { - logger.error('Error deleting messages:', err); - throw new Error('Failed to delete messages.'); - } - }, - - /** - * Retrieves messages from the database. - * @param {Record} filter - * @param {string | undefined} [select] - * @returns - */ - async getMessages(filter, select) { - try { - if (select) { - return await Message.find(filter).select(select).sort({ createdAt: 1 }).lean(); - } - - return await Message.find(filter).sort({ createdAt: 1 }).lean(); - } catch (err) { - logger.error('Error getting messages:', err); - throw new Error('Failed to get messages.'); - } - }, - - async deleteMessages(filter) { - try { - return await Message.deleteMany(filter); - } catch (err) { - logger.error('Error deleting messages:', err); - throw new Error('Failed to delete messages.'); - } - }, +module.exports = { + Message, + saveMessage, + bulkSaveMessages, + recordMessage, + updateMessageText, + updateMessage, + deleteMessagesSince, + getMessages, + deleteMessages, }; diff --git a/api/models/Message.spec.js b/api/models/Message.spec.js new file mode 100644 index 0000000000..d5fbba9ed8 --- /dev/null +++ b/api/models/Message.spec.js @@ -0,0 +1,239 @@ +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); + +jest.mock('mongoose'); + +const mockFindQuery = { + select: jest.fn().mockReturnThis(), + sort: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + deleteMany: jest.fn().mockResolvedValue({ deletedCount: 1 }), +}; + +const mockSchema = { + findOneAndUpdate: jest.fn(), + updateOne: jest.fn(), + findOne: jest.fn(() => ({ + lean: jest.fn(), + })), + find: jest.fn(() => mockFindQuery), + deleteMany: jest.fn(), +}; + +mongoose.model.mockReturnValue(mockSchema); + +jest.mock('~/models/schema/messageSchema', () => mockSchema); + +jest.mock('~/config/winston', () => ({ + error: jest.fn(), +})); + +const { + saveMessage, + getMessages, + updateMessage, + deleteMessages, + updateMessageText, + deleteMessagesSince, +} = require('~/models/Message'); + +describe('Message Operations', () => { + let mockReq; + let mockMessage; + + beforeEach(() => { + jest.clearAllMocks(); + + mockReq = { + user: { id: 'user123' }, + }; + + mockMessage = { + messageId: 'msg123', + conversationId: uuidv4(), + text: 'Hello, world!', + user: 'user123', + }; + + mockSchema.findOneAndUpdate.mockResolvedValue({ + toObject: () => mockMessage, + }); + }); + + describe('saveMessage', () => { + it('should save a message for an authenticated user', async () => { + const result = await saveMessage(mockReq, mockMessage); + expect(result).toEqual(mockMessage); + expect(mockSchema.findOneAndUpdate).toHaveBeenCalledWith( + { messageId: 'msg123', user: 'user123' }, + expect.objectContaining({ user: 'user123' }), + expect.any(Object), + ); + }); + + it('should throw an error for unauthenticated user', async () => { + mockReq.user = null; + await expect(saveMessage(mockReq, mockMessage)).rejects.toThrow('User not authenticated'); + }); + + it('should throw an error for invalid conversation ID', async () => { + mockMessage.conversationId = 'invalid-id'; + await expect(saveMessage(mockReq, mockMessage)).rejects.toThrow('Invalid conversation ID'); + }); + }); + + describe('updateMessageText', () => { + it('should update message text for the authenticated user', async () => { + await updateMessageText(mockReq, { messageId: 'msg123', text: 'Updated text' }); + expect(mockSchema.updateOne).toHaveBeenCalledWith( + { messageId: 'msg123', user: 'user123' }, + { text: 'Updated text' }, + ); + }); + }); + + describe('updateMessage', () => { + it('should update a message for the authenticated user', async () => { + mockSchema.findOneAndUpdate.mockResolvedValue(mockMessage); + const result = await updateMessage(mockReq, { messageId: 'msg123', text: 'Updated text' }); + expect(result).toEqual( + expect.objectContaining({ + messageId: 'msg123', + text: 'Hello, world!', + isEdited: true, + }), + ); + }); + + it('should throw an error if message is not found', async () => { + mockSchema.findOneAndUpdate.mockResolvedValue(null); + await expect( + updateMessage(mockReq, { messageId: 'nonexistent', text: 'Test' }), + ).rejects.toThrow('Message not found or user not authorized.'); + }); + }); + + describe('deleteMessagesSince', () => { + it('should delete messages only for the authenticated user', async () => { + mockSchema.findOne().lean.mockResolvedValueOnce({ createdAt: new Date() }); + mockFindQuery.deleteMany.mockResolvedValueOnce({ deletedCount: 1 }); + const result = await deleteMessagesSince(mockReq, { + messageId: 'msg123', + conversationId: 'convo123', + }); + expect(mockSchema.findOne).toHaveBeenCalledWith({ messageId: 'msg123', user: 'user123' }); + expect(mockSchema.find).not.toHaveBeenCalled(); + expect(result).toBeUndefined(); + }); + + it('should return undefined if no message is found', async () => { + mockSchema.findOne().lean.mockResolvedValueOnce(null); + const result = await deleteMessagesSince(mockReq, { + messageId: 'nonexistent', + conversationId: 'convo123', + }); + expect(result).toBeUndefined(); + }); + }); + + describe('getMessages', () => { + it('should retrieve messages with the correct filter', async () => { + const filter = { conversationId: 'convo123' }; + await getMessages(filter); + expect(mockSchema.find).toHaveBeenCalledWith(filter); + expect(mockFindQuery.sort).toHaveBeenCalledWith({ createdAt: 1 }); + expect(mockFindQuery.lean).toHaveBeenCalled(); + }); + }); + + describe('deleteMessages', () => { + it('should delete messages with the correct filter', async () => { + await deleteMessages({ user: 'user123' }); + expect(mockSchema.deleteMany).toHaveBeenCalledWith({ user: 'user123' }); + }); + }); + + describe('Conversation Hijacking Prevention', () => { + it('should not allow editing a message in another user\'s conversation', async () => { + const attackerReq = { user: { id: 'attacker123' } }; + const victimConversationId = 'victim-convo-123'; + const victimMessageId = 'victim-msg-123'; + + mockSchema.findOneAndUpdate.mockResolvedValue(null); + + await expect( + updateMessage(attackerReq, { + messageId: victimMessageId, + conversationId: victimConversationId, + text: 'Hacked message', + }), + ).rejects.toThrow('Message not found or user not authorized.'); + + expect(mockSchema.findOneAndUpdate).toHaveBeenCalledWith( + { messageId: victimMessageId, user: 'attacker123' }, + expect.anything(), + expect.anything(), + ); + }); + + it('should not allow deleting messages from another user\'s conversation', async () => { + const attackerReq = { user: { id: 'attacker123' } }; + const victimConversationId = 'victim-convo-123'; + const victimMessageId = 'victim-msg-123'; + + mockSchema.findOne().lean.mockResolvedValueOnce(null); // Simulating message not found for this user + const result = await deleteMessagesSince(attackerReq, { + messageId: victimMessageId, + conversationId: victimConversationId, + }); + + expect(result).toBeUndefined(); + expect(mockSchema.findOne).toHaveBeenCalledWith({ + messageId: victimMessageId, + user: 'attacker123', + }); + }); + + it('should not allow inserting a new message into another user\'s conversation', async () => { + const attackerReq = { user: { id: 'attacker123' } }; + const victimConversationId = uuidv4(); // Use a valid UUID + + await expect( + saveMessage(attackerReq, { + conversationId: victimConversationId, + text: 'Inserted malicious message', + messageId: 'new-msg-123', + }), + ).resolves.not.toThrow(); // It should not throw an error + + // Check that the message was saved with the attacker's user ID + expect(mockSchema.findOneAndUpdate).toHaveBeenCalledWith( + { messageId: 'new-msg-123', user: 'attacker123' }, + expect.objectContaining({ + user: 'attacker123', + conversationId: victimConversationId, + }), + expect.anything(), + ); + }); + + it('should allow retrieving messages from any conversation', async () => { + const victimConversationId = 'victim-convo-123'; + + await getMessages({ conversationId: victimConversationId }); + + expect(mockSchema.find).toHaveBeenCalledWith({ + conversationId: victimConversationId, + }); + + mockSchema.find.mockReturnValueOnce({ + select: jest.fn().mockReturnThis(), + sort: jest.fn().mockReturnThis(), + lean: jest.fn().mockResolvedValue([{ text: 'Test message' }]), + }); + + const result = await getMessages({ conversationId: victimConversationId }); + expect(result).toEqual([{ text: 'Test message' }]); + }); + }); +}); diff --git a/api/server/controllers/AskController.js b/api/server/controllers/AskController.js index 2c07398f77..5b211d66b9 100644 --- a/api/server/controllers/AskController.js +++ b/api/server/controllers/AskController.js @@ -55,7 +55,7 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { const { onProgress: progressCallback, getPartialText } = createOnProgress({ onProgress: throttle( ({ text: partialText }) => { - saveMessage({ + saveMessage(req, { messageId: responseMessageId, sender, conversationId, @@ -144,11 +144,11 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { }); res.end(); - await saveMessage({ ...response, user }); + await saveMessage(req, { ...response, user }); } if (!client.skipSaveUserMessage) { - await saveMessage(userMessage); + await saveMessage(req, userMessage); } if (addTitle && parentMessageId === Constants.NO_PARENT && newConvo) { diff --git a/api/server/controllers/EditController.js b/api/server/controllers/EditController.js index 3315454d38..66114b0f6c 100644 --- a/api/server/controllers/EditController.js +++ b/api/server/controllers/EditController.js @@ -56,7 +56,7 @@ const EditController = async (req, res, next, initializeClient) => { generation, onProgress: throttle( ({ text: partialText }) => { - saveMessage({ + saveMessage(req, { messageId: responseMessageId, sender, conversationId, @@ -141,7 +141,7 @@ const EditController = async (req, res, next, initializeClient) => { }); res.end(); - await saveMessage({ ...response, user }); + await saveMessage(req, { ...response, user }); } } catch (error) { const partialText = getPartialText(); diff --git a/api/server/controllers/assistants/chatV1.js b/api/server/controllers/assistants/chatV1.js index 624f013af2..3107e78c79 100644 --- a/api/server/controllers/assistants/chatV1.js +++ b/api/server/controllers/assistants/chatV1.js @@ -120,21 +120,22 @@ const chatV1 = async (req, res) => { ? ' If using Azure OpenAI, files are only available in the region of the assistant\'s model at the time of upload.' : '' }`; - return sendResponse(res, messageData, errorMessage); + return sendResponse(req, res, messageData, errorMessage); } else if (error?.message?.includes('string too long')) { return sendResponse( + req, res, messageData, 'Message too long. The Assistants API has a limit of 32,768 characters per message. Please shorten it and try again.', ); } else if (error?.message?.includes(ViolationTypes.TOKEN_BALANCE)) { - return sendResponse(res, messageData, error.message); + return sendResponse(req, res, messageData, error.message); } else { logger.error('[/assistants/chat/]', error); } if (!openai || !thread_id || !run_id) { - return sendResponse(res, messageData, defaultErrorMessage); + return sendResponse(req, res, messageData, defaultErrorMessage); } await sleep(2000); @@ -221,10 +222,10 @@ const chatV1 = async (req, res) => { }; } catch (error) { logger.error('[/assistants/chat/] Error finalizing error process', error); - return sendResponse(res, messageData, 'The Assistant run failed'); + return sendResponse(req, res, messageData, 'The Assistant run failed'); } - return sendResponse(res, finalEvent); + return sendResponse(req, res, finalEvent); }; try { diff --git a/api/server/controllers/assistants/chatV2.js b/api/server/controllers/assistants/chatV2.js index 3b73d1520f..4e10201f73 100644 --- a/api/server/controllers/assistants/chatV2.js +++ b/api/server/controllers/assistants/chatV2.js @@ -117,21 +117,22 @@ const chatV2 = async (req, res) => { ? ' If using Azure OpenAI, files are only available in the region of the assistant\'s model at the time of upload.' : '' }`; - return sendResponse(res, messageData, errorMessage); + return sendResponse(req, res, messageData, errorMessage); } else if (error?.message?.includes('string too long')) { return sendResponse( + req, res, messageData, 'Message too long. The Assistants API has a limit of 32,768 characters per message. Please shorten it and try again.', ); } else if (error?.message?.includes(ViolationTypes.TOKEN_BALANCE)) { - return sendResponse(res, messageData, error.message); + return sendResponse(req, res, messageData, error.message); } else { logger.error('[/assistants/chat/]', error); } if (!openai || !thread_id || !run_id) { - return sendResponse(res, messageData, defaultErrorMessage); + return sendResponse(req, res, messageData, defaultErrorMessage); } await sleep(2000); @@ -218,10 +219,10 @@ const chatV2 = async (req, res) => { }; } catch (error) { logger.error('[/assistants/chat/] Error finalizing error process', error); - return sendResponse(res, messageData, 'The Assistant run failed'); + return sendResponse(req, res, messageData, 'The Assistant run failed'); } - return sendResponse(res, finalEvent); + return sendResponse(req, res, finalEvent); }; try { diff --git a/api/server/middleware/abortMiddleware.js b/api/server/middleware/abortMiddleware.js index 6b93bcac28..4ee5684a2f 100644 --- a/api/server/middleware/abortMiddleware.js +++ b/api/server/middleware/abortMiddleware.js @@ -116,7 +116,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => { { promptTokens, completionTokens }, ); - saveMessage({ ...responseMessage, user }); + saveMessage(req, { ...responseMessage, user }); let conversation; if (userMessagePromise) { @@ -190,7 +190,7 @@ const handleAbortError = async (res, req, error, data) => { } }; - await sendError(res, options, callback); + await sendError(req, res, options, callback); }; if (partialText && partialText.length > 5) { diff --git a/api/server/middleware/denyRequest.js b/api/server/middleware/denyRequest.js index 37952176bf..8e89bccee0 100644 --- a/api/server/middleware/denyRequest.js +++ b/api/server/middleware/denyRequest.js @@ -41,10 +41,10 @@ const denyRequest = async (req, res, errorMessage) => { const shouldSaveMessage = _convoId && parentMessageId && parentMessageId !== Constants.NO_PARENT; if (shouldSaveMessage) { - await saveMessage({ ...userMessage, user: req.user.id }); + await saveMessage(req, { ...userMessage, user: req.user.id }); } - return await sendError(res, { + return await sendError(req, res, { sender: getResponseSender(req.body), messageId: crypto.randomUUID(), conversationId, diff --git a/api/server/middleware/validateImageRequest.js b/api/server/middleware/validateImageRequest.js index c0e8e5fe83..e07e48cc71 100644 --- a/api/server/middleware/validateImageRequest.js +++ b/api/server/middleware/validateImageRequest.js @@ -31,10 +31,14 @@ function validateImageRequest(req, res, next) { return res.status(403).send('Access Denied'); } - if (req.path.includes(payload.id)) { + const fullPath = decodeURIComponent(req.originalUrl); + const pathPattern = new RegExp(`^/images/${payload.id}/[^/]+$`); + + if (pathPattern.test(fullPath)) { logger.debug('[validateImageRequest] Image request validated'); next(); } else { + logger.warn('[validateImageRequest] Invalid image path'); res.status(403).send('Access Denied'); } } diff --git a/api/server/routes/ask/askChatGPTBrowser.js b/api/server/routes/ask/askChatGPTBrowser.js index 4ce1770b8e..8b4be397a0 100644 --- a/api/server/routes/ask/askChatGPTBrowser.js +++ b/api/server/routes/ask/askChatGPTBrowser.js @@ -51,7 +51,7 @@ router.post('/', setHeaders, async (req, res) => { }); if (!overrideParentMessageId) { - await saveMessage({ ...userMessage, user: req.user.id }); + await saveMessage(req, { ...userMessage, user: req.user.id }); await saveConvo(req.user.id, { ...userMessage, ...endpointOption, @@ -93,7 +93,7 @@ const ask = async ({ const currentTimestamp = Date.now(); if (currentTimestamp - lastSavedTimestamp > 500) { lastSavedTimestamp = currentTimestamp; - saveMessage({ + saveMessage(req, { messageId: responseMessageId, sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI', conversationId, @@ -159,7 +159,7 @@ const ask = async ({ isCreatedByUser: false, }; - await saveMessage({ ...responseMessage, user }); + await saveMessage(req, { ...responseMessage, user }); responseMessage.messageId = newResponseMessageId; // STEP2 update the conversation @@ -192,7 +192,7 @@ const ask = async ({ // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. if (!overrideParentMessageId) { - await saveMessage({ + await saveMessage(req, { ...userMessage, user, messageId: userMessageId, @@ -229,7 +229,7 @@ const ask = async ({ isCreatedByUser: false, text: `${getPartialMessage() ?? ''}\n\nError message: "${error.message}"`, }; - await saveMessage({ ...errorMessage, user }); + await saveMessage(req, { ...errorMessage, user }); handleError(res, errorMessage); } }; diff --git a/api/server/routes/ask/bingAI.js b/api/server/routes/ask/bingAI.js index 916cda4b10..b5763c3b3d 100644 --- a/api/server/routes/ask/bingAI.js +++ b/api/server/routes/ask/bingAI.js @@ -70,7 +70,7 @@ router.post('/', setHeaders, async (req, res) => { }); if (!overrideParentMessageId) { - await saveMessage({ ...userMessage, user: req.user.id }); + await saveMessage(req, { ...userMessage, user: req.user.id }); await saveConvo(req.user.id, { ...userMessage, ...endpointOption, @@ -118,7 +118,7 @@ const ask = async ({ const currentTimestamp = Date.now(); if (currentTimestamp - lastSavedTimestamp > 500) { lastSavedTimestamp = currentTimestamp; - saveMessage({ + saveMessage(req, { messageId: responseMessageId, sender: model, conversationId, @@ -197,7 +197,7 @@ const ask = async ({ isCreatedByUser: false, }; - await saveMessage({ ...responseMessage, user }); + await saveMessage(req, { ...responseMessage, user }); responseMessage.messageId = newResponseMessageId; let conversationUpdate = { @@ -221,7 +221,7 @@ const ask = async ({ // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. if (!overrideParentMessageId) { - await saveMessage({ + await saveMessage(req, { ...userMessage, user, messageId: userMessageId, @@ -266,7 +266,7 @@ const ask = async ({ isCreatedByUser: false, }; - saveMessage({ ...responseMessage, user }); + saveMessage(req, { ...responseMessage, user }); return { title: await getConvoTitle(user, conversationId), @@ -288,7 +288,7 @@ const ask = async ({ model, isCreatedByUser: false, }; - await saveMessage({ ...errorMessage, user }); + await saveMessage(req, { ...errorMessage, user }); handleError(res, errorMessage); } } diff --git a/api/server/routes/ask/gptPlugins.js b/api/server/routes/ask/gptPlugins.js index 1db3e333dc..299bb199f2 100644 --- a/api/server/routes/ask/gptPlugins.js +++ b/api/server/routes/ask/gptPlugins.js @@ -85,7 +85,7 @@ router.post( clearTimeout(timer); } - throttledSaveMessage({ + throttledSaveMessage(req, { messageId: responseMessageId, sender, conversationId, @@ -170,7 +170,7 @@ router.post( const onChainEnd = () => { if (!client.skipSaveUserMessage) { - saveMessage({ ...userMessage, user }); + saveMessage(req, { ...userMessage, user }); } sendIntermediateMessage(res, { plugins, @@ -208,7 +208,7 @@ router.post( logger.debug('[/ask/gptPlugins]', response); response.plugins = plugins.map((p) => ({ ...p, loading: false })); - await saveMessage({ ...response, user }); + await saveMessage(req, { ...response, user }); const { conversation = {} } = await client.responsePromise; conversation.title = diff --git a/api/server/routes/edit/gptPlugins.js b/api/server/routes/edit/gptPlugins.js index 4db05bd493..0e4a77567b 100644 --- a/api/server/routes/edit/gptPlugins.js +++ b/api/server/routes/edit/gptPlugins.js @@ -91,7 +91,7 @@ router.post( plugin.loading = false; } - throttledSaveMessage({ + throttledSaveMessage(req, { messageId: responseMessageId, sender, conversationId, @@ -110,7 +110,7 @@ router.post( let { intermediateSteps: steps } = data; plugin.outputs = steps && steps[0].action ? formatSteps(steps) : 'An error occurred.'; plugin.loading = false; - saveMessage({ ...userMessage, user }); + saveMessage(req, { ...userMessage, user }); sendIntermediateMessage(res, { plugin, parentMessageId: userMessage.messageId, @@ -141,7 +141,7 @@ router.post( plugin.inputs.push(formattedAction); plugin.latest = formattedAction.plugin; if (!start && !client.skipSaveUserMessage) { - saveMessage({ ...userMessage, user }); + saveMessage(req, { ...userMessage, user }); } sendIntermediateMessage(res, { plugin, @@ -180,7 +180,7 @@ router.post( logger.debug('[/edit/gptPlugins] CLIENT RESPONSE', response); response.plugin = { ...plugin, loading: false }; - await saveMessage({ ...response, user }); + await saveMessage(req, { ...response, user }); const { conversation = {} } = await client.responsePromise; conversation.title = diff --git a/api/server/routes/messages.js b/api/server/routes/messages.js index e0bdadfc50..f647540acd 100644 --- a/api/server/routes/messages.js +++ b/api/server/routes/messages.js @@ -1,46 +1,42 @@ const express = require('express'); const router = express.Router(); -const { - getMessages, - updateMessage, - saveConvo, - saveMessage, - deleteMessages, -} = require('../../models'); -const { countTokens } = require('../utils'); -const { requireJwtAuth, validateMessageReq } = require('../middleware/'); +const { saveConvo, saveMessage, getMessages, updateMessage, deleteMessages } = require('~/models'); +const { requireJwtAuth, validateMessageReq } = require('~/server/middleware'); +const { countTokens } = require('~/server/utils'); router.use(requireJwtAuth); +router.use(validateMessageReq); -router.get('/:conversationId', validateMessageReq, async (req, res) => { +router.get('/:conversationId', async (req, res) => { const { conversationId } = req.params; res.status(200).send(await getMessages({ conversationId }, '-_id -__v -user')); }); // CREATE -router.post('/:conversationId', validateMessageReq, async (req, res) => { +router.post('/:conversationId', async (req, res) => { const message = req.body; - const savedMessage = await saveMessage({ ...message, user: req.user.id }); + const savedMessage = await saveMessage(req, { ...message, user: req.user.id }); await saveConvo(req.user.id, savedMessage); res.status(201).send(savedMessage); }); // READ -router.get('/:conversationId/:messageId', validateMessageReq, async (req, res) => { +router.get('/:conversationId/:messageId', async (req, res) => { const { conversationId, messageId } = req.params; res.status(200).send(await getMessages({ conversationId, messageId }, '-_id -__v -user')); }); // UPDATE -router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) => { +router.put('/:conversationId/:messageId', async (req, res) => { const { messageId, model } = req.params; const { text } = req.body; const tokenCount = await countTokens(text, model); - res.status(201).json(await updateMessage({ messageId, text, tokenCount })); + const result = await updateMessage(req, { messageId, text, tokenCount }); + res.status(201).json(result); }); // DELETE -router.delete('/:conversationId/:messageId', validateMessageReq, async (req, res) => { +router.delete('/:conversationId/:messageId', async (req, res) => { const { messageId } = req.params; await deleteMessages({ messageId }); res.status(204).send(); diff --git a/api/server/services/Runs/StreamRunManager.js b/api/server/services/Runs/StreamRunManager.js index 01c97c0f79..71eb0b0100 100644 --- a/api/server/services/Runs/StreamRunManager.js +++ b/api/server/services/Runs/StreamRunManager.js @@ -143,7 +143,7 @@ class StreamRunManager { * @returns {Promise} */ async saveInitialMessage() { - return saveMessage({ + return saveMessage(this.req, { conversationId: this.finalMessage.conversationId, messageId: this.finalMessage.messageId, parentMessageId: this.parentMessageId, diff --git a/api/server/utils/streamResponse.js b/api/server/utils/streamResponse.js index b7a691d91a..0f042339a9 100644 --- a/api/server/utils/streamResponse.js +++ b/api/server/utils/streamResponse.js @@ -30,7 +30,8 @@ const sendMessage = (res, message, event = 'message') => { /** * Processes an error with provided options, saves the error message and sends a corresponding SSE response * @async - * @param {object} res - The server response. + * @param {object} req - The request. + * @param {object} res - The response. * @param {object} options - The options for handling the error containing message properties. * @param {object} options.user - The user ID. * @param {string} options.sender - The sender of the message. @@ -41,7 +42,7 @@ const sendMessage = (res, message, event = 'message') => { * @param {boolean} options.shouldSaveMessage - [Optional] Whether the message should be saved. Default is true. * @param {function} callback - [Optional] The callback function to be executed. */ -const sendError = async (res, options, callback) => { +const sendError = async (req, res, options, callback) => { const { user, sender, @@ -69,7 +70,7 @@ const sendError = async (res, options, callback) => { } if (shouldSaveMessage) { - await saveMessage({ ...errorMessage, user }); + await saveMessage(req, { ...errorMessage, user }); } if (!errorMessage.error) { @@ -97,11 +98,12 @@ const sendError = async (res, options, callback) => { /** * Sends the response based on whether headers have been sent or not. + * @param {Express.Request} req - The server response. * @param {Express.Response} res - The server response. * @param {Object} data - The data to be sent. * @param {string} [errorMessage] - The error message, if any. */ -const sendResponse = (res, data, errorMessage) => { +const sendResponse = (req, res, data, errorMessage) => { if (!res.headersSent) { if (errorMessage) { return res.status(500).json({ error: errorMessage }); @@ -110,7 +112,7 @@ const sendResponse = (res, data, errorMessage) => { } if (errorMessage) { - return sendError(res, { ...data, text: errorMessage }); + return sendError(req, res, { ...data, text: errorMessage }); } return sendMessage(res, data); }; From d5782ac66ccac7a0b07b832491ff54780f7fde6a Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:07:11 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8=20feat:=20auto=20send=20text=20sl?= =?UTF-8?q?ider=20(#3312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: convert main component to float * feat: convert the remaining components * feat: use `recoilState` instead of `recoilValue` * feat: replaced `AutoSendTextSwitch` to `AutoSendTextSelector` * feat: use `autoSendText` in the `useSpeechToTextExternal` hook * fix: `autoSendText` timeout --- client/src/components/Chat/Input/ChatForm.tsx | 4 +- .../Speech/ConversationModeSwitch.tsx | 8 +- .../Speech/STT/AutoSendTextSelector.tsx | 50 + .../Speech/STT/AutoSendTextSwitch.tsx | 35 - .../Speech/STT/AutoTranscribeAudioSwitch.tsx | 2 +- .../Speech/STT/DecibelSelector.tsx | 4 +- .../STT/__tests__/AutoSendTextSwitch.spec.tsx | 38 - .../Nav/SettingsTabs/Speech/STT/index.ts | 2 +- .../Nav/SettingsTabs/Speech/Speech.tsx | 6 +- .../hooks/Input/useSpeechToTextExternal.ts | 7 +- client/src/localization/languages/Eng.ts | 3 +- client/src/localization/languages/Fi.ts | 1323 +++++++++-------- client/src/store/settings.ts | 2 +- packages/data-provider/src/config.ts | 2 +- 14 files changed, 737 insertions(+), 749 deletions(-) create mode 100644 client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSelector.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSwitch.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Speech/STT/__tests__/AutoSendTextSwitch.spec.tsx diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 2ad4580eea..f2d3caefcb 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -38,8 +38,8 @@ const ChatForm = ({ index = 0 }) => { const submitButtonRef = useRef(null); const textAreaRef = useRef(null); - const SpeechToText = useRecoilValue(store.speechToText); - const TextToSpeech = useRecoilValue(store.textToSpeech); + const SpeechToText = useRecoilState(store.speechToText); + const TextToSpeech = useRecoilState(store.textToSpeech); const automaticPlayback = useRecoilValue(store.automaticPlayback); const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index)); diff --git a/client/src/components/Nav/SettingsTabs/Speech/ConversationModeSwitch.tsx b/client/src/components/Nav/SettingsTabs/Speech/ConversationModeSwitch.tsx index 4f3bba67d9..7510175dfc 100644 --- a/client/src/components/Nav/SettingsTabs/Speech/ConversationModeSwitch.tsx +++ b/client/src/components/Nav/SettingsTabs/Speech/ConversationModeSwitch.tsx @@ -10,15 +10,15 @@ export default function ConversationModeSwitch({ }) { const localize = useLocalize(); const [conversationMode, setConversationMode] = useRecoilState(store.conversationMode); - const [speechToText] = useRecoilState(store.speechToText); - const [textToSpeech] = useRecoilState(store.textToSpeech); - const [, setAutoSendText] = useRecoilState(store.autoSendText); + const speechToText = useRecoilState(store.speechToText); + const textToSpeech = useRecoilState(store.textToSpeech); + const [, setAutoSendText] = useRecoilState(store.autoSendText); const [, setDecibelValue] = useRecoilState(store.decibelValue); const [, setAutoTranscribeAudio] = useRecoilState(store.autoTranscribeAudio); const handleCheckedChange = (value: boolean) => { setAutoTranscribeAudio(value); - setAutoSendText(value); + setAutoSendText(3); setDecibelValue(-45); setConversationMode(value); if (onCheckedChange) { diff --git a/client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSelector.tsx b/client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSelector.tsx new file mode 100644 index 0000000000..06ece50490 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSelector.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { useRecoilState } from 'recoil'; +import { cn, defaultTextProps, optionText } from '~/utils/'; +import { Slider, InputNumber } from '~/components/ui'; +import { useLocalize } from '~/hooks'; +import store from '~/store'; + +export default function AutoSendTextSelector() { + const localize = useLocalize(); + + const speechToText = useRecoilState(store.speechToText); + const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText); + + return ( +
+
+
{localize('com_nav_auto_send_text')}
+
+ ({localize('com_nav_auto_send_text_disabled')}) +
+
+ setAutoSendText(value[0])} + doubleClickHandler={() => setAutoSendText(-1)} + min={-1} + max={60} + step={1} + className="ml-4 flex h-4 w-24" + disabled={!speechToText} + /> +
+ setAutoSendText(value ? value[0] : 0)} + min={-1} + max={60} + 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', + ), + )} + /> +
+
+ ); +} diff --git a/client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSwitch.tsx b/client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSwitch.tsx deleted file mode 100644 index 2d47095b23..0000000000 --- a/client/src/components/Nav/SettingsTabs/Speech/STT/AutoSendTextSwitch.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useRecoilState } from 'recoil'; -import { Switch } from '~/components/ui'; -import { useLocalize } from '~/hooks'; -import store from '~/store'; - -export default function AutoSendTextSwitch({ - onCheckedChange, -}: { - onCheckedChange?: (value: boolean) => void; -}) { - const localize = useLocalize(); - const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText); - const [SpeechToText] = useRecoilState(store.speechToText); - - const handleCheckedChange = (value: boolean) => { - setAutoSendText(value); - if (onCheckedChange) { - onCheckedChange(value); - } - }; - - return ( -
-
{localize('com_nav_auto_send_text')}
- -
- ); -} diff --git a/client/src/components/Nav/SettingsTabs/Speech/STT/AutoTranscribeAudioSwitch.tsx b/client/src/components/Nav/SettingsTabs/Speech/STT/AutoTranscribeAudioSwitch.tsx index 99bd794173..a9c6b3ea5a 100644 --- a/client/src/components/Nav/SettingsTabs/Speech/STT/AutoTranscribeAudioSwitch.tsx +++ b/client/src/components/Nav/SettingsTabs/Speech/STT/AutoTranscribeAudioSwitch.tsx @@ -12,7 +12,7 @@ export default function AutoTranscribeAudioSwitch({ const [autoTranscribeAudio, setAutoTranscribeAudio] = useRecoilState( store.autoTranscribeAudio, ); - const [speechToText] = useRecoilState(store.speechToText); + const speechToText = useRecoilState(store.speechToText); const handleCheckedChange = (value: boolean) => { setAutoTranscribeAudio(value); diff --git a/client/src/components/Nav/SettingsTabs/Speech/STT/DecibelSelector.tsx b/client/src/components/Nav/SettingsTabs/Speech/STT/DecibelSelector.tsx index 1796f11eb8..30ff66dc0d 100755 --- a/client/src/components/Nav/SettingsTabs/Speech/STT/DecibelSelector.tsx +++ b/client/src/components/Nav/SettingsTabs/Speech/STT/DecibelSelector.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState } from 'recoil'; import { Slider, InputNumber } from '~/components/ui'; import { useLocalize } from '~/hooks'; import store from '~/store'; @@ -7,7 +7,7 @@ import { cn, defaultTextProps, optionText } from '~/utils/'; export default function DecibelSelector() { const localize = useLocalize(); - const speechToText = useRecoilValue(store.speechToText); + const speechToText = useRecoilState(store.speechToText); const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue); return ( diff --git a/client/src/components/Nav/SettingsTabs/Speech/STT/__tests__/AutoSendTextSwitch.spec.tsx b/client/src/components/Nav/SettingsTabs/Speech/STT/__tests__/AutoSendTextSwitch.spec.tsx deleted file mode 100644 index 95b394798d..0000000000 --- a/client/src/components/Nav/SettingsTabs/Speech/STT/__tests__/AutoSendTextSwitch.spec.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import '@testing-library/jest-dom/extend-expect'; -import { render, fireEvent } from 'test/layout-test-utils'; -import AutoSendTextSwitch from '../AutoSendTextSwitch'; -import { RecoilRoot } from 'recoil'; - -describe('AutoSendTextSwitch', () => { - /** - * Mock function to set the auto-send-text state. - */ - let mockSetAutoSendText: jest.Mock | ((value: boolean) => void) | undefined; - - beforeEach(() => { - mockSetAutoSendText = jest.fn(); - }); - - it('renders correctly', () => { - const { getByTestId } = render( - - - , - ); - - expect(getByTestId('AutoSendText')).toBeInTheDocument(); - }); - - it('calls onCheckedChange when the switch is toggled', () => { - const { getByTestId } = render( - - - , - ); - const switchElement = getByTestId('AutoSendText'); - fireEvent.click(switchElement); - - expect(mockSetAutoSendText).toHaveBeenCalledWith(true); - }); -}); diff --git a/client/src/components/Nav/SettingsTabs/Speech/STT/index.ts b/client/src/components/Nav/SettingsTabs/Speech/STT/index.ts index 904f7bbdf2..e78c4b7b1d 100644 --- a/client/src/components/Nav/SettingsTabs/Speech/STT/index.ts +++ b/client/src/components/Nav/SettingsTabs/Speech/STT/index.ts @@ -1,4 +1,4 @@ -export { default as AutoSendTextSwitch } from './AutoSendTextSwitch'; +export { default as AutoSendTextSelector } from './AutoSendTextSelector'; export { default as SpeechToTextSwitch } from './SpeechToTextSwitch'; export { default as EngineSTTDropdown } from './EngineSTTDropdown'; export { default as DecibelSelector } from './DecibelSelector'; diff --git a/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx b/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx index 010a7f09f7..f382fa3fb2 100644 --- a/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx +++ b/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx @@ -20,7 +20,7 @@ import { AutoTranscribeAudioSwitch, LanguageSTTDropdown, SpeechToTextSwitch, - AutoSendTextSwitch, + AutoSendTextSelector, EngineSTTDropdown, DecibelSelector, } from './STT'; @@ -220,8 +220,8 @@ function Speech() {
)} -
- +
+
diff --git a/client/src/hooks/Input/useSpeechToTextExternal.ts b/client/src/hooks/Input/useSpeechToTextExternal.ts index 3eb74bcd1b..371d5d22a2 100644 --- a/client/src/hooks/Input/useSpeechToTextExternal.ts +++ b/client/src/hooks/Input/useSpeechToTextExternal.ts @@ -10,7 +10,7 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void const { externalSpeechToText } = useGetAudioSettings(); const [speechToText] = useRecoilState(store.speechToText); const [autoTranscribeAudio] = useRecoilState(store.autoTranscribeAudio); - const [autoSendText] = useRecoilState(store.autoSendText); + const [autoSendText] = useRecoilState(store.autoSendText); const [text, setText] = useState(''); const [isListening, setIsListening] = useState(false); const [permission, setPermission] = useState(false); @@ -27,10 +27,11 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void const extractedText = data.text; setText(extractedText); setIsRequestBeingMade(false); - if (autoSendText && speechToText && extractedText.length > 0) { + + if (autoSendText > -1 && speechToText && extractedText.length > 0) { setTimeout(() => { onTranscriptionComplete(extractedText); - }, 3000); + }, autoSendText * 1000); } }, onError: () => { diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index 8c8919d7b0..4575ec2dbb 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -628,7 +628,8 @@ export default { com_nav_delete_warning: 'WARNING: This will permanently delete your account.', com_nav_delete_data_info: 'All your data will be deleted.', com_nav_conversation_mode: 'Conversation Mode', - com_nav_auto_send_text: 'Auto send text (after 3 sec)', + com_nav_auto_send_text: 'Auto send text', + com_nav_auto_send_text_disabled: 'set -1 to disable', com_nav_auto_transcribe_audio: 'Auto transcribe audio', com_nav_db_sensitivity: 'Decibel sensitivity', com_nav_playback_rate: 'Audio Playback Rate', diff --git a/client/src/localization/languages/Fi.ts b/client/src/localization/languages/Fi.ts index f4857de2cd..a9ad9e4551 100644 --- a/client/src/localization/languages/Fi.ts +++ b/client/src/localization/languages/Fi.ts @@ -3,662 +3,671 @@ // file deepcode ignore HardcodedNonCryptoSecret: No hardcoded secrets present in this file export default { - com_error_moderation: - 'Näyttää siltä, että moderointijärjestelmämme merkitsi lähetetyn sisällön yhteisön sääntöjen vastaisiksi. Emme voi jatkaa tämän aiheen käsittelyä. Jos sinulla on muita kysymyksiä tai aiheita joita haluaisit käsitellä, ole hyvä ja muokkaa viestiäsi, tai aloita uusi keskustelu.', - com_error_no_user_key: 'Avainta ei löytynyt. Lisää avain ja yritä uudestaan.', - com_error_no_base_url: 'Base URL puuttuu. Syötä URL ja yritä uudestaan.', - com_error_invalid_user_key: 'Avain ei kelpaa. Lisää toimiva avain ja yritä uudestaan.', - com_error_expired_user_key: - '{0} varten annettu avain vanheni {1}. Syötä uusi avain ja yritä uudestaan.', - com_files_no_results: 'Ei tuloksia.', - com_files_filter: 'Suodata tiedostoja...', - com_files_number_selected: '{0}/{1} tiedostoa valittu', - com_sidepanel_select_assistant: 'Valitse Avustaja', - com_sidepanel_parameters: 'Parametrit', - com_sidepanel_assistant_builder: 'Avustajan rakentaminen', - com_sidepanel_hide_panel: 'Piilota sivupalkki', - com_sidepanel_attach_files: 'Liitä tiedostoja', - com_sidepanel_manage_files: 'Hallinnoi tiedostoja', - com_assistants_capabilities: 'Kyvykkyydet', - com_assistants_file_search: 'Tiedostohaku', - com_assistants_file_search_info: - 'Vektoritietokannan liittämistä tiedostohakuun ei vielä tueta. Voit liittää ne rajapinnan palveluntarjoajan käyttöliittymän kautta, tai liittää tiedostoja viesteihin keskusteluketjupohjaisesti.', - com_assistants_knowledge: 'Tiedot', - com_assistants_knowledge_info: - 'Jos lataat tiedostoja Tietoihin, Avustajasi kanssa käytyihin keskusteluihin voi tulla niiden sisältöä.', - com_assistants_knowledge_disabled: - 'Avustaja täytyy ensin luoda, ja Kooditulkki tai Tiedonhaku täytyy olla päällä ja asetukset tallennettuna, ennen kuin tiedostoja voidaan ladata Tietoihin.', - com_assistants_image_vision: 'Kuvanäkö', - com_assistants_code_interpreter: 'Kooditulkki', - com_assistants_code_interpreter_files: - 'Seuraavat tiedostot ovat vain Kooditulkin käytettävissä:', - com_assistants_retrieval: 'Tiedonhaku', - com_assistants_search_name: 'Hae Avustajia nimen perusteella', - com_assistants_tools: 'Työkalut', - com_assistants_actions: 'Toiminnot', - com_assistants_add_tools: 'Lisää Työkaluja', - com_assistants_add_actions: 'Lisää Toimintoja', - com_assistants_non_retrieval_model: - 'Tiedostohaku ei ole käytössä tässä mallissa. Valitse toinen malli.', - com_assistants_available_actions: 'Käytettävissä olevat Toiminnot', - com_assistants_running_action: 'Suoritetaan toimintoa', - com_assistants_completed_action: 'Puhuttiin {0}:lle', - com_assistants_completed_function: 'Suoritettiin {0}', - com_assistants_function_use: 'Avustaja käytti: {0}', - com_assistants_domain_info: 'Avustaja lähetti tiedon tänne: {0}', - com_assistants_delete_actions_success: 'Toiminto poistettiin Avustajalta onnistuneesti', - com_assistants_update_actions_success: 'Toiminto luotiiin tai päivitettiin onnistuneesti', - com_assistants_update_actions_error: 'Toiminnon luomisessa tai päivittämisessä tapahtui virhe.', - com_assistants_delete_actions_error: 'Toiminnon poistamisessa tapahtui virhe.', - com_assistants_actions_info: 'Salli Avustajalle Tiedonhaku tai Toimintojen suorittaminen API-kutsujen kautta', - com_assistants_name_placeholder: 'Valinnainen: Avustajan nimi', - com_assistants_instructions_placeholder: 'Avustajan käyttämät järjestelmäohjeet', - com_assistants_description_placeholder: 'Valinnainen: Kuvaus Avustajasta', - com_assistants_actions_disabled: 'Avustaja täytyy luoda ennen toimintojen lisäämistä', - com_assistants_update_success: 'Päivitys onnistui', - com_assistants_update_error: 'Avustajan päivittämisessä tapahtui virhe.', - com_assistants_create_success: 'Luonti onnistui', - com_assistants_create_error: 'Avustajan luonnissa tapahtui virhe.', - com_ui_date_today: 'Tänään', - com_ui_date_yesterday: 'Eilen', - com_ui_date_previous_7_days: 'Edelliset 7 päivää', - com_ui_date_previous_30_days: 'Edelliset 30 päivää', - com_ui_date_january: 'Tammikuu', - com_ui_date_february: 'Helmikuu', - com_ui_date_march: 'Maaliskuu', - com_ui_date_april: 'Huhtikuu', - com_ui_date_may: 'Toukokuu', - com_ui_date_june: 'Kesäkuu', - com_ui_date_july: 'Heinäkuu', - com_ui_date_august: 'Elokuu', - com_ui_date_september: 'Syyskuu', - com_ui_date_october: 'Lokakuu', - com_ui_date_november: 'Marraskuu', - com_ui_date_december: 'Joulukuu', - com_ui_field_required: 'Tämä kenttä on pakollinen', - com_ui_download_error: 'Virhe tiedoston lataamisesta. Tiedosto on saatettu poistaa.', - com_ui_attach_error_type: 'Päätepiste ei tue tiedostotyyppiä::', - com_ui_attach_error_openai: 'Avustajan tiedostoja ei voi liittää muihin päätepisteisiin', - com_ui_attach_warn_endpoint: 'Ilman yhteensopivaa työkalua muut kuin Avustajan tiedostot voidaan jättää huomiotta.', - com_ui_attach_error_size: 'Tiedoston koko ylittää päätepisteen rajan:', - com_ui_attach_error: - 'Tiedosto ei voi liittää. Luo tai valitse keskustelu, tai kokeile ladata sivu uudestaan.', - com_ui_examples: 'Esimerkkejä', - com_ui_new_chat: 'Uusi keskustelu', - com_ui_happy_birthday: 'On 1. syntymäpäiväni!', - com_ui_experimental: 'Kokeelliset ominaisuudet', - com_ui_on: 'Päällä', - com_ui_off: 'Pois', - com_ui_yes: 'Kyllä', - com_ui_no: 'Ei', - com_ui_ascending: 'Nouseva', - com_ui_descending: 'Laskeva', - com_ui_show_all: 'Näytä kaikki', - com_ui_name: 'Nimi', - com_ui_date: 'Päivämäärä', - com_ui_storage: 'Varasto', - com_ui_context: 'Konteksti', - com_ui_size: 'Koko', - com_ui_host: 'Host', - com_ui_update: 'Päivitys', - com_ui_authentication: 'Autentikointi', - com_ui_instructions: 'Ohjeet', - com_ui_description: 'Kuvaus', - com_ui_error: 'Virhe', - com_ui_error_connection: 'Palvelimeen yhdistäessä tapahtui virhe. Kokeile ladata sivu uudestaan.', - com_ui_select: 'Valitse', - com_ui_input: 'Syöte', - com_ui_close: 'Sulje', - com_ui_model: 'Malli', - com_ui_select_model: 'Valitse malli', - com_ui_select_search_model: 'Hae mallia nimen perusteella', - com_ui_select_search_plugin: 'Hae lisäosaa nimen perusteella', - com_ui_use_prompt: 'Käytä syötettä', - com_ui_prev: 'Edellinen', - com_ui_next: 'Seuraava', - com_ui_stop: 'Pysäytä', - com_ui_upload_files: 'Lataa tiedostoja', - com_ui_prompt: 'Syöte', - com_ui_prompts: 'Syötteet', - com_ui_prompt_name: 'Syötteen nimi', - com_ui_delete_prompt: 'Poista syöte?', - com_ui_admin: 'Ylläpito', - com_ui_simple: 'Yksinkertainen', - com_ui_versions: 'Versiot', - com_ui_version_var: 'Versio {0}', - com_ui_advanced: 'Edistynyt', - com_ui_admin_settings: 'Ylläpitoasetukset', - com_ui_error_save_admin_settings: 'Ylläpitoasetusten tallentamisessa tapahtui virhe.', - com_ui_prompt_preview_not_shared: 'Tekijä ei ole sallinut yhteistyötä tälle syötteelle.', - com_ui_prompt_name_required: 'Syötteen nimi on pakollinen', - com_ui_prompt_text_required: 'Teksti on pakollinen', - com_ui_prompt_text: 'Teksti', - com_ui_back_to_chat: 'Palaa keskusteluun', - com_ui_back_to_prompts: 'Palaa syötteisiin', - com_ui_categories: 'Kategoriat', - com_ui_filter_prompts_name: 'Syötteiden nimisuodatus', - com_ui_search_categories: 'Hakukategoriat', - com_ui_manage: 'Hallinnoi', - com_ui_variables: 'Muuttujat', - com_ui_variables_info: - 'Käytä kaksoisaaltosulkeita tekstissäsi muuttujien luomiseen, esim. {{esimerkkimuuttuja}}. Muuttujia voi täyttää myöhemmin syötettä käyttäessä.', - com_ui_special_variables: - 'Erikoismuuttujat: Käytä {{current_date}} kuluvaa päivämäärää varten, ja {{current_user}} käyttäjätunnustasi varten.', - com_ui_showing: 'Näytetään', - com_ui_of: '/', - com_ui_entries: 'Merkinnät', - com_ui_pay_per_call: 'Kaikki tekoälykeskustelut yhdessä paikassa. Maksa kerrasta, älä kuukaudesta.', - com_ui_new_footer: 'Kaikki tekoälykeskustelut yhdessä paikassa.', - com_ui_latest_footer: 'Kaikki tekoälyt kaikille.', - com_ui_enter: 'Syötä', - com_ui_submit: 'Lähetä', - com_ui_none_selected: 'Ei valintaa', - com_ui_upload_success: 'Tiedoston lataus onnistui', - com_ui_upload_error: 'Tiedoston lataamisessa tapahtui virhe', - com_ui_upload_invalid: 'Virheellinen ladattava tiedosto. Tiedoston täytyy olla kokorajaan mahtuva kuvatiedosto', - com_ui_upload_invalid_var: 'Virheellinen ladattava tiedosto. Tiedoston täytyy olla enintään {0} MB kokoinen kuvatiedosto', - com_ui_cancel: 'Peruuta', - com_ui_save: 'Tallenna', - com_ui_renaming_var: 'Uudelleennimetään "{0}"', - com_ui_save_submit: 'Tallenna & Lähetä', - com_user_message: 'Sinä', - com_ui_read_aloud: 'Lue ääneen', - com_ui_copied: 'Kopioitu!', - com_ui_copy_code: 'Kopioi koodi', - com_ui_copy_to_clipboard: 'Kopioi leikepöydälle', - com_ui_copied_to_clipboard: 'Kopioitu leikepöydältä', - com_ui_fork: 'Haarauta', - com_ui_fork_info_1: 'Käytä tätä asetusta viestien haarauttamiseen halutulla tavalla.', - com_ui_fork_info_2: - '"Haarauttaminen" luo uuden keskustelun siten, että se alkaa/päättyy tietyistä tämänhetkisen keskustelun viesteistä, luoden kopion halutulla tavalla.', - com_ui_fork_info_3: - '"Kohdeviesti" tarkoittaa joko viestiä, josta tämä ponnahdusikkuna avattiin, tai, jos rastitat "{0}", viimeisintä viestiä keskustelussa.', - com_ui_fork_info_visible: - 'Tämä vaihtoehto haarauttaa vain näkyvissä olevat viestit; toisin sanoen, suoran polun kohdeviestiin, ilman sivupolkuja.', - com_ui_fork_info_branches: - 'Tämä vaihtoehto haarauttaa näkyvissä olevat viestit sekä niihin liittyvät sivupolut; toisin sanoen, suoran polun kohdeviestiin sisällyttäen matkalla olevat sivupolut.', - com_ui_fork_info_target: - 'Tämä vaihtoehto haarauttaa kaikki viestit kohdeviestiin asti, sisällyttäen sen naapurit; toisin sanoen, kaikki sivupolut riippumatta siitä ovatko ne näkyvissä tai samalla polulla tulevat matkaan.', - com_ui_fork_info_start: - 'Jos tämä on valittu, haarauttaminen alkaa tästä viestistä keskustelun viimeiseen viestiin saakka, yllä valitun toimintatavan mukaisesti.', - com_ui_fork_info_remember: - 'Jos tämä on valittu, tallentaa tehdyt valinnat tulevaa jatkokäyttöä varten nopeuttaen keskusteluhaarojen luomista samoilla asetuksilla.', - com_ui_fork_success: 'Keskustelun haarauttaminen onnistui.', - com_ui_fork_processing: 'Haarautetaan keskustelua...', - com_ui_fork_error: 'Keskustelun haarauttamisessa tapahtui virhe', - com_ui_fork_change_default: 'Oletushaarautustapa', - com_ui_fork_default: 'Käytä oletushaarautustapaa', - com_ui_fork_remember: 'Muista', - com_ui_fork_split_target_setting: 'Aloita haara oletuksena kohdeviestistä', - com_ui_fork_split_target: 'Aloita haara tästä', - com_ui_fork_remember_checked: - 'Valintasi muistetaan käytön jälkeen. Voit muuttaa tätä milloin tahansa asetuksista.', - com_ui_fork_all_target: 'Sisällytä kaikki tänne/täältä', - com_ui_fork_branches: 'Sisällytä sivupolut', - com_ui_fork_visible: 'Vain näkyvät viestit', - com_ui_fork_from_message: 'Valitse haarautustapa', - com_ui_mention: 'Mainitse päätepiste, Avustaja tai asetus vaihtaaksesi siihen pikana', - com_ui_add: 'Lisää malli tai esiasetus lisävastausta varten', - com_ui_regenerate: 'Luo uudestaan', - com_ui_continue: 'Jatka', - com_ui_edit: 'Muokkaa', - com_ui_loading: 'Ladataan...', - com_ui_success: 'Onnistui', - com_ui_all: 'kaikki', - com_ui_all_proper: 'Kaikki', - com_ui_clear: 'Tyhjennä', - com_ui_revoke: 'Peruuta', - com_ui_revoke_info: 'Peruuta kaikki käyttäjän antamat tunnisteet', - com_ui_import_conversation: 'Tuo', - com_ui_nothing_found: 'Mitään ei löytynyt', - com_ui_go_to_conversation: 'Siirry keskusteluun', - com_ui_import_conversation_info: 'Tuo keskusteluja JSON-tiedostosta', - com_ui_import_conversation_success: 'Keskustelujen tuonti onnistui', - com_ui_import_conversation_error: 'Keskustelujesi tuonnissa tapahtui virhe', - com_ui_import_conversation_file_type_error: 'Tiedostotyyppi ei ole tuettu tuonnissa', - com_ui_confirm_action: 'Vahvista toiminto', - com_ui_chat: 'Keskustelu', - com_ui_dashboard: 'Työpöytä', - com_ui_chats: 'keskustelut', - com_ui_avatar: 'Profiilikuva', - com_ui_unknown: 'Tuntematon', - com_ui_result: 'Tulos', - com_ui_image_gen: 'Kuvanluonti', - com_ui_assistant: 'Avustaja', - com_ui_assistant_deleted: 'Avustajan poisto onnistui', - com_ui_assistant_delete_error: 'Avustajan poistossa tapahtui virhe', - com_ui_assistants: 'Avustajat', - com_ui_attachment: 'Liitetiedosto', - com_ui_assistants_output: 'Avustajien tuotokset', - com_ui_delete: 'Poista', - com_ui_create: 'Luo', - com_ui_create_prompt: 'Luo syöte', - com_ui_share: 'Jaa', - com_ui_share_var: 'Jaa {0}', - com_ui_copy_link: 'Kopioi linkki', - com_ui_update_link: 'Päivitä linkki', - com_ui_create_link: 'Luo linkki', - com_ui_share_to_all_users: 'Jaa kaikille käyttäjille', - com_ui_my_prompts: 'Omat syötteet', - com_ui_no_category: 'Ei kategoriaa', - com_ui_shared_prompts: 'Jaetut syötteet', - com_ui_prompts_allow_use: 'Salli syötteiden käyttäminen', - com_ui_prompts_allow_create: 'Salli syötteiden luominen', - com_ui_prompts_allow_share_global: 'Salli syötteiden jakaminen kaikille käyttäjille', - com_ui_prompt_shared_to_all: 'Tämä syöte on jaettu kaikille käyttäjille', - com_ui_prompt_update_error: 'Syötteen päivityksessä tapahtui virhe', - com_ui_prompt_already_shared_to_all: 'Tämä syöte on jo jaettu kaikille käyttäjille', - com_ui_description_placeholder: 'Valinnainen: Lisää kuvaus syötteelle', - com_ui_command_placeholder: 'Valinnainen: Käsky syötteelle. Oletuskäskynä on nimi.', - com_ui_command_usage_placeholder: 'Valitse syöte käskyn tai nimen perusteella', - com_ui_no_prompt_description: 'Kuvausta ei löytynyt.', - com_ui_share_link_to_chat: 'Jaa linkki keskusteluun', - com_ui_share_error: 'Keskustelulinkin jakamisessa tapahtui virhe', - com_ui_share_retrieve_error: 'Jaettujen linkkien jakamisessa tapahtui virhe', - com_ui_share_delete_error: 'Jaetun linkin poistossa tapahtui virhe', - com_ui_share_create_message: 'Nimesi ja jakamisen jälkeen lisätäämäsi viestit pysyvät yksityisinä.', - com_ui_share_created_message: - 'Jakolinkki keskusteluun on luotu. Hallinnoi aiemmin jaettuja keskusteluja milloin vain Asetusten kautta.', - com_ui_share_update_message: - 'Nimesi, mukautetut ohjeet, ja mahdolliset viestit jotka lisäät jakamisen jälkeen jäävät yksityisiksi.', - com_ui_share_updated_message: - 'Keskustelun jakolinkki on päivitetty. Hallinnoi aiemmin jaettuja keskusteluja milloin vain Asetusten kautta.', - com_ui_shared_link_not_found: 'Jakolinkki ei löytynyt', - com_ui_delete_conversation: 'Poista keskustelu?', - com_ui_delete_confirm: 'Tämä suorittaa poiston', - com_ui_delete_confirm_prompt_version_var: - 'Tämä poistaa valitun version "{0}":lta. Jos muita versioita ei ole, syöte poistetaan samalla.', - com_ui_delete_assistant_confirm: - 'Haluatko varmasti poistaa tämän Avustajan? Poistoa ei voi perua.', - com_ui_rename: 'Nimeä uudestaan', - com_ui_archive: 'Arkisto', - com_ui_archive_error: 'Keskustelun arkistointi epäonnistui', - com_ui_unarchive: 'Palauta arkistosta', - com_ui_unarchive_error: 'Palautus arkistosta epäonnistui', - com_ui_more_options: 'Lisää', - com_ui_preview: 'Esikatsele', - com_ui_upload: 'Lataa', - com_ui_connect: 'Yhdistä', - com_ui_locked: 'Lukittu', - com_ui_upload_delay: - '"{0}" lataaminen kestää odotettua pidempään. Ole hyvä ja odota kunnes tiedosto saadaan indeksoitua tiedonhakua varten.', - com_ui_privacy_policy: 'Tietosuojailmoitus', - com_ui_terms_of_service: 'Käyttöehdot', - com_ui_use_micrphone: 'Käytä mikrofonia', - com_ui_min_tags: 'Enempää arvoja ei voida poistaa. Niiden minimimäärä on {0}.', - com_ui_max_tags: 'Maksimimäärä on {0}. käytetään viimeisimpiä arvoja.', - com_auth_error_login: - 'Kirjautuminen annetuilla tiedoilla ei onnistunut. Tarkista kirjautumistiedot, ja yritä uudestaan.', - com_auth_error_login_rl: - 'Liian monta kirjautumisyritystä lyhyen ajan sisällä. Yritä myöhemmin uudestaan.', - com_auth_error_login_ban: - 'Tilisi on väliaikaisesti suljettu palvelun sääntöjen rikkomisesta.', - com_auth_error_login_server: - 'Tapahtui sisäinen palvelinvirhe. Odota hetki, ja yritä uudestaan.', - com_auth_error_login_unverified: - 'Tiliäsi ei ole vahvistettu. Vahvistuslinkin pitäisi löytyä sähköposteistasi.', - com_auth_no_account: 'Ei tunnusta?', - com_auth_sign_up: 'Rekisteröidy', - com_auth_sign_in: 'Kirjaudu', - com_auth_google_login: 'Jatka Googlella', - com_auth_facebook_login: 'Jatka Facebookilla', - com_auth_github_login: 'Jatka Githubilla', - com_auth_discord_login: 'Jatka Discordilla', - com_auth_email: 'Sähköposti', - com_auth_email_required: 'Sähköposti on pakollinen', - com_auth_email_min_length: 'Sähköpostiosoitteen on oltava vähintään 6 merkkiä pitkä', - com_auth_email_max_length: 'Sähköpostiosoitteen ei pitäisi olla 120 merkkiä pidempi', - com_auth_email_pattern: 'Sähköpostiosoite on syötettävä oikeassa muodossa', - com_auth_email_address: 'Sähköpostiosoite Email address', - com_auth_password: 'Salasana', - com_auth_password_required: 'Salasana on pakollinen', - com_auth_password_min_length: 'Salasanan on oltava vähintään 8 merkkiä pitkä', - com_auth_password_max_length: 'Salasana voi olla enintään 128 merkkiä', - com_auth_password_forgot: 'Salasana unohtunut?', - com_auth_password_confirm: 'Vahvista salasana', - com_auth_password_not_match: 'Salasanat eivät täsmää', - com_auth_continue: 'Jatka', - com_auth_create_account: 'Luo tili', - com_auth_error_create: - 'Tilin rekisteröinnissä tapahtui virhe. Yritä uudestaan.', - com_auth_full_name: 'Koko nimi', - com_auth_name_required: 'Nimi on pakollinen', - com_auth_name_min_length: 'Nimessä on oltava vähintään 3 merkkiä', - com_auth_name_max_length: 'Nimi voi olla enintään 80 merkkiä pitkä', - com_auth_username: 'Käyttäjänimi (valinnainen)', - com_auth_username_required: 'Käyttäjänimi on pakollinen', - com_auth_username_min_length: 'Käyttäjänimessä on oltava vähintään 2 merkkiä', - com_auth_username_max_length: 'Käyttäjänimi voi olla enintään 20 merkkiä pitkä', - com_auth_already_have_account: 'Käyttäjätilisi on jo luotu?', - com_auth_login: 'Kirjaudu', - com_auth_registration_success_insecure: 'Rekisteröityminen onnistui.', - com_auth_registration_success_generic: 'Tarkista sähköpostisi sähköpostiosoitteen vahvistamiseksi.', - com_auth_reset_password: 'Aseta uusi salasana', - com_auth_click: 'Napauta', - com_auth_here: 'TÄTÄ', - com_auth_to_reset_your_password: 'asettaaksesi uuden salasanan.', - com_auth_reset_password_link_sent: 'Sähköposti lähetetty', - com_auth_reset_password_if_email_exists: - 'Jos kyseiselle sähköpostiosoitteelle löytyy käyttäjätili, siihen lähetetään sähköposti joka sisältää ohjeet salasanan uusimiseen. Tarkistathan myös roskapostikansion.', - com_auth_reset_password_email_sent: - 'Jos käyttäjä on rekisteröitynyt, salasanan vaihto-ohjeet lähetetään hänelle sähköpostitse.', - com_auth_reset_password_success: 'Salasanan asettaminen onnistui', - com_auth_login_with_new_password: 'Voit nyt kirjautua uudella salasanallasi.', - com_auth_error_invalid_reset_token: 'Tämä salasanan uusimistunniste ei ole enää voimassa.', - com_auth_click_here: 'Napauta tästä', - com_auth_to_try_again: 'kokeillaksesi uudestaan.', - com_auth_submit_registration: 'Lähetä rekisteröityminen', - com_auth_welcome_back: 'Tervetuloa takaisin', - com_auth_back_to_login: 'Palaa kirjautumiseen', - com_auth_email_verification_failed: 'Sähköpostin varmentaminen epäonnistui', - com_auth_email_verification_rate_limited: 'Liian monta yritystä. Kokeile myöhemmin uudestaan', - com_auth_email_verification_success: 'Sähköposti varmennettu', - com_auth_email_resent_success: 'Varmennussähköpostin uudelleenlähetys onnistui', - com_auth_email_resent_failed: 'Varmennussähköpostin uudelleenlähetys epäonnistui', - com_auth_email_verification_failed_token_missing: 'Varmennus epäonnistui tunnisteen puuttumisen vuoksi', - com_auth_email_verification_invalid: 'Sähköpostin varmentaminen ei voimassa', - com_auth_email_verification_in_progress: 'Varmennetaan sähköpostia. Ole hyvä ja odota.', - com_auth_email_verification_resend_prompt: 'Sähköposti ei saapunut perille?', - com_auth_email_resend_link: 'Lähetä sähköposti uudestaan', - com_auth_email_verification_redirecting: 'Uudelleenohjataan {0} sekunnissa...', - com_endpoint_open_menu: 'Avaa valikko', - com_endpoint_bing_enable_sydney: 'Ota Sydney käyttöön', - com_endpoint_bing_to_enable_sydney: 'Ottaaksesi Sydneyn käyttöön', - com_endpoint_bing_jailbreak: 'Jailbreak', - com_endpoint_bing_context_placeholder: - 'Bing voi käyttää jopa 7000 tokenia keskustelussa viittausympäristönä käytettyä \'kontekstia\' varten. Tarkka raja ei ole tiedossa, mutta yli 7000 tokenia käyttäessä voi esiintyä virheitä.', - com_endpoint_bing_system_message_placeholder: - 'VAROITUS: Tämän ominaisuuden väärinkäyttö saattaa johtaa Bingin KÄYTTÖKIELTOON! Napauta \'Järjestelmäviestiä\', niin saat täydet ohjeet ja oletusviestin (jos jätetty pois), mikä on turvalliseksi katsottu \'Sydney\'-esiasetus.', - com_endpoint_system_message: 'Järjestelmäviesti', - com_endpoint_message: 'Vastaanottajana', - com_endpoint_messages: 'Viestit', - com_endpoint_message_not_appendable: 'Muokkaa viestiäsi tai Luo uudestaan.', - com_endpoint_default_blank: 'oletus: tyhjä', - com_endpoint_default_false: 'oletus: false', - com_endpoint_default_creative: 'oletus: creative', - com_endpoint_default_empty: 'oletus: tyhjä', - com_endpoint_default_with_num: 'oletus: {0}', - com_endpoint_context: 'Konteksti', - com_endpoint_tone_style: 'Tyylisävy', - com_endpoint_token_count: 'Token-määrä', - com_endpoint_output: 'Tulos', - com_endpoint_context_tokens: 'Konteksti-tokenien maksimimäärä', - com_endpoint_context_info: `Kontekstia varten käytettävien tokeneiden maksimimäärä. Käytä tätä pyyntökohtaisten token-määrien hallinnointiin. Jos tätä ei määritetä, käytössä ovat järjestelmän oletusarvot perustuen tiedossa olevien mallien konteksti-ikkunoiden kokoon. Korkeamman arvon asettaminen voi aiheuttaa virheitä tai korkeamman token-hinnan.`, - com_endpoint_google_temp: - 'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', - com_endpoint_google_topp: - 'Top-P vaikuttaa siihen kuinka malli valitsee tokeneita tulokseen. Tokenit valitaan top-k:sta (ks. Top-k -parametri) todennäköisimmistä vähiten todennäköseen, kunnes niiden todennäköisyyksien summa ylittää Top-P -arvon.', - com_endpoint_google_topk: - 'Top-k vaikuttaa siihen, miten malli valitsee tokeineita tulokseen. Jos Top-k on 1, valitaan se token, joka on kaikkien todennäköisen mallin sanastossa (tunnetaan myös nimellä ahne dekoodaus), kun taas top-k 3 tarkoittaisi, että seuraavat token valitaan 3 todennäköisimmän tokenin joukosta, lämpötilaa hyödyntäen.', - com_endpoint_google_maxoutputtokens: - 'Maksimimäärä tokeneillre, joita generoidaan tulokseen. Valitse pienempi arvo saadaksesi lyhyempiä vastauksia, ja suurempi arvo pitkiä vastauksia varten.', - com_endpoint_google_custom_name_placeholder: 'Aseta Googlelle mukautettu nimi', - com_endpoint_prompt_prefix_placeholder: 'Aseta mukautetut ohjeet tai konteksti. Jätetään huomiotta, jos tyhjä.', - com_endpoint_instructions_assistants_placeholder: - 'Yliajaa Avustajan ohjeet. Tätä voi hyödyntää käytöksen muuttamiseen keskustelukohtaisesti.', - com_endpoint_prompt_prefix_assistants_placeholder: - 'Anna lisäohjeita tai kontekstia Avustajan pääohjeiden lisäksi. Set additional instructions or context on top of the Assistant\'s main instructions. Jätetään huomiotta, jos tyhjä.', - com_endpoint_custom_name: 'Mukautettu nimi', - com_endpoint_prompt_prefix: 'Mukautetut ohjeet', - com_endpoint_prompt_prefix_assistants: 'Lisäohjeet', - com_endpoint_instructions_assistants: 'Yliaja ohjeet', - com_endpoint_temperature: 'Lämpötila', - com_endpoint_default: 'oletus', - com_endpoint_top_p: 'Top P', - com_endpoint_top_k: 'Top k', - com_endpoint_max_output_tokens: 'Tulos-tokeneiden maksimimäärä', - com_endpoint_stop: 'Pysäytyssekvenssit', - com_endpoint_stop_placeholder: 'Erota arvot toisistaan rivinvaihdoilla', - com_endpoint_openai_max_tokens: `Valinnainen \`max_tokens\` -kenttä, joka kuvaa keskustelun vastauksessa generoitujen tokeneiden maksimimäärää. + com_error_moderation: + 'Näyttää siltä, että moderointijärjestelmämme merkitsi lähetetyn sisällön yhteisön sääntöjen vastaisiksi. Emme voi jatkaa tämän aiheen käsittelyä. Jos sinulla on muita kysymyksiä tai aiheita joita haluaisit käsitellä, ole hyvä ja muokkaa viestiäsi, tai aloita uusi keskustelu.', + com_error_no_user_key: 'Avainta ei löytynyt. Lisää avain ja yritä uudestaan.', + com_error_no_base_url: 'Base URL puuttuu. Syötä URL ja yritä uudestaan.', + com_error_invalid_user_key: 'Avain ei kelpaa. Lisää toimiva avain ja yritä uudestaan.', + com_error_expired_user_key: + '{0} varten annettu avain vanheni {1}. Syötä uusi avain ja yritä uudestaan.', + com_files_no_results: 'Ei tuloksia.', + com_files_filter: 'Suodata tiedostoja...', + com_files_number_selected: '{0}/{1} tiedostoa valittu', + com_sidepanel_select_assistant: 'Valitse Avustaja', + com_sidepanel_parameters: 'Parametrit', + com_sidepanel_assistant_builder: 'Avustajan rakentaminen', + com_sidepanel_hide_panel: 'Piilota sivupalkki', + com_sidepanel_attach_files: 'Liitä tiedostoja', + com_sidepanel_manage_files: 'Hallinnoi tiedostoja', + com_assistants_capabilities: 'Kyvykkyydet', + com_assistants_file_search: 'Tiedostohaku', + com_assistants_file_search_info: + 'Vektoritietokannan liittämistä tiedostohakuun ei vielä tueta. Voit liittää ne rajapinnan palveluntarjoajan käyttöliittymän kautta, tai liittää tiedostoja viesteihin keskusteluketjupohjaisesti.', + com_assistants_knowledge: 'Tiedot', + com_assistants_knowledge_info: + 'Jos lataat tiedostoja Tietoihin, Avustajasi kanssa käytyihin keskusteluihin voi tulla niiden sisältöä.', + com_assistants_knowledge_disabled: + 'Avustaja täytyy ensin luoda, ja Kooditulkki tai Tiedonhaku täytyy olla päällä ja asetukset tallennettuna, ennen kuin tiedostoja voidaan ladata Tietoihin.', + com_assistants_image_vision: 'Kuvanäkö', + com_assistants_code_interpreter: 'Kooditulkki', + com_assistants_code_interpreter_files: 'Seuraavat tiedostot ovat vain Kooditulkin käytettävissä:', + com_assistants_retrieval: 'Tiedonhaku', + com_assistants_search_name: 'Hae Avustajia nimen perusteella', + com_assistants_tools: 'Työkalut', + com_assistants_actions: 'Toiminnot', + com_assistants_add_tools: 'Lisää Työkaluja', + com_assistants_add_actions: 'Lisää Toimintoja', + com_assistants_non_retrieval_model: + 'Tiedostohaku ei ole käytössä tässä mallissa. Valitse toinen malli.', + com_assistants_available_actions: 'Käytettävissä olevat Toiminnot', + com_assistants_running_action: 'Suoritetaan toimintoa', + com_assistants_completed_action: 'Puhuttiin {0}:lle', + com_assistants_completed_function: 'Suoritettiin {0}', + com_assistants_function_use: 'Avustaja käytti: {0}', + com_assistants_domain_info: 'Avustaja lähetti tiedon tänne: {0}', + com_assistants_delete_actions_success: 'Toiminto poistettiin Avustajalta onnistuneesti', + com_assistants_update_actions_success: 'Toiminto luotiiin tai päivitettiin onnistuneesti', + com_assistants_update_actions_error: 'Toiminnon luomisessa tai päivittämisessä tapahtui virhe.', + com_assistants_delete_actions_error: 'Toiminnon poistamisessa tapahtui virhe.', + com_assistants_actions_info: + 'Salli Avustajalle Tiedonhaku tai Toimintojen suorittaminen API-kutsujen kautta', + com_assistants_name_placeholder: 'Valinnainen: Avustajan nimi', + com_assistants_instructions_placeholder: 'Avustajan käyttämät järjestelmäohjeet', + com_assistants_description_placeholder: 'Valinnainen: Kuvaus Avustajasta', + com_assistants_actions_disabled: 'Avustaja täytyy luoda ennen toimintojen lisäämistä', + com_assistants_update_success: 'Päivitys onnistui', + com_assistants_update_error: 'Avustajan päivittämisessä tapahtui virhe.', + com_assistants_create_success: 'Luonti onnistui', + com_assistants_create_error: 'Avustajan luonnissa tapahtui virhe.', + com_ui_date_today: 'Tänään', + com_ui_date_yesterday: 'Eilen', + com_ui_date_previous_7_days: 'Edelliset 7 päivää', + com_ui_date_previous_30_days: 'Edelliset 30 päivää', + com_ui_date_january: 'Tammikuu', + com_ui_date_february: 'Helmikuu', + com_ui_date_march: 'Maaliskuu', + com_ui_date_april: 'Huhtikuu', + com_ui_date_may: 'Toukokuu', + com_ui_date_june: 'Kesäkuu', + com_ui_date_july: 'Heinäkuu', + com_ui_date_august: 'Elokuu', + com_ui_date_september: 'Syyskuu', + com_ui_date_october: 'Lokakuu', + com_ui_date_november: 'Marraskuu', + com_ui_date_december: 'Joulukuu', + com_ui_field_required: 'Tämä kenttä on pakollinen', + com_ui_download_error: 'Virhe tiedoston lataamisesta. Tiedosto on saatettu poistaa.', + com_ui_attach_error_type: 'Päätepiste ei tue tiedostotyyppiä::', + com_ui_attach_error_openai: 'Avustajan tiedostoja ei voi liittää muihin päätepisteisiin', + com_ui_attach_warn_endpoint: + 'Ilman yhteensopivaa työkalua muut kuin Avustajan tiedostot voidaan jättää huomiotta.', + com_ui_attach_error_size: 'Tiedoston koko ylittää päätepisteen rajan:', + com_ui_attach_error: + 'Tiedosto ei voi liittää. Luo tai valitse keskustelu, tai kokeile ladata sivu uudestaan.', + com_ui_examples: 'Esimerkkejä', + com_ui_new_chat: 'Uusi keskustelu', + com_ui_happy_birthday: 'On 1. syntymäpäiväni!', + com_ui_experimental: 'Kokeelliset ominaisuudet', + com_ui_on: 'Päällä', + com_ui_off: 'Pois', + com_ui_yes: 'Kyllä', + com_ui_no: 'Ei', + com_ui_ascending: 'Nouseva', + com_ui_descending: 'Laskeva', + com_ui_show_all: 'Näytä kaikki', + com_ui_name: 'Nimi', + com_ui_date: 'Päivämäärä', + com_ui_storage: 'Varasto', + com_ui_context: 'Konteksti', + com_ui_size: 'Koko', + com_ui_host: 'Host', + com_ui_update: 'Päivitys', + com_ui_authentication: 'Autentikointi', + com_ui_instructions: 'Ohjeet', + com_ui_description: 'Kuvaus', + com_ui_error: 'Virhe', + com_ui_error_connection: 'Palvelimeen yhdistäessä tapahtui virhe. Kokeile ladata sivu uudestaan.', + com_ui_select: 'Valitse', + com_ui_input: 'Syöte', + com_ui_close: 'Sulje', + com_ui_model: 'Malli', + com_ui_select_model: 'Valitse malli', + com_ui_select_search_model: 'Hae mallia nimen perusteella', + com_ui_select_search_plugin: 'Hae lisäosaa nimen perusteella', + com_ui_use_prompt: 'Käytä syötettä', + com_ui_prev: 'Edellinen', + com_ui_next: 'Seuraava', + com_ui_stop: 'Pysäytä', + com_ui_upload_files: 'Lataa tiedostoja', + com_ui_prompt: 'Syöte', + com_ui_prompts: 'Syötteet', + com_ui_prompt_name: 'Syötteen nimi', + com_ui_delete_prompt: 'Poista syöte?', + com_ui_admin: 'Ylläpito', + com_ui_simple: 'Yksinkertainen', + com_ui_versions: 'Versiot', + com_ui_version_var: 'Versio {0}', + com_ui_advanced: 'Edistynyt', + com_ui_admin_settings: 'Ylläpitoasetukset', + com_ui_error_save_admin_settings: 'Ylläpitoasetusten tallentamisessa tapahtui virhe.', + com_ui_prompt_preview_not_shared: 'Tekijä ei ole sallinut yhteistyötä tälle syötteelle.', + com_ui_prompt_name_required: 'Syötteen nimi on pakollinen', + com_ui_prompt_text_required: 'Teksti on pakollinen', + com_ui_prompt_text: 'Teksti', + com_ui_back_to_chat: 'Palaa keskusteluun', + com_ui_back_to_prompts: 'Palaa syötteisiin', + com_ui_categories: 'Kategoriat', + com_ui_filter_prompts_name: 'Syötteiden nimisuodatus', + com_ui_search_categories: 'Hakukategoriat', + com_ui_manage: 'Hallinnoi', + com_ui_variables: 'Muuttujat', + com_ui_variables_info: + 'Käytä kaksoisaaltosulkeita tekstissäsi muuttujien luomiseen, esim. {{esimerkkimuuttuja}}. Muuttujia voi täyttää myöhemmin syötettä käyttäessä.', + com_ui_special_variables: + 'Erikoismuuttujat: Käytä {{current_date}} kuluvaa päivämäärää varten, ja {{current_user}} käyttäjätunnustasi varten.', + com_ui_showing: 'Näytetään', + com_ui_of: '/', + com_ui_entries: 'Merkinnät', + com_ui_pay_per_call: + 'Kaikki tekoälykeskustelut yhdessä paikassa. Maksa kerrasta, älä kuukaudesta.', + com_ui_new_footer: 'Kaikki tekoälykeskustelut yhdessä paikassa.', + com_ui_latest_footer: 'Kaikki tekoälyt kaikille.', + com_ui_enter: 'Syötä', + com_ui_submit: 'Lähetä', + com_ui_none_selected: 'Ei valintaa', + com_ui_upload_success: 'Tiedoston lataus onnistui', + com_ui_upload_error: 'Tiedoston lataamisessa tapahtui virhe', + com_ui_upload_invalid: + 'Virheellinen ladattava tiedosto. Tiedoston täytyy olla kokorajaan mahtuva kuvatiedosto', + com_ui_upload_invalid_var: + 'Virheellinen ladattava tiedosto. Tiedoston täytyy olla enintään {0} MB kokoinen kuvatiedosto', + com_ui_cancel: 'Peruuta', + com_ui_save: 'Tallenna', + com_ui_renaming_var: 'Uudelleennimetään "{0}"', + com_ui_save_submit: 'Tallenna & Lähetä', + com_user_message: 'Sinä', + com_ui_read_aloud: 'Lue ääneen', + com_ui_copied: 'Kopioitu!', + com_ui_copy_code: 'Kopioi koodi', + com_ui_copy_to_clipboard: 'Kopioi leikepöydälle', + com_ui_copied_to_clipboard: 'Kopioitu leikepöydältä', + com_ui_fork: 'Haarauta', + com_ui_fork_info_1: 'Käytä tätä asetusta viestien haarauttamiseen halutulla tavalla.', + com_ui_fork_info_2: + '"Haarauttaminen" luo uuden keskustelun siten, että se alkaa/päättyy tietyistä tämänhetkisen keskustelun viesteistä, luoden kopion halutulla tavalla.', + com_ui_fork_info_3: + '"Kohdeviesti" tarkoittaa joko viestiä, josta tämä ponnahdusikkuna avattiin, tai, jos rastitat "{0}", viimeisintä viestiä keskustelussa.', + com_ui_fork_info_visible: + 'Tämä vaihtoehto haarauttaa vain näkyvissä olevat viestit; toisin sanoen, suoran polun kohdeviestiin, ilman sivupolkuja.', + com_ui_fork_info_branches: + 'Tämä vaihtoehto haarauttaa näkyvissä olevat viestit sekä niihin liittyvät sivupolut; toisin sanoen, suoran polun kohdeviestiin sisällyttäen matkalla olevat sivupolut.', + com_ui_fork_info_target: + 'Tämä vaihtoehto haarauttaa kaikki viestit kohdeviestiin asti, sisällyttäen sen naapurit; toisin sanoen, kaikki sivupolut riippumatta siitä ovatko ne näkyvissä tai samalla polulla tulevat matkaan.', + com_ui_fork_info_start: + 'Jos tämä on valittu, haarauttaminen alkaa tästä viestistä keskustelun viimeiseen viestiin saakka, yllä valitun toimintatavan mukaisesti.', + com_ui_fork_info_remember: + 'Jos tämä on valittu, tallentaa tehdyt valinnat tulevaa jatkokäyttöä varten nopeuttaen keskusteluhaarojen luomista samoilla asetuksilla.', + com_ui_fork_success: 'Keskustelun haarauttaminen onnistui.', + com_ui_fork_processing: 'Haarautetaan keskustelua...', + com_ui_fork_error: 'Keskustelun haarauttamisessa tapahtui virhe', + com_ui_fork_change_default: 'Oletushaarautustapa', + com_ui_fork_default: 'Käytä oletushaarautustapaa', + com_ui_fork_remember: 'Muista', + com_ui_fork_split_target_setting: 'Aloita haara oletuksena kohdeviestistä', + com_ui_fork_split_target: 'Aloita haara tästä', + com_ui_fork_remember_checked: + 'Valintasi muistetaan käytön jälkeen. Voit muuttaa tätä milloin tahansa asetuksista.', + com_ui_fork_all_target: 'Sisällytä kaikki tänne/täältä', + com_ui_fork_branches: 'Sisällytä sivupolut', + com_ui_fork_visible: 'Vain näkyvät viestit', + com_ui_fork_from_message: 'Valitse haarautustapa', + com_ui_mention: 'Mainitse päätepiste, Avustaja tai asetus vaihtaaksesi siihen pikana', + com_ui_add: 'Lisää malli tai esiasetus lisävastausta varten', + com_ui_regenerate: 'Luo uudestaan', + com_ui_continue: 'Jatka', + com_ui_edit: 'Muokkaa', + com_ui_loading: 'Ladataan...', + com_ui_success: 'Onnistui', + com_ui_all: 'kaikki', + com_ui_all_proper: 'Kaikki', + com_ui_clear: 'Tyhjennä', + com_ui_revoke: 'Peruuta', + com_ui_revoke_info: 'Peruuta kaikki käyttäjän antamat tunnisteet', + com_ui_import_conversation: 'Tuo', + com_ui_nothing_found: 'Mitään ei löytynyt', + com_ui_go_to_conversation: 'Siirry keskusteluun', + com_ui_import_conversation_info: 'Tuo keskusteluja JSON-tiedostosta', + com_ui_import_conversation_success: 'Keskustelujen tuonti onnistui', + com_ui_import_conversation_error: 'Keskustelujesi tuonnissa tapahtui virhe', + com_ui_import_conversation_file_type_error: 'Tiedostotyyppi ei ole tuettu tuonnissa', + com_ui_confirm_action: 'Vahvista toiminto', + com_ui_chat: 'Keskustelu', + com_ui_dashboard: 'Työpöytä', + com_ui_chats: 'keskustelut', + com_ui_avatar: 'Profiilikuva', + com_ui_unknown: 'Tuntematon', + com_ui_result: 'Tulos', + com_ui_image_gen: 'Kuvanluonti', + com_ui_assistant: 'Avustaja', + com_ui_assistant_deleted: 'Avustajan poisto onnistui', + com_ui_assistant_delete_error: 'Avustajan poistossa tapahtui virhe', + com_ui_assistants: 'Avustajat', + com_ui_attachment: 'Liitetiedosto', + com_ui_assistants_output: 'Avustajien tuotokset', + com_ui_delete: 'Poista', + com_ui_create: 'Luo', + com_ui_create_prompt: 'Luo syöte', + com_ui_share: 'Jaa', + com_ui_share_var: 'Jaa {0}', + com_ui_copy_link: 'Kopioi linkki', + com_ui_update_link: 'Päivitä linkki', + com_ui_create_link: 'Luo linkki', + com_ui_share_to_all_users: 'Jaa kaikille käyttäjille', + com_ui_my_prompts: 'Omat syötteet', + com_ui_no_category: 'Ei kategoriaa', + com_ui_shared_prompts: 'Jaetut syötteet', + com_ui_prompts_allow_use: 'Salli syötteiden käyttäminen', + com_ui_prompts_allow_create: 'Salli syötteiden luominen', + com_ui_prompts_allow_share_global: 'Salli syötteiden jakaminen kaikille käyttäjille', + com_ui_prompt_shared_to_all: 'Tämä syöte on jaettu kaikille käyttäjille', + com_ui_prompt_update_error: 'Syötteen päivityksessä tapahtui virhe', + com_ui_prompt_already_shared_to_all: 'Tämä syöte on jo jaettu kaikille käyttäjille', + com_ui_description_placeholder: 'Valinnainen: Lisää kuvaus syötteelle', + com_ui_command_placeholder: 'Valinnainen: Käsky syötteelle. Oletuskäskynä on nimi.', + com_ui_command_usage_placeholder: 'Valitse syöte käskyn tai nimen perusteella', + com_ui_no_prompt_description: 'Kuvausta ei löytynyt.', + com_ui_share_link_to_chat: 'Jaa linkki keskusteluun', + com_ui_share_error: 'Keskustelulinkin jakamisessa tapahtui virhe', + com_ui_share_retrieve_error: 'Jaettujen linkkien jakamisessa tapahtui virhe', + com_ui_share_delete_error: 'Jaetun linkin poistossa tapahtui virhe', + com_ui_share_create_message: + 'Nimesi ja jakamisen jälkeen lisätäämäsi viestit pysyvät yksityisinä.', + com_ui_share_created_message: + 'Jakolinkki keskusteluun on luotu. Hallinnoi aiemmin jaettuja keskusteluja milloin vain Asetusten kautta.', + com_ui_share_update_message: + 'Nimesi, mukautetut ohjeet, ja mahdolliset viestit jotka lisäät jakamisen jälkeen jäävät yksityisiksi.', + com_ui_share_updated_message: + 'Keskustelun jakolinkki on päivitetty. Hallinnoi aiemmin jaettuja keskusteluja milloin vain Asetusten kautta.', + com_ui_shared_link_not_found: 'Jakolinkki ei löytynyt', + com_ui_delete_conversation: 'Poista keskustelu?', + com_ui_delete_confirm: 'Tämä suorittaa poiston', + com_ui_delete_confirm_prompt_version_var: + 'Tämä poistaa valitun version "{0}":lta. Jos muita versioita ei ole, syöte poistetaan samalla.', + com_ui_delete_assistant_confirm: + 'Haluatko varmasti poistaa tämän Avustajan? Poistoa ei voi perua.', + com_ui_rename: 'Nimeä uudestaan', + com_ui_archive: 'Arkisto', + com_ui_archive_error: 'Keskustelun arkistointi epäonnistui', + com_ui_unarchive: 'Palauta arkistosta', + com_ui_unarchive_error: 'Palautus arkistosta epäonnistui', + com_ui_more_options: 'Lisää', + com_ui_preview: 'Esikatsele', + com_ui_upload: 'Lataa', + com_ui_connect: 'Yhdistä', + com_ui_locked: 'Lukittu', + com_ui_upload_delay: + '"{0}" lataaminen kestää odotettua pidempään. Ole hyvä ja odota kunnes tiedosto saadaan indeksoitua tiedonhakua varten.', + com_ui_privacy_policy: 'Tietosuojailmoitus', + com_ui_terms_of_service: 'Käyttöehdot', + com_ui_use_micrphone: 'Käytä mikrofonia', + com_ui_min_tags: 'Enempää arvoja ei voida poistaa. Niiden minimimäärä on {0}.', + com_ui_max_tags: 'Maksimimäärä on {0}. käytetään viimeisimpiä arvoja.', + com_auth_error_login: + 'Kirjautuminen annetuilla tiedoilla ei onnistunut. Tarkista kirjautumistiedot, ja yritä uudestaan.', + com_auth_error_login_rl: + 'Liian monta kirjautumisyritystä lyhyen ajan sisällä. Yritä myöhemmin uudestaan.', + com_auth_error_login_ban: 'Tilisi on väliaikaisesti suljettu palvelun sääntöjen rikkomisesta.', + com_auth_error_login_server: 'Tapahtui sisäinen palvelinvirhe. Odota hetki, ja yritä uudestaan.', + com_auth_error_login_unverified: + 'Tiliäsi ei ole vahvistettu. Vahvistuslinkin pitäisi löytyä sähköposteistasi.', + com_auth_no_account: 'Ei tunnusta?', + com_auth_sign_up: 'Rekisteröidy', + com_auth_sign_in: 'Kirjaudu', + com_auth_google_login: 'Jatka Googlella', + com_auth_facebook_login: 'Jatka Facebookilla', + com_auth_github_login: 'Jatka Githubilla', + com_auth_discord_login: 'Jatka Discordilla', + com_auth_email: 'Sähköposti', + com_auth_email_required: 'Sähköposti on pakollinen', + com_auth_email_min_length: 'Sähköpostiosoitteen on oltava vähintään 6 merkkiä pitkä', + com_auth_email_max_length: 'Sähköpostiosoitteen ei pitäisi olla 120 merkkiä pidempi', + com_auth_email_pattern: 'Sähköpostiosoite on syötettävä oikeassa muodossa', + com_auth_email_address: 'Sähköpostiosoite Email address', + com_auth_password: 'Salasana', + com_auth_password_required: 'Salasana on pakollinen', + com_auth_password_min_length: 'Salasanan on oltava vähintään 8 merkkiä pitkä', + com_auth_password_max_length: 'Salasana voi olla enintään 128 merkkiä', + com_auth_password_forgot: 'Salasana unohtunut?', + com_auth_password_confirm: 'Vahvista salasana', + com_auth_password_not_match: 'Salasanat eivät täsmää', + com_auth_continue: 'Jatka', + com_auth_create_account: 'Luo tili', + com_auth_error_create: 'Tilin rekisteröinnissä tapahtui virhe. Yritä uudestaan.', + com_auth_full_name: 'Koko nimi', + com_auth_name_required: 'Nimi on pakollinen', + com_auth_name_min_length: 'Nimessä on oltava vähintään 3 merkkiä', + com_auth_name_max_length: 'Nimi voi olla enintään 80 merkkiä pitkä', + com_auth_username: 'Käyttäjänimi (valinnainen)', + com_auth_username_required: 'Käyttäjänimi on pakollinen', + com_auth_username_min_length: 'Käyttäjänimessä on oltava vähintään 2 merkkiä', + com_auth_username_max_length: 'Käyttäjänimi voi olla enintään 20 merkkiä pitkä', + com_auth_already_have_account: 'Käyttäjätilisi on jo luotu?', + com_auth_login: 'Kirjaudu', + com_auth_registration_success_insecure: 'Rekisteröityminen onnistui.', + com_auth_registration_success_generic: + 'Tarkista sähköpostisi sähköpostiosoitteen vahvistamiseksi.', + com_auth_reset_password: 'Aseta uusi salasana', + com_auth_click: 'Napauta', + com_auth_here: 'TÄTÄ', + com_auth_to_reset_your_password: 'asettaaksesi uuden salasanan.', + com_auth_reset_password_link_sent: 'Sähköposti lähetetty', + com_auth_reset_password_if_email_exists: + 'Jos kyseiselle sähköpostiosoitteelle löytyy käyttäjätili, siihen lähetetään sähköposti joka sisältää ohjeet salasanan uusimiseen. Tarkistathan myös roskapostikansion.', + com_auth_reset_password_email_sent: + 'Jos käyttäjä on rekisteröitynyt, salasanan vaihto-ohjeet lähetetään hänelle sähköpostitse.', + com_auth_reset_password_success: 'Salasanan asettaminen onnistui', + com_auth_login_with_new_password: 'Voit nyt kirjautua uudella salasanallasi.', + com_auth_error_invalid_reset_token: 'Tämä salasanan uusimistunniste ei ole enää voimassa.', + com_auth_click_here: 'Napauta tästä', + com_auth_to_try_again: 'kokeillaksesi uudestaan.', + com_auth_submit_registration: 'Lähetä rekisteröityminen', + com_auth_welcome_back: 'Tervetuloa takaisin', + com_auth_back_to_login: 'Palaa kirjautumiseen', + com_auth_email_verification_failed: 'Sähköpostin varmentaminen epäonnistui', + com_auth_email_verification_rate_limited: 'Liian monta yritystä. Kokeile myöhemmin uudestaan', + com_auth_email_verification_success: 'Sähköposti varmennettu', + com_auth_email_resent_success: 'Varmennussähköpostin uudelleenlähetys onnistui', + com_auth_email_resent_failed: 'Varmennussähköpostin uudelleenlähetys epäonnistui', + com_auth_email_verification_failed_token_missing: + 'Varmennus epäonnistui tunnisteen puuttumisen vuoksi', + com_auth_email_verification_invalid: 'Sähköpostin varmentaminen ei voimassa', + com_auth_email_verification_in_progress: 'Varmennetaan sähköpostia. Ole hyvä ja odota.', + com_auth_email_verification_resend_prompt: 'Sähköposti ei saapunut perille?', + com_auth_email_resend_link: 'Lähetä sähköposti uudestaan', + com_auth_email_verification_redirecting: 'Uudelleenohjataan {0} sekunnissa...', + com_endpoint_open_menu: 'Avaa valikko', + com_endpoint_bing_enable_sydney: 'Ota Sydney käyttöön', + com_endpoint_bing_to_enable_sydney: 'Ottaaksesi Sydneyn käyttöön', + com_endpoint_bing_jailbreak: 'Jailbreak', + com_endpoint_bing_context_placeholder: + 'Bing voi käyttää jopa 7000 tokenia keskustelussa viittausympäristönä käytettyä \'kontekstia\' varten. Tarkka raja ei ole tiedossa, mutta yli 7000 tokenia käyttäessä voi esiintyä virheitä.', + com_endpoint_bing_system_message_placeholder: + 'VAROITUS: Tämän ominaisuuden väärinkäyttö saattaa johtaa Bingin KÄYTTÖKIELTOON! Napauta \'Järjestelmäviestiä\', niin saat täydet ohjeet ja oletusviestin (jos jätetty pois), mikä on turvalliseksi katsottu \'Sydney\'-esiasetus.', + com_endpoint_system_message: 'Järjestelmäviesti', + com_endpoint_message: 'Vastaanottajana', + com_endpoint_messages: 'Viestit', + com_endpoint_message_not_appendable: 'Muokkaa viestiäsi tai Luo uudestaan.', + com_endpoint_default_blank: 'oletus: tyhjä', + com_endpoint_default_false: 'oletus: false', + com_endpoint_default_creative: 'oletus: creative', + com_endpoint_default_empty: 'oletus: tyhjä', + com_endpoint_default_with_num: 'oletus: {0}', + com_endpoint_context: 'Konteksti', + com_endpoint_tone_style: 'Tyylisävy', + com_endpoint_token_count: 'Token-määrä', + com_endpoint_output: 'Tulos', + com_endpoint_context_tokens: 'Konteksti-tokenien maksimimäärä', + com_endpoint_context_info: 'Kontekstia varten käytettävien tokeneiden maksimimäärä. Käytä tätä pyyntökohtaisten token-määrien hallinnointiin. Jos tätä ei määritetä, käytössä ovat järjestelmän oletusarvot perustuen tiedossa olevien mallien konteksti-ikkunoiden kokoon. Korkeamman arvon asettaminen voi aiheuttaa virheitä tai korkeamman token-hinnan.', + com_endpoint_google_temp: + 'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', + com_endpoint_google_topp: + 'Top-P vaikuttaa siihen kuinka malli valitsee tokeneita tulokseen. Tokenit valitaan top-k:sta (ks. Top-k -parametri) todennäköisimmistä vähiten todennäköseen, kunnes niiden todennäköisyyksien summa ylittää Top-P -arvon.', + com_endpoint_google_topk: + 'Top-k vaikuttaa siihen, miten malli valitsee tokeineita tulokseen. Jos Top-k on 1, valitaan se token, joka on kaikkien todennäköisen mallin sanastossa (tunnetaan myös nimellä ahne dekoodaus), kun taas top-k 3 tarkoittaisi, että seuraavat token valitaan 3 todennäköisimmän tokenin joukosta, lämpötilaa hyödyntäen.', + com_endpoint_google_maxoutputtokens: + 'Maksimimäärä tokeneillre, joita generoidaan tulokseen. Valitse pienempi arvo saadaksesi lyhyempiä vastauksia, ja suurempi arvo pitkiä vastauksia varten.', + com_endpoint_google_custom_name_placeholder: 'Aseta Googlelle mukautettu nimi', + com_endpoint_prompt_prefix_placeholder: + 'Aseta mukautetut ohjeet tai konteksti. Jätetään huomiotta, jos tyhjä.', + com_endpoint_instructions_assistants_placeholder: + 'Yliajaa Avustajan ohjeet. Tätä voi hyödyntää käytöksen muuttamiseen keskustelukohtaisesti.', + com_endpoint_prompt_prefix_assistants_placeholder: + 'Anna lisäohjeita tai kontekstia Avustajan pääohjeiden lisäksi. Set additional instructions or context on top of the Assistant\'s main instructions. Jätetään huomiotta, jos tyhjä.', + com_endpoint_custom_name: 'Mukautettu nimi', + com_endpoint_prompt_prefix: 'Mukautetut ohjeet', + com_endpoint_prompt_prefix_assistants: 'Lisäohjeet', + com_endpoint_instructions_assistants: 'Yliaja ohjeet', + com_endpoint_temperature: 'Lämpötila', + com_endpoint_default: 'oletus', + com_endpoint_top_p: 'Top P', + com_endpoint_top_k: 'Top k', + com_endpoint_max_output_tokens: 'Tulos-tokeneiden maksimimäärä', + com_endpoint_stop: 'Pysäytyssekvenssit', + com_endpoint_stop_placeholder: 'Erota arvot toisistaan rivinvaihdoilla', + com_endpoint_openai_max_tokens: `Valinnainen \`max_tokens\` -kenttä, joka kuvaa keskustelun vastauksessa generoitujen tokeneiden maksimimäärää. Syötteen ja vastauksen kokonaispituutta rajoittaa mallin konteksti-ikkuna. Konteksti -ikkunan koon ylittämisestä voi seurata virheitä.`, - com_endpoint_openai_temp: - 'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', - com_endpoint_openai_max: - 'Luotavien tokeneiden maksimimäärä. Mallin konteksti-ikkuna rajoittaa syötteiden ja vastausten kokonaispituutta.', - com_endpoint_openai_topp: - 'Vaihtoehto lämpötilapohjaiselle otannalle, ydinotanta, valitsee tokeneita Top P -todennäköisyysmassasta. Esimerkiksi arvo 0.1 tarkoittaa että vain top 10% tokeneista todennäköisyysmassassa huomioidaan. Suosittelemme, että muokkaat tätä tai lämpötilaa, mutta ei molempia.', - com_endpoint_openai_freq: - 'Lukuarvo väliltä -2.0 - 2.0. Positiiviset arvot rankaisevat uusia tokeneita perustuen niiden esiintymistiheyteen siihen mennessä luodussa tekstissä, mikä vähentää todennäköisyyttä, että malli toistaa saman rivin täsmälleen samanlaisena.', - com_endpoint_openai_pres: - 'Lukuarvo väliltä -2.0 - 2.0. Positiiviset arvot rankaisevat uusia tokeneita perustuen niiden esiintymiseen siihen mennessä luodussa tekstissä, ja lisäävät todennäköisyyttä että malli aloittaa uuden aiheen.', - com_endpoint_openai_resend: - 'Lähetä uudestaan kaikki aiemmin liitetyt kuvat. Huom: tämä voi lisätä token-kustannuksia huomattavasti, ja useiden kuvien käsittelystä kerralla voi seurata virheitä.', - com_endpoint_openai_resend_files: - 'Lähetä uudestaan kaikki aiemmin liitetyt tiedostot. Huom: tämä lisää token-kustannuksia, ja useiden tiedostojen käsittelystä kerralla voi seurata virheitä.', - com_endpoint_openai_detail: - 'Kuvatarkkuus Vision-pyynnöille. "Matala" on halvempi ja nopeampi, "Korkea" on yksityiskohtaisempi ja kalliimpi, ja "Auto" valitsee näiden välillä automaattisesti kuvan koon perusteella.', - com_endpoint_openai_stop: 'Enintään 4 sekvenssiä, joiden kohdalla API lopettaa tokenien luomisen.', - com_endpoint_openai_custom_name_placeholder: 'Anna tekoälylle mukautettu nimi', - com_endpoint_openai_prompt_prefix_placeholder: - 'Aseta mukautetut ohjeet Järjestelmäohjeisiin sisällytettäväksi. Oletus: tyhjä', - com_endpoint_anthropic_temp: - 'Vaihteluväli on 0 - 1. Käytä lähempänä nollaa olevaa lämpötilaa analyyttisiin tai monivalintatehtäviin, ja lähempänä yhtä luoviin ja generatiivisiin tehtäviin. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', - com_endpoint_anthropic_topp: - 'Top-P vaikuttaa siihen kuinka malli valitsee tokeneita tulokseen. Tokenit valitaan top-k:sta (ks. Top-k -parametri) todennäköisimmistä vähiten todennäköseen, kunnes niiden todennäköisyyksien summa ylittää Top-P -arvon.', - com_endpoint_anthropic_topk: - 'Top-k vaikuttaa siihen, miten malli valitsee tokeineita tulokseen. Jos Top-k on 1, valitaan se token, joka on kaikkien todennäköisen mallin sanastossa (tunnetaan myös nimellä ahne dekoodaus), kun taas top-k 3 tarkoittaisi, että seuraavat token valitaan 3 todennäköisimmän tokenin joukosta, lämpötilaa hyödyntäen.', - com_endpoint_anthropic_maxoutputtokens: - 'Vastauksen maksimi-tokenmäärä. valitse pienempi arvo, jos haluat lyhyempiä vastauksia, ja korkeampi arvo, jos haluat pidempiä vastauksia.', - com_endpoint_anthropic_custom_name_placeholder: 'Aseta mukautettu nimi Anthropicille', - com_endpoint_frequency_penalty: 'Esiintymistiheysrangaistus', - com_endpoint_presence_penalty: 'Esiintymisrangaistus', - com_endpoint_plug_use_functions: 'Käytä Toimintoja', - com_endpoint_plug_resend_files: 'Lähetä tiedostot uudestaan', - com_endpoint_plug_resend_images: 'Lähetä kuvat uudestaan', - com_endpoint_plug_image_detail: 'Kuvan yksityiskohdat', - com_endpoint_plug_skip_completion: 'Ohita Vastaus', - com_endpoint_disabled_with_tools: 'poissa käytöstä työkalujan kanssa', - com_endpoint_disabled_with_tools_placeholder: 'Poissa käytössä Työkalut valittuna', - com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: - 'Aseta mukautetut ohjeet Järjestelmäohjeisiin liitettäviksi. Oletus: tyhjä', - com_endpoint_import: 'Tuo', - com_endpoint_set_custom_name: 'Aseta mukautettu nimi, jotta esiasetus olisi helpompi löytää', - com_endpoint_preset_delete_confirm: 'Haluatko varmasti poistaa nämä esiasetukset?', - com_endpoint_preset_clear_all_confirm: 'Haluatko varmasti poistaa kaikki esiasetuksesi?', - com_endpoint_preset_import: 'Esiasetus tuotu!', - com_endpoint_preset_import_error: 'Esiasetuksen tuonnissa tapahtui virhe. Yritä uudestaan.', - com_endpoint_preset_save_error: 'Esiasetuksen tallennuksessa tapahtui virhe. Yritä uudestaan.', - com_endpoint_preset_delete_error: 'Esiasetuksen poistossa tapahtui virhe. Yritä uudestaan.', - com_endpoint_preset_default_removed: ' ei ole enää oletus-esiasetus.', - com_endpoint_preset_default_item: 'Oletus:', - com_endpoint_preset_default_none: 'Oletus-esiasetusta ei ole käytössä', - com_endpoint_preset_title: 'Esiasetus', - com_endpoint_preset_saved: 'Tallennettu!', - com_endpoint_preset_default: 'on nyt oletus-esiasetus.', - com_endpoint_preset: 'esiasetus', - com_endpoint_presets: 'esiasetukset', - com_endpoint_preset_selected: 'Esiasetus käytössä!', - com_endpoint_preset_selected_title: 'Käytössä!', - com_endpoint_preset_name: 'Esiasetuksen nimi', - com_endpoint_new_topic: 'Uusi aihe', - com_endpoint: 'Päätepiste', - com_endpoint_hide: 'Piilota', - com_endpoint_show: 'Näytä', - com_endpoint_examples: ' Esiasetukset', - com_endpoint_completion: 'Vastaus', - com_endpoint_agent: 'Agentti', - com_endpoint_show_what_settings: 'Näytä {0} asetusta', - com_endpoint_export: 'Vie', - com_endpoint_export_share: 'Vie/Jaa', - com_endpoint_assistant: 'Avustaja', - com_endpoint_use_active_assistant: 'Käytä aktiivista Avustajaa', - com_endpoint_assistant_model: 'Avustajan malli', - com_endpoint_save_as_preset: 'Tallenna esiasetukseksi', - com_endpoint_presets_clear_warning: - 'Haluatko varmasti tyhjentää kaikki esiasetukset? Tätä toimintoa ei voi perua.', - com_endpoint_not_implemented: 'Ei toteutettu', - com_endpoint_no_presets: 'Ei vielä esiasetuksia. Käytä Asetukset-painiketta luodaksesi esiasetuksen.', - com_endpoint_not_available: 'Päätepistettä ei ole tarjolla', - com_endpoint_view_options: 'Katseluvaihtoehdot', - com_endpoint_save_convo_as_preset: 'Tallenna keskustelu esiasetukseksi', - com_endpoint_my_preset: 'Esiasetukseni', - com_endpoint_agent_model: 'Agenttimalli (Suositus: GPT-3.5)', - com_endpoint_completion_model: 'Vastausmalli (Suositus: GPT-4)', - com_endpoint_func_hover: 'Salli lisäosien käyttö OpenAI-toimintoina', - com_endpoint_skip_hover: - 'Mahdollista vastausaskeleen ohitus, joka mahdollistaa lopuulisen vastauksen ja generoitujen askeleiden tarkastelun', - com_endpoint_config_key: 'Aseta API-avain', - com_endpoint_assistant_placeholder: 'Valitse Avustaja oikeanpuoleisesta sivupalkista', - com_endpoint_config_placeholder: 'Keskustellaksesi aseta avaimesi Ylätunnistevalikossa.', - com_endpoint_config_key_for: 'Aseta API-avain:', - com_endpoint_config_key_name: 'Avain', - com_endpoint_config_value: 'Aseta arvo:', - com_endpoint_config_key_name_placeholder: 'Aseta ensin API-avain', - com_endpoint_config_key_encryption: 'Avaimesi salataan ja poistetaan: ', - com_endpoint_config_key_never_expires: 'Avaimesi ei koskaan vanhene', - com_endpoint_config_key_expiry: 'vanhenemisaika', - com_endpoint_config_click_here: 'Napauta tästä', - com_endpoint_config_google_service_key: 'Google Service Account Key', - com_endpoint_config_google_cloud_platform: '(Google Cloud Platform:ista)', - com_endpoint_config_google_api_key: 'Google API Key', - com_endpoint_config_google_gemini_api: '(Gemini API)', - com_endpoint_config_google_api_info: 'Saadaksesi Generative Language API -avaimesi (Gemini:a varten),', - com_endpoint_config_key_import_json_key: 'Tuo palveluosoitteen JSON-avain.', - com_endpoint_config_key_import_json_key_success: 'Palveluosoitteetn JSON-avain tuotu onnistuneesti', - com_endpoint_config_key_import_json_key_invalid: - 'Virheellinen palveluosoitteen JSON-avain. Toitko oikean tiedoston?', - com_endpoint_config_key_get_edge_key: 'Saadaksisi pääsytunnuksesi Bingiä varten, kirjaudu', - com_endpoint_config_key_get_edge_key_dev_tool: - 'Käytä kehitystyökaluja ja lisäosaa sivustolle kirjautuneena _U -evästeen kopioimiseen. Jos tämä ei toimi, seuraa näitä', - com_endpoint_config_key_edge_instructions: 'ohjeita', - com_endpoint_config_key_edge_full_key_string: 'saadaksesi täydet evästemerkkijonot.', - com_endpoint_config_key_chatgpt: 'Saadaksesi pääsytunnuksesi ChatGPT:n \'ilmaisversiota\' varten, kirjaudu', - com_endpoint_config_key_chatgpt_then_visit: 'sitten vieraile', - com_endpoint_config_key_chatgpt_copy_token: 'Kopioi pääsytunnus.', - com_endpoint_config_key_google_need_to: 'Sinun täytyy', - com_endpoint_config_key_google_vertex_ai: 'sallia Vertex AI', - com_endpoint_config_key_google_vertex_api: 'API Google Cloud:issa, sitten', - com_endpoint_config_key_google_service_account: 'Luo Palvelutili (Service Account)', - com_endpoint_config_key_google_vertex_api_role: - 'Muista napauttaa \'Create and Continue\' jotta saat ainakin \'Vertex AI User\' -roolin. Lopuksi luo JSON-avain tänne tuotavaksi.', - com_nav_welcome_assistant: 'Ole hyvä ja valitse Avustaja', - com_nav_welcome_message: 'Miten voin auttaa tänään?', - com_nav_auto_scroll: 'Vieritä automaattisesti viimeisimpään viestiin keskustelua avatessa', - com_nav_hide_panel: 'Piilota oikeanpuoleinen sivupaneeli', - com_nav_modular_chat: 'Salli päätepisteen vaihto kesken keskustelun', - com_nav_latex_parsing: 'Tulkitse LaTeX:ia viesteissä (saattaa vaikuttaa suoritustehoon)', - com_nav_text_to_speech: 'Tekstistä puheeksi', - com_nav_automatic_playback: 'Toista viimeisin viesti automaattisesti', - com_nav_speech_to_text: 'Puheesta tekstiksi', - com_nav_profile_picture: 'Profiilikuva', - com_nav_change_picture: 'Vaihda kuva', - com_nav_plugin_store: 'Lisäosakauppa', - com_nav_plugin_install: 'Asenna', - com_nav_plugin_uninstall: 'Poista', - com_nav_tool_add: 'Lisää', - com_nav_tool_remove: 'Poista', - com_nav_tool_dialog: 'Avustajatyökalut', - com_ui_misc: 'Muu', - com_ui_roleplay: 'Roolipeli', - com_ui_write: 'Kirjoittaminen', - com_ui_idea: 'Ideat', - com_ui_shop: 'Ostokset', - com_ui_finance: 'Talous', - com_ui_code: 'Koodi', - com_ui_travel: 'Matkustus', - com_ui_teach_or_explain: 'Oppiminen', - com_ui_select_a_category: 'Kategoriaa ei valittu', - com_nav_tool_dialog_description: 'Avustaja täytyy tallentaa, jotta työkaluvalinta säilyisi.', - com_show_agent_settings: 'Näytä Agentin asetukset', - com_show_completion_settings: 'Näytä Vastausasetukset', - com_hide_examples: 'Piilota esimerkit', - com_show_examples: 'Näytä esimerkit', - com_nav_plugin_search: 'Hae lisäosaa', - com_nav_tool_search: 'Hakutyökalut', - com_nav_plugin_auth_error: - 'Tämän lisäosan varmentamisessa tapahtui virhe. Yritä uudestaan.', - com_nav_export_filename: 'Tiedoston nimi', - com_nav_export_filename_placeholder: 'Aseta tiedoston nimi', - com_nav_export_type: 'Tyyppi', - com_nav_export_include_endpoint_options: 'Sisällytä päätepistevaihtoehdot', - com_nav_enabled: 'Päällä', - com_nav_not_supported: 'Ei tuettu', - com_nav_export_all_message_branches: 'Vie kaikki sivupolut', - com_nav_export_recursive_or_sequential: 'Rekursiivisesti vai sarjassa?', - com_nav_export_recursive: 'Rekursiivisesti', - com_nav_export_conversation: 'Vie keskustelu', - com_nav_export: 'Vie', - com_nav_shared_links: 'Jaetut linkit', - com_nav_shared_links_manage: 'Hallinnoi', - com_nav_shared_links_empty: 'Sinulla ei ole jaettuja linkkejä.', - com_nav_shared_links_name: 'Nimi', - com_nav_shared_links_date_shared: 'Jakopäivä', - com_nav_source_chat: 'Katsele lähdekeskustelua', - com_nav_my_files: 'Omat tiedostot', - com_nav_theme: 'Teema', - com_nav_theme_system: 'Oletus', - com_nav_theme_dark: 'Tumma', - com_nav_theme_light: 'Vaalea', - com_nav_enter_to_send: 'Lähetä viestit Enter-painikkeella', - com_nav_user_name_display: 'Näytä käyttäjänimi viesteissä', - com_nav_save_drafts: 'Tallenna luonnokset paikallisesti', - com_nav_show_code: 'Kooditulkkia käyttäessä näytä aina koodi', - com_nav_auto_send_prompts: 'Lähetä syötteet automaattisesti', - com_nav_always_make_prod: 'Tee aina uudet versiot tuotantoon', - com_nav_clear_all_chats: 'Tyhjennä kaikki keskustelut', - com_nav_confirm_clear: 'Vahvista tyhjennys', - com_nav_close_sidebar: 'Sulje sivupalkki', - com_nav_open_sidebar: 'Avaa sivupalkki', - com_nav_send_message: 'Lähetä viesti', - com_nav_log_out: 'Kirjaudu ulos', - com_nav_user: 'KÄYTTÄJÄ', - com_nav_archived_chats: 'Arkistoidut keskustelut', - com_nav_archived_chats_manage: 'Hallinnoi', - com_nav_archived_chats_empty: 'Sinulla ei ole arkistoituja keskusteluita.', - com_nav_archive_all_chats: 'Arkistoi kaikki keskustelut', - com_nav_archive_all: 'Arkistoi kaikki', - com_nav_archive_name: 'Nimi', - com_nav_archive_created_at: 'Arkistointipäivä', - com_nav_clear_conversation: 'Tyhjennä keskustelut', - com_nav_clear_conversation_confirm_message: - 'Oletko varma että haluat tyhjentää kaikki keskustelut? Tätä toimintoa ei voi peruuttaa.', - com_nav_help_faq: 'Ohjeet & FAQ', - com_nav_settings: 'Asetukset', - com_nav_search_placeholder: 'Etsi keskusteluista', - com_nav_delete_account: 'Poista käyttäjätili', - com_nav_delete_account_confirm: 'Poista käyttäjätili - oletko varma?', - com_nav_delete_account_button: 'Poista käyttäjätilini pysyvästi', - com_nav_delete_account_email_placeholder: 'Syötä käyttäjätilisi sähköpostiosoite', - com_nav_delete_account_confirm_placeholder: 'Jatkaaksesi syötä "DELETE" alla olevaan syötekenttään', - com_nav_delete_warning: 'VAROITUS: Tämä poistaa käyttäjätilisi pysyvästi.', - com_nav_delete_data_info: 'Kaikki tietosi poistetaan.', - com_nav_conversation_mode: 'Keskustelumoodi', - com_nav_auto_send_text: 'Lähetä teksti automaattisesti (3 sekunnin kuluttua)', - com_nav_auto_transcribe_audio: 'Automaattinen äänen litterointi', - com_nav_db_sensitivity: 'Desibeliherkkyys', - com_nav_playback_rate: 'Äänen toiston nopeus', - com_nav_audio_play_error: 'Virhe ääntä toistaessa: {0}', - com_nav_audio_process_error: 'Virhe ääntä käsitellessä: {0}', - com_nav_long_audio_warning: 'Pidemmän tekstin käsittely kestää kauemmin.', - com_nav_engine: 'Puhemoottori', - com_nav_browser: 'Selain', - com_nav_external: 'Ulkoinen', - com_nav_delete_cache_storage: 'Tyhjennä TTS (tekstistä ääneksi) -välimuistivarasto', - com_nav_enable_cache_tts: 'TTS (tekstistä ääneksi) -välimuisti käyttöön', - com_nav_voice_select: 'Ääni', - com_nav_info_enter_to_send: - 'Jos tämä on päällä, Enter-näppäimen painaminen lähettää viestin. Kun asetus on pois päältä, Enter lisää rivinvaihdon, ja viestin lähettämiseksi on painettava CTRL + ENTER.', - com_nav_info_save_draft: - 'Jos tämä on päällä, teksti ja liitteet jotka syötät keskusteluun tallennetaan automaattisesti paikallisina luonnoksina. Nämä luonnokset ovat käytettävissä, vaikka välillä lataisit sivun uudestaan tai vaihtaisit toiseen keskusteluun. Luonnokset on tallettettu paikallisesti omalle laitteellesi, ja ne poistetaan, kun viesti on lähetetty.', - com_nav_info_fork_change_default: - '\'Vain näkyvät viestit\' sisältää vain suoran polun valittuun viestiin. \'Sisällytä sivupolut\' lisää polun varrella olevat sivupolut. \'Lisää kaikki tänne/täältä\' sisällyttää kaikki kytköksissä olevat viestit ja sivupolut.', - com_nav_info_fork_split_target_setting: - 'Jos tämä on päällä, haara syntyy kohdeviestistä keskustelun viimeiseen viestiin valitun haarautumistavan mukaisesti.', - com_nav_info_user_name_display: - 'Jos tämä on päällä, lähettäjän käyttäjänimi näytetään jokaisen lähettämäsi viestin päällä. Jos tämä ei ole käytössä, viestien päällä näytetään vain "Sinä".', - com_nav_info_latex_parsing: - 'Kun tämä on päällä, viesteissä oleva LaTeX-koodi näytetään yhtälöinä. Tämän asetuksen jättäminen pois päältä saattaa parantaa suorituskykyä, jos et tarvitse LaTeX-tulkkia.', - com_nav_info_revoke: - 'Tämä toiminto peruu ja poistaa kaikki antamasi API-avaimet. Ennen kuin voit jatkaa päätepisteiden käyttöä, sinun on syötettävä uudet tunnisteet.', - com_nav_info_delete_cache_storage: - 'Tämä toiminto poistaa kaikki laitteesi välimuistiin tallennetut TTS (tekstistä puheeksi) -äänitiedostot. Välimuistiin tallennettuja äänitiedostoja käytetään aiemmin luotujen TTS-tiedostojen toistamisen nopeuttamikseksi, mutta ne saattavat viedä levytilaa laitteellasi.', - com_nav_setting_general: 'Yleiset', - com_nav_setting_beta: 'Beta-toiminnot', - com_nav_setting_data: 'Datakontrollit', - com_nav_setting_account: 'Käyttäjätili', - com_nav_setting_speech: 'Puhe', - com_nav_language: 'Kieli', - com_nav_lang_auto: 'Tunnista automaattisesti', - }; - \ No newline at end of file + com_endpoint_openai_temp: + 'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', + com_endpoint_openai_max: + 'Luotavien tokeneiden maksimimäärä. Mallin konteksti-ikkuna rajoittaa syötteiden ja vastausten kokonaispituutta.', + com_endpoint_openai_topp: + 'Vaihtoehto lämpötilapohjaiselle otannalle, ydinotanta, valitsee tokeneita Top P -todennäköisyysmassasta. Esimerkiksi arvo 0.1 tarkoittaa että vain top 10% tokeneista todennäköisyysmassassa huomioidaan. Suosittelemme, että muokkaat tätä tai lämpötilaa, mutta ei molempia.', + com_endpoint_openai_freq: + 'Lukuarvo väliltä -2.0 - 2.0. Positiiviset arvot rankaisevat uusia tokeneita perustuen niiden esiintymistiheyteen siihen mennessä luodussa tekstissä, mikä vähentää todennäköisyyttä, että malli toistaa saman rivin täsmälleen samanlaisena.', + com_endpoint_openai_pres: + 'Lukuarvo väliltä -2.0 - 2.0. Positiiviset arvot rankaisevat uusia tokeneita perustuen niiden esiintymiseen siihen mennessä luodussa tekstissä, ja lisäävät todennäköisyyttä että malli aloittaa uuden aiheen.', + com_endpoint_openai_resend: + 'Lähetä uudestaan kaikki aiemmin liitetyt kuvat. Huom: tämä voi lisätä token-kustannuksia huomattavasti, ja useiden kuvien käsittelystä kerralla voi seurata virheitä.', + com_endpoint_openai_resend_files: + 'Lähetä uudestaan kaikki aiemmin liitetyt tiedostot. Huom: tämä lisää token-kustannuksia, ja useiden tiedostojen käsittelystä kerralla voi seurata virheitä.', + com_endpoint_openai_detail: + 'Kuvatarkkuus Vision-pyynnöille. "Matala" on halvempi ja nopeampi, "Korkea" on yksityiskohtaisempi ja kalliimpi, ja "Auto" valitsee näiden välillä automaattisesti kuvan koon perusteella.', + com_endpoint_openai_stop: + 'Enintään 4 sekvenssiä, joiden kohdalla API lopettaa tokenien luomisen.', + com_endpoint_openai_custom_name_placeholder: 'Anna tekoälylle mukautettu nimi', + com_endpoint_openai_prompt_prefix_placeholder: + 'Aseta mukautetut ohjeet Järjestelmäohjeisiin sisällytettäväksi. Oletus: tyhjä', + com_endpoint_anthropic_temp: + 'Vaihteluväli on 0 - 1. Käytä lähempänä nollaa olevaa lämpötilaa analyyttisiin tai monivalintatehtäviin, ja lähempänä yhtä luoviin ja generatiivisiin tehtäviin. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', + com_endpoint_anthropic_topp: + 'Top-P vaikuttaa siihen kuinka malli valitsee tokeneita tulokseen. Tokenit valitaan top-k:sta (ks. Top-k -parametri) todennäköisimmistä vähiten todennäköseen, kunnes niiden todennäköisyyksien summa ylittää Top-P -arvon.', + com_endpoint_anthropic_topk: + 'Top-k vaikuttaa siihen, miten malli valitsee tokeineita tulokseen. Jos Top-k on 1, valitaan se token, joka on kaikkien todennäköisen mallin sanastossa (tunnetaan myös nimellä ahne dekoodaus), kun taas top-k 3 tarkoittaisi, että seuraavat token valitaan 3 todennäköisimmän tokenin joukosta, lämpötilaa hyödyntäen.', + com_endpoint_anthropic_maxoutputtokens: + 'Vastauksen maksimi-tokenmäärä. valitse pienempi arvo, jos haluat lyhyempiä vastauksia, ja korkeampi arvo, jos haluat pidempiä vastauksia.', + com_endpoint_anthropic_custom_name_placeholder: 'Aseta mukautettu nimi Anthropicille', + com_endpoint_frequency_penalty: 'Esiintymistiheysrangaistus', + com_endpoint_presence_penalty: 'Esiintymisrangaistus', + com_endpoint_plug_use_functions: 'Käytä Toimintoja', + com_endpoint_plug_resend_files: 'Lähetä tiedostot uudestaan', + com_endpoint_plug_resend_images: 'Lähetä kuvat uudestaan', + com_endpoint_plug_image_detail: 'Kuvan yksityiskohdat', + com_endpoint_plug_skip_completion: 'Ohita Vastaus', + com_endpoint_disabled_with_tools: 'poissa käytöstä työkalujan kanssa', + com_endpoint_disabled_with_tools_placeholder: 'Poissa käytössä Työkalut valittuna', + com_endpoint_plug_set_custom_instructions_for_gpt_placeholder: + 'Aseta mukautetut ohjeet Järjestelmäohjeisiin liitettäviksi. Oletus: tyhjä', + com_endpoint_import: 'Tuo', + com_endpoint_set_custom_name: 'Aseta mukautettu nimi, jotta esiasetus olisi helpompi löytää', + com_endpoint_preset_delete_confirm: 'Haluatko varmasti poistaa nämä esiasetukset?', + com_endpoint_preset_clear_all_confirm: 'Haluatko varmasti poistaa kaikki esiasetuksesi?', + com_endpoint_preset_import: 'Esiasetus tuotu!', + com_endpoint_preset_import_error: 'Esiasetuksen tuonnissa tapahtui virhe. Yritä uudestaan.', + com_endpoint_preset_save_error: 'Esiasetuksen tallennuksessa tapahtui virhe. Yritä uudestaan.', + com_endpoint_preset_delete_error: 'Esiasetuksen poistossa tapahtui virhe. Yritä uudestaan.', + com_endpoint_preset_default_removed: ' ei ole enää oletus-esiasetus.', + com_endpoint_preset_default_item: 'Oletus:', + com_endpoint_preset_default_none: 'Oletus-esiasetusta ei ole käytössä', + com_endpoint_preset_title: 'Esiasetus', + com_endpoint_preset_saved: 'Tallennettu!', + com_endpoint_preset_default: 'on nyt oletus-esiasetus.', + com_endpoint_preset: 'esiasetus', + com_endpoint_presets: 'esiasetukset', + com_endpoint_preset_selected: 'Esiasetus käytössä!', + com_endpoint_preset_selected_title: 'Käytössä!', + com_endpoint_preset_name: 'Esiasetuksen nimi', + com_endpoint_new_topic: 'Uusi aihe', + com_endpoint: 'Päätepiste', + com_endpoint_hide: 'Piilota', + com_endpoint_show: 'Näytä', + com_endpoint_examples: ' Esiasetukset', + com_endpoint_completion: 'Vastaus', + com_endpoint_agent: 'Agentti', + com_endpoint_show_what_settings: 'Näytä {0} asetusta', + com_endpoint_export: 'Vie', + com_endpoint_export_share: 'Vie/Jaa', + com_endpoint_assistant: 'Avustaja', + com_endpoint_use_active_assistant: 'Käytä aktiivista Avustajaa', + com_endpoint_assistant_model: 'Avustajan malli', + com_endpoint_save_as_preset: 'Tallenna esiasetukseksi', + com_endpoint_presets_clear_warning: + 'Haluatko varmasti tyhjentää kaikki esiasetukset? Tätä toimintoa ei voi perua.', + com_endpoint_not_implemented: 'Ei toteutettu', + com_endpoint_no_presets: + 'Ei vielä esiasetuksia. Käytä Asetukset-painiketta luodaksesi esiasetuksen.', + com_endpoint_not_available: 'Päätepistettä ei ole tarjolla', + com_endpoint_view_options: 'Katseluvaihtoehdot', + com_endpoint_save_convo_as_preset: 'Tallenna keskustelu esiasetukseksi', + com_endpoint_my_preset: 'Esiasetukseni', + com_endpoint_agent_model: 'Agenttimalli (Suositus: GPT-3.5)', + com_endpoint_completion_model: 'Vastausmalli (Suositus: GPT-4)', + com_endpoint_func_hover: 'Salli lisäosien käyttö OpenAI-toimintoina', + com_endpoint_skip_hover: + 'Mahdollista vastausaskeleen ohitus, joka mahdollistaa lopuulisen vastauksen ja generoitujen askeleiden tarkastelun', + com_endpoint_config_key: 'Aseta API-avain', + com_endpoint_assistant_placeholder: 'Valitse Avustaja oikeanpuoleisesta sivupalkista', + com_endpoint_config_placeholder: 'Keskustellaksesi aseta avaimesi Ylätunnistevalikossa.', + com_endpoint_config_key_for: 'Aseta API-avain:', + com_endpoint_config_key_name: 'Avain', + com_endpoint_config_value: 'Aseta arvo:', + com_endpoint_config_key_name_placeholder: 'Aseta ensin API-avain', + com_endpoint_config_key_encryption: 'Avaimesi salataan ja poistetaan: ', + com_endpoint_config_key_never_expires: 'Avaimesi ei koskaan vanhene', + com_endpoint_config_key_expiry: 'vanhenemisaika', + com_endpoint_config_click_here: 'Napauta tästä', + com_endpoint_config_google_service_key: 'Google Service Account Key', + com_endpoint_config_google_cloud_platform: '(Google Cloud Platform:ista)', + com_endpoint_config_google_api_key: 'Google API Key', + com_endpoint_config_google_gemini_api: '(Gemini API)', + com_endpoint_config_google_api_info: + 'Saadaksesi Generative Language API -avaimesi (Gemini:a varten),', + com_endpoint_config_key_import_json_key: 'Tuo palveluosoitteen JSON-avain.', + com_endpoint_config_key_import_json_key_success: + 'Palveluosoitteetn JSON-avain tuotu onnistuneesti', + com_endpoint_config_key_import_json_key_invalid: + 'Virheellinen palveluosoitteen JSON-avain. Toitko oikean tiedoston?', + com_endpoint_config_key_get_edge_key: 'Saadaksisi pääsytunnuksesi Bingiä varten, kirjaudu', + com_endpoint_config_key_get_edge_key_dev_tool: + 'Käytä kehitystyökaluja ja lisäosaa sivustolle kirjautuneena _U -evästeen kopioimiseen. Jos tämä ei toimi, seuraa näitä', + com_endpoint_config_key_edge_instructions: 'ohjeita', + com_endpoint_config_key_edge_full_key_string: 'saadaksesi täydet evästemerkkijonot.', + com_endpoint_config_key_chatgpt: + 'Saadaksesi pääsytunnuksesi ChatGPT:n \'ilmaisversiota\' varten, kirjaudu', + com_endpoint_config_key_chatgpt_then_visit: 'sitten vieraile', + com_endpoint_config_key_chatgpt_copy_token: 'Kopioi pääsytunnus.', + com_endpoint_config_key_google_need_to: 'Sinun täytyy', + com_endpoint_config_key_google_vertex_ai: 'sallia Vertex AI', + com_endpoint_config_key_google_vertex_api: 'API Google Cloud:issa, sitten', + com_endpoint_config_key_google_service_account: 'Luo Palvelutili (Service Account)', + com_endpoint_config_key_google_vertex_api_role: + 'Muista napauttaa \'Create and Continue\' jotta saat ainakin \'Vertex AI User\' -roolin. Lopuksi luo JSON-avain tänne tuotavaksi.', + com_nav_welcome_assistant: 'Ole hyvä ja valitse Avustaja', + com_nav_welcome_message: 'Miten voin auttaa tänään?', + com_nav_auto_scroll: 'Vieritä automaattisesti viimeisimpään viestiin keskustelua avatessa', + com_nav_hide_panel: 'Piilota oikeanpuoleinen sivupaneeli', + com_nav_modular_chat: 'Salli päätepisteen vaihto kesken keskustelun', + com_nav_latex_parsing: 'Tulkitse LaTeX:ia viesteissä (saattaa vaikuttaa suoritustehoon)', + com_nav_text_to_speech: 'Tekstistä puheeksi', + com_nav_automatic_playback: 'Toista viimeisin viesti automaattisesti', + com_nav_speech_to_text: 'Puheesta tekstiksi', + com_nav_profile_picture: 'Profiilikuva', + com_nav_change_picture: 'Vaihda kuva', + com_nav_plugin_store: 'Lisäosakauppa', + com_nav_plugin_install: 'Asenna', + com_nav_plugin_uninstall: 'Poista', + com_nav_tool_add: 'Lisää', + com_nav_tool_remove: 'Poista', + com_nav_tool_dialog: 'Avustajatyökalut', + com_ui_misc: 'Muu', + com_ui_roleplay: 'Roolipeli', + com_ui_write: 'Kirjoittaminen', + com_ui_idea: 'Ideat', + com_ui_shop: 'Ostokset', + com_ui_finance: 'Talous', + com_ui_code: 'Koodi', + com_ui_travel: 'Matkustus', + com_ui_teach_or_explain: 'Oppiminen', + com_ui_select_a_category: 'Kategoriaa ei valittu', + com_nav_tool_dialog_description: 'Avustaja täytyy tallentaa, jotta työkaluvalinta säilyisi.', + com_show_agent_settings: 'Näytä Agentin asetukset', + com_show_completion_settings: 'Näytä Vastausasetukset', + com_hide_examples: 'Piilota esimerkit', + com_show_examples: 'Näytä esimerkit', + com_nav_plugin_search: 'Hae lisäosaa', + com_nav_tool_search: 'Hakutyökalut', + com_nav_plugin_auth_error: 'Tämän lisäosan varmentamisessa tapahtui virhe. Yritä uudestaan.', + com_nav_export_filename: 'Tiedoston nimi', + com_nav_export_filename_placeholder: 'Aseta tiedoston nimi', + com_nav_export_type: 'Tyyppi', + com_nav_export_include_endpoint_options: 'Sisällytä päätepistevaihtoehdot', + com_nav_enabled: 'Päällä', + com_nav_not_supported: 'Ei tuettu', + com_nav_export_all_message_branches: 'Vie kaikki sivupolut', + com_nav_export_recursive_or_sequential: 'Rekursiivisesti vai sarjassa?', + com_nav_export_recursive: 'Rekursiivisesti', + com_nav_export_conversation: 'Vie keskustelu', + com_nav_export: 'Vie', + com_nav_shared_links: 'Jaetut linkit', + com_nav_shared_links_manage: 'Hallinnoi', + com_nav_shared_links_empty: 'Sinulla ei ole jaettuja linkkejä.', + com_nav_shared_links_name: 'Nimi', + com_nav_shared_links_date_shared: 'Jakopäivä', + com_nav_source_chat: 'Katsele lähdekeskustelua', + com_nav_my_files: 'Omat tiedostot', + com_nav_theme: 'Teema', + com_nav_theme_system: 'Oletus', + com_nav_theme_dark: 'Tumma', + com_nav_theme_light: 'Vaalea', + com_nav_enter_to_send: 'Lähetä viestit Enter-painikkeella', + com_nav_user_name_display: 'Näytä käyttäjänimi viesteissä', + com_nav_save_drafts: 'Tallenna luonnokset paikallisesti', + com_nav_show_code: 'Kooditulkkia käyttäessä näytä aina koodi', + com_nav_auto_send_prompts: 'Lähetä syötteet automaattisesti', + com_nav_always_make_prod: 'Tee aina uudet versiot tuotantoon', + com_nav_clear_all_chats: 'Tyhjennä kaikki keskustelut', + com_nav_confirm_clear: 'Vahvista tyhjennys', + com_nav_close_sidebar: 'Sulje sivupalkki', + com_nav_open_sidebar: 'Avaa sivupalkki', + com_nav_send_message: 'Lähetä viesti', + com_nav_log_out: 'Kirjaudu ulos', + com_nav_user: 'KÄYTTÄJÄ', + com_nav_archived_chats: 'Arkistoidut keskustelut', + com_nav_archived_chats_manage: 'Hallinnoi', + com_nav_archived_chats_empty: 'Sinulla ei ole arkistoituja keskusteluita.', + com_nav_archive_all_chats: 'Arkistoi kaikki keskustelut', + com_nav_archive_all: 'Arkistoi kaikki', + com_nav_archive_name: 'Nimi', + com_nav_archive_created_at: 'Arkistointipäivä', + com_nav_clear_conversation: 'Tyhjennä keskustelut', + com_nav_clear_conversation_confirm_message: + 'Oletko varma että haluat tyhjentää kaikki keskustelut? Tätä toimintoa ei voi peruuttaa.', + com_nav_help_faq: 'Ohjeet & FAQ', + com_nav_settings: 'Asetukset', + com_nav_search_placeholder: 'Etsi keskusteluista', + com_nav_delete_account: 'Poista käyttäjätili', + com_nav_delete_account_confirm: 'Poista käyttäjätili - oletko varma?', + com_nav_delete_account_button: 'Poista käyttäjätilini pysyvästi', + com_nav_delete_account_email_placeholder: 'Syötä käyttäjätilisi sähköpostiosoite', + com_nav_delete_account_confirm_placeholder: + 'Jatkaaksesi syötä "DELETE" alla olevaan syötekenttään', + com_nav_delete_warning: 'VAROITUS: Tämä poistaa käyttäjätilisi pysyvästi.', + com_nav_delete_data_info: 'Kaikki tietosi poistetaan.', + com_nav_conversation_mode: 'Keskustelumoodi', + com_nav_auto_send_text: 'Lähetä teksti automaattisesti (3 sekunnin kuluttua)', + com_nav_auto_transcribe_audio: 'Automaattinen äänen litterointi', + com_nav_db_sensitivity: 'Desibeliherkkyys', + com_nav_playback_rate: 'Äänen toiston nopeus', + com_nav_audio_play_error: 'Virhe ääntä toistaessa: {0}', + com_nav_audio_process_error: 'Virhe ääntä käsitellessä: {0}', + com_nav_long_audio_warning: 'Pidemmän tekstin käsittely kestää kauemmin.', + com_nav_engine: 'Puhemoottori', + com_nav_browser: 'Selain', + com_nav_external: 'Ulkoinen', + com_nav_delete_cache_storage: 'Tyhjennä TTS (tekstistä ääneksi) -välimuistivarasto', + com_nav_enable_cache_tts: 'TTS (tekstistä ääneksi) -välimuisti käyttöön', + com_nav_voice_select: 'Ääni', + com_nav_info_enter_to_send: + 'Jos tämä on päällä, Enter-näppäimen painaminen lähettää viestin. Kun asetus on pois päältä, Enter lisää rivinvaihdon, ja viestin lähettämiseksi on painettava CTRL + ENTER.', + com_nav_info_save_draft: + 'Jos tämä on päällä, teksti ja liitteet jotka syötät keskusteluun tallennetaan automaattisesti paikallisina luonnoksina. Nämä luonnokset ovat käytettävissä, vaikka välillä lataisit sivun uudestaan tai vaihtaisit toiseen keskusteluun. Luonnokset on tallettettu paikallisesti omalle laitteellesi, ja ne poistetaan, kun viesti on lähetetty.', + com_nav_info_fork_change_default: + '\'Vain näkyvät viestit\' sisältää vain suoran polun valittuun viestiin. \'Sisällytä sivupolut\' lisää polun varrella olevat sivupolut. \'Lisää kaikki tänne/täältä\' sisällyttää kaikki kytköksissä olevat viestit ja sivupolut.', + com_nav_info_fork_split_target_setting: + 'Jos tämä on päällä, haara syntyy kohdeviestistä keskustelun viimeiseen viestiin valitun haarautumistavan mukaisesti.', + com_nav_info_user_name_display: + 'Jos tämä on päällä, lähettäjän käyttäjänimi näytetään jokaisen lähettämäsi viestin päällä. Jos tämä ei ole käytössä, viestien päällä näytetään vain "Sinä".', + com_nav_info_latex_parsing: + 'Kun tämä on päällä, viesteissä oleva LaTeX-koodi näytetään yhtälöinä. Tämän asetuksen jättäminen pois päältä saattaa parantaa suorituskykyä, jos et tarvitse LaTeX-tulkkia.', + com_nav_info_revoke: + 'Tämä toiminto peruu ja poistaa kaikki antamasi API-avaimet. Ennen kuin voit jatkaa päätepisteiden käyttöä, sinun on syötettävä uudet tunnisteet.', + com_nav_info_delete_cache_storage: + 'Tämä toiminto poistaa kaikki laitteesi välimuistiin tallennetut TTS (tekstistä puheeksi) -äänitiedostot. Välimuistiin tallennettuja äänitiedostoja käytetään aiemmin luotujen TTS-tiedostojen toistamisen nopeuttamikseksi, mutta ne saattavat viedä levytilaa laitteellasi.', + com_nav_setting_general: 'Yleiset', + com_nav_setting_beta: 'Beta-toiminnot', + com_nav_setting_data: 'Datakontrollit', + com_nav_setting_account: 'Käyttäjätili', + com_nav_setting_speech: 'Puhe', + com_nav_language: 'Kieli', + com_nav_lang_auto: 'Tunnista automaattisesti', +}; diff --git a/client/src/store/settings.ts b/client/src/store/settings.ts index 72826eafd9..7f00b88c80 100644 --- a/client/src/store/settings.ts +++ b/client/src/store/settings.ts @@ -45,7 +45,7 @@ const localStorageAtoms = { languageSTT: atomWithLocalStorage('languageSTT', ''), autoTranscribeAudio: atomWithLocalStorage('autoTranscribeAudio', false), decibelValue: atomWithLocalStorage('decibelValue', -45), - autoSendText: atomWithLocalStorage('autoSendText', false), + autoSendText: atomWithLocalStorage('autoSendText', -1), textToSpeech: atomWithLocalStorage('textToSpeech', true), engineTTS: atomWithLocalStorage('engineTTS', 'browser'), diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 220f5642f1..14cce495c3 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -304,7 +304,7 @@ const speechTab = z languageSTT: z.string().optional(), autoTranscribeAudio: z.boolean().optional(), decibelValue: z.number().optional(), - autoSendText: z.boolean().optional(), + autoSendText: z.number().optional(), }), ) .optional(), From 237a0de8b686f2703b8827641d55e152f86de949 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:08:13 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=94=84=20feat:=20chat=20direction=20(?= =?UTF-8?q?LTR-RTL)=20(#3260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: chat direction * fix: FileRow * feat: smooth trigger transition --- .../components/Chat/Input/AudioRecorder.tsx | 10 ++- client/src/components/Chat/Input/ChatForm.tsx | 15 ++++- .../Chat/Input/Files/AttachFile.tsx | 12 +++- .../components/Chat/Input/Files/FileRow.tsx | 10 ++- .../src/components/Chat/Input/SendButton.tsx | 62 ++++++++++--------- .../src/components/Chat/Input/StopButton.tsx | 11 +++- client/src/components/Nav/Settings.tsx | 20 +++--- .../{Messages/Messages.tsx => Chat/Chat.tsx} | 10 ++- .../Nav/SettingsTabs/Chat/ChatDirection.tsx | 31 ++++++++++ .../{Messages => Chat}/EnterToSend.tsx | 0 .../{Messages => Chat}/ForkSettings.tsx | 0 .../{Messages => Chat}/SaveDraft.tsx | 0 .../{Messages => Chat}/ShowCodeSwitch.tsx | 0 .../src/components/Nav/SettingsTabs/index.ts | 2 +- client/src/components/ui/TextareaAutosize.tsx | 6 +- client/src/hooks/Input/useTextarea.ts | 2 +- client/src/localization/languages/Ar.ts | 5 -- client/src/localization/languages/De.ts | 5 -- client/src/localization/languages/Eng.ts | 3 +- client/src/localization/languages/Es.ts | 5 -- client/src/localization/languages/Fr.ts | 5 -- client/src/localization/languages/It.ts | 5 -- client/src/localization/languages/Jp.ts | 5 -- client/src/localization/languages/Ko.ts | 5 -- client/src/localization/languages/Ru.ts | 5 -- client/src/localization/languages/Zh.ts | 5 -- .../localization/languages/ZhTraditional.ts | 5 -- .../localization/prompts/instructions/It.md | 4 -- client/src/store/settings.ts | 1 + client/src/style.css | 3 +- packages/data-provider/src/config.ts | 4 +- 31 files changed, 145 insertions(+), 111 deletions(-) rename client/src/components/Nav/SettingsTabs/{Messages/Messages.tsx => Chat/Chat.tsx} (73%) create mode 100644 client/src/components/Nav/SettingsTabs/Chat/ChatDirection.tsx rename client/src/components/Nav/SettingsTabs/{Messages => Chat}/EnterToSend.tsx (100%) rename client/src/components/Nav/SettingsTabs/{Messages => Chat}/ForkSettings.tsx (100%) rename client/src/components/Nav/SettingsTabs/{Messages => Chat}/SaveDraft.tsx (100%) rename client/src/components/Nav/SettingsTabs/{Messages => Chat}/ShowCodeSwitch.tsx (100%) diff --git a/client/src/components/Chat/Input/AudioRecorder.tsx b/client/src/components/Chat/Input/AudioRecorder.tsx index dd088ea3c8..1fbb3cc61b 100644 --- a/client/src/components/Chat/Input/AudioRecorder.tsx +++ b/client/src/components/Chat/Input/AudioRecorder.tsx @@ -4,16 +4,19 @@ import { ListeningIcon, Spinner } from '~/components/svg'; import { useLocalize, useSpeechToText } from '~/hooks'; import { useChatFormContext } from '~/Providers'; import { globalAudioId } from '~/common'; +import { cn } from '~/utils'; export default function AudioRecorder({ textAreaRef, methods, ask, + isRTL, disabled, }: { textAreaRef: React.RefObject; methods: ReturnType; ask: (data: { text: string }) => void; + isRTL: boolean; disabled: boolean; }) { const localize = useLocalize(); @@ -77,7 +80,12 @@ export default function AudioRecorder({ - - - {localize('com_nav_send_message')} - - - - ); - }), + forwardRef( + (props: { disabled: boolean; isRTL: boolean }, ref: React.ForwardedRef) => { + const localize = useLocalize(); + return ( + + + + + + + {localize('com_nav_send_message')} + + + + ); + }, + ), ); const SendButton = React.memo( forwardRef((props: SendButtonProps, ref: React.ForwardedRef) => { const data = useWatch({ control: props.control }); - return ; + return ; }), ); diff --git a/client/src/components/Chat/Input/StopButton.tsx b/client/src/components/Chat/Input/StopButton.tsx index 125ca1ea25..28ac9bbff5 100644 --- a/client/src/components/Chat/Input/StopButton.tsx +++ b/client/src/components/Chat/Input/StopButton.tsx @@ -1,6 +1,13 @@ -export default function StopButton({ stop, setShowStopButton }) { +import { cn } from '~/utils'; + +export default function StopButton({ stop, setShowStopButton, isRTL }) { return ( -
+