feat(Google): Support all Text/Chat Models, Response streaming, PaLM -> Google 🤖 (#1316)

* feat: update PaLM icons

* feat: add additional google models

* POC: formatting inputs for Vertex AI streaming

* refactor: move endpoints services outside of /routes dir to /services/Endpoints

* refactor: shorten schemas import

* refactor: rename PALM to GOOGLE

* feat: make Google editable endpoint

* feat: reusable Ask and Edit controllers based off Anthropic

* chore: organize imports/logic

* fix(parseConvo): include examples in googleSchema

* fix: google only allows odd number of messages to be sent

* fix: pass proxy to AnthropicClient

* refactor: change `google` altName to `Google`

* refactor: update getModelMaxTokens and related functions to handle maxTokensMap with nested endpoint model key/values

* refactor: google Icon and response sender changes (Codey and Google logo instead of PaLM in all cases)

* feat: google support for maxTokensMap

* feat: google updated endpoints with Ask/Edit controllers, buildOptions, and initializeClient

* feat(GoogleClient): now builds prompt for text models and supports real streaming from Vertex AI through langchain

* chore(GoogleClient): remove comments, left before for reference in git history

* docs: update google instructions (WIP)

* docs(apis_and_tokens.md): add images to google instructions

* docs: remove typo apis_and_tokens.md

* Update apis_and_tokens.md

* feat(Google): use default settings map, fully support context for both text and chat models, fully support examples for chat models

* chore: update more PaLM references to Google

* chore: move playwright out of workflows to avoid failing tests
This commit is contained in:
Danny Avila 2023-12-10 14:54:13 -05:00 committed by GitHub
parent 8a1968b2f8
commit 583e978a82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 1613 additions and 784 deletions

View file

@ -105,10 +105,10 @@ DEBUG_OPENAI=false
# OPENROUTER_API_KEY= # OPENROUTER_API_KEY=
#============# #============#
# PaLM # # Google #
#============# #============#
PALM_KEY=user_provided GOOGLE_KEY=user_provided
# GOOGLE_REVERSE_PROXY= # GOOGLE_REVERSE_PROXY=
#============# #============#

View file

@ -34,7 +34,7 @@
- 🌎 Multilingual UI: - 🌎 Multilingual UI:
- English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro, Русский - English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro, Русский
- 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands - 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands
- 🤖 AI model selection: OpenAI API, Azure, BingAI, ChatGPT Browser, PaLM2, Anthropic (Claude), Plugins - 🤖 AI model selection: OpenAI API, Azure, BingAI, ChatGPT, Google Vertex AI, Anthropic (Claude), Plugins
- 💾 Create, Save, & Share Custom Presets - 💾 Create, Save, & Share Custom Presets
- 🔄 Edit, Resubmit, and Continue messages with conversation branching - 🔄 Edit, Resubmit, and Continue messages with conversation branching
- 📤 Export conversations as screenshots, markdown, text, json. - 📤 Export conversations as screenshots, markdown, text, json.

View file

@ -1,6 +1,6 @@
const Anthropic = require('@anthropic-ai/sdk'); const Anthropic = require('@anthropic-ai/sdk');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const { getResponseSender, EModelEndpoint } = require('~/server/routes/endpoints/schemas'); const { getResponseSender, EModelEndpoint } = require('~/server/services/Endpoints');
const { getModelMaxTokens } = require('~/utils'); const { getModelMaxTokens } = require('~/utils');
const BaseClient = require('./BaseClient'); const BaseClient = require('./BaseClient');
@ -46,7 +46,8 @@ class AnthropicClient extends BaseClient {
stop: modelOptions.stop, // no stop method for now stop: modelOptions.stop, // no stop method for now
}; };
this.maxContextTokens = getModelMaxTokens(this.modelOptions.model) ?? 100000; this.maxContextTokens =
getModelMaxTokens(this.modelOptions.model, EModelEndpoint.anthropic) ?? 100000;
this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1500; this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1500;
this.maxPromptTokens = this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens; this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;

View file

@ -445,6 +445,7 @@ class BaseClient {
amount: promptTokens, amount: promptTokens,
debug: this.options.debug, debug: this.options.debug,
model: this.modelOptions.model, model: this.modelOptions.model,
endpoint: this.options.endpoint,
}, },
}); });
} }

View file

@ -1,23 +1,43 @@
const BaseClient = require('./BaseClient');
const { google } = require('googleapis'); const { google } = require('googleapis');
const { Agent, ProxyAgent } = require('undici'); const { Agent, ProxyAgent } = require('undici');
const { GoogleVertexAI } = require('langchain/llms/googlevertexai');
const { ChatGoogleVertexAI } = require('langchain/chat_models/googlevertexai');
const { AIMessage, HumanMessage, SystemMessage } = require('langchain/schema');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const {
getResponseSender,
EModelEndpoint,
endpointSettings,
} = require('~/server/services/Endpoints');
const { getModelMaxTokens } = require('~/utils');
const { formatMessage } = require('./prompts');
const BaseClient = require('./BaseClient');
const loc = 'us-central1';
const publisher = 'google';
const endpointPrefix = `https://${loc}-aiplatform.googleapis.com`;
// const apiEndpoint = loc + '-aiplatform.googleapis.com';
const tokenizersCache = {}; const tokenizersCache = {};
const settings = endpointSettings[EModelEndpoint.google];
class GoogleClient extends BaseClient { class GoogleClient extends BaseClient {
constructor(credentials, options = {}) { constructor(credentials, options = {}) {
super('apiKey', options); super('apiKey', options);
this.credentials = credentials;
this.client_email = credentials.client_email; this.client_email = credentials.client_email;
this.project_id = credentials.project_id; this.project_id = credentials.project_id;
this.private_key = credentials.private_key; this.private_key = credentials.private_key;
this.sender = 'PaLM2'; this.access_token = null;
if (options.skipSetOptions) {
return;
}
this.setOptions(options); this.setOptions(options);
} }
/* Google/PaLM2 specific methods */ /* Google specific methods */
constructUrl() { constructUrl() {
return `https://us-central1-aiplatform.googleapis.com/v1/projects/${this.project_id}/locations/us-central1/publishers/google/models/${this.modelOptions.model}:predict`; return `${endpointPrefix}/v1/projects/${this.project_id}/locations/${loc}/publishers/${publisher}/models/${this.modelOptions.model}:serverStreamingPredict`;
} }
async getClient() { async getClient() {
@ -35,6 +55,24 @@ class GoogleClient extends BaseClient {
return jwtClient; return jwtClient;
} }
async getAccessToken() {
const scopes = ['https://www.googleapis.com/auth/cloud-platform'];
const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes);
return new Promise((resolve, reject) => {
jwtClient.authorize((err, tokens) => {
if (err) {
console.error('Error: jwtClient failed to authorize');
console.error(err.message);
reject(err);
} else {
console.log('Access Token:', tokens.access_token);
resolve(tokens.access_token);
}
});
});
}
/* Required Client methods */ /* Required Client methods */
setOptions(options) { setOptions(options) {
if (this.options && !this.options.replaceOptions) { if (this.options && !this.options.replaceOptions) {
@ -53,30 +91,33 @@ class GoogleClient extends BaseClient {
this.options = options; this.options = options;
} }
this.options.examples = this.options.examples.filter( this.options.examples = this.options.examples
(obj) => obj.input.content !== '' && obj.output.content !== '', .filter((ex) => ex)
); .filter((obj) => obj.input.content !== '' && obj.output.content !== '');
const modelOptions = this.options.modelOptions || {}; const modelOptions = this.options.modelOptions || {};
this.modelOptions = { this.modelOptions = {
...modelOptions, ...modelOptions,
// set some good defaults (check for undefined in some cases because they may be 0) // set some good defaults (check for undefined in some cases because they may be 0)
model: modelOptions.model || 'chat-bison', model: modelOptions.model || settings.model.default,
temperature: typeof modelOptions.temperature === 'undefined' ? 0.2 : modelOptions.temperature, // 0 - 1, 0.2 is recommended temperature:
topP: typeof modelOptions.topP === 'undefined' ? 0.95 : modelOptions.topP, // 0 - 1, default: 0.95 typeof modelOptions.temperature === 'undefined'
topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK, // 1-40, default: 40 ? settings.temperature.default
: modelOptions.temperature,
topP: typeof modelOptions.topP === 'undefined' ? settings.topP.default : modelOptions.topP,
topK: typeof modelOptions.topK === 'undefined' ? settings.topK.default : modelOptions.topK,
// stop: modelOptions.stop // no stop method for now // stop: modelOptions.stop // no stop method for now
}; };
this.isChatModel = this.modelOptions.model.startsWith('chat-'); this.isChatModel = this.modelOptions.model.includes('chat');
const { isChatModel } = this; const { isChatModel } = this;
this.isTextModel = this.modelOptions.model.startsWith('text-'); this.isTextModel = !isChatModel && /code|text/.test(this.modelOptions.model);
const { isTextModel } = this; const { isTextModel } = this;
this.maxContextTokens = this.options.maxContextTokens || (isTextModel ? 8000 : 4096); this.maxContextTokens = getModelMaxTokens(this.modelOptions.model, EModelEndpoint.google);
// The max prompt tokens is determined by the max context tokens minus the max response tokens. // The max prompt tokens is determined by the max context tokens minus the max response tokens.
// Earlier messages will be dropped until the prompt is within the limit. // Earlier messages will be dropped until the prompt is within the limit.
this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1024; this.maxResponseTokens = this.modelOptions.maxOutputTokens || settings.maxOutputTokens.default;
this.maxPromptTokens = this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens; this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
@ -88,6 +129,14 @@ class GoogleClient extends BaseClient {
); );
} }
this.sender =
this.options.sender ??
getResponseSender({
model: this.modelOptions.model,
endpoint: EModelEndpoint.google,
modelLabel: this.options.modelLabel,
});
this.userLabel = this.options.userLabel || 'User'; this.userLabel = this.options.userLabel || 'User';
this.modelLabel = this.options.modelLabel || 'Assistant'; this.modelLabel = this.options.modelLabel || 'Assistant';
@ -99,8 +148,8 @@ class GoogleClient extends BaseClient {
this.endToken = ''; this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('cl100k_base'); this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
} else if (isTextModel) { } else if (isTextModel) {
this.startToken = '<|im_start|>'; this.startToken = '||>';
this.endToken = '<|im_end|>'; this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, { this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, {
'<|im_start|>': 100264, '<|im_start|>': 100264,
'<|im_end|>': 100265, '<|im_end|>': 100265,
@ -138,15 +187,18 @@ class GoogleClient extends BaseClient {
return this; return this;
} }
getMessageMapMethod() { formatMessages() {
return ((message) => ({ return ((message) => ({
author: message?.author ?? (message.isCreatedByUser ? this.userLabel : this.modelLabel), author: message?.author ?? (message.isCreatedByUser ? this.userLabel : this.modelLabel),
content: message?.content ?? message.text, content: message?.content ?? message.text,
})).bind(this); })).bind(this);
} }
buildMessages(messages = []) { buildMessages(messages = [], parentMessageId) {
const formattedMessages = messages.map(this.getMessageMapMethod()); if (this.isTextModel) {
return this.buildMessagesPrompt(messages, parentMessageId);
}
const formattedMessages = messages.map(this.formatMessages());
let payload = { let payload = {
instances: [ instances: [
{ {
@ -164,15 +216,6 @@ class GoogleClient extends BaseClient {
payload.instances[0].examples = this.options.examples; payload.instances[0].examples = this.options.examples;
} }
/* TO-DO: text model needs more context since it can't process an array of messages */
if (this.isTextModel) {
payload.instances = [
{
prompt: messages[messages.length - 1].content,
},
];
}
if (this.options.debug) { if (this.options.debug) {
console.debug('GoogleClient buildMessages'); console.debug('GoogleClient buildMessages');
console.dir(payload, { depth: null }); console.dir(payload, { depth: null });
@ -181,7 +224,157 @@ class GoogleClient extends BaseClient {
return { prompt: payload }; return { prompt: payload };
} }
async getCompletion(payload, abortController = null) { async buildMessagesPrompt(messages, parentMessageId) {
const orderedMessages = this.constructor.getMessagesForConversation({
messages,
parentMessageId,
});
if (this.options.debug) {
console.debug('GoogleClient: orderedMessages', orderedMessages, parentMessageId);
}
const formattedMessages = orderedMessages.map((message) => ({
author: message.isCreatedByUser ? this.userLabel : this.modelLabel,
content: message?.content ?? message.text,
}));
let lastAuthor = '';
let groupedMessages = [];
for (let message of formattedMessages) {
// If last author is not same as current author, add to new group
if (lastAuthor !== message.author) {
groupedMessages.push({
author: message.author,
content: [message.content],
});
lastAuthor = message.author;
// If same author, append content to the last group
} else {
groupedMessages[groupedMessages.length - 1].content.push(message.content);
}
}
let identityPrefix = '';
if (this.options.userLabel) {
identityPrefix = `\nHuman's name: ${this.options.userLabel}`;
}
if (this.options.modelLabel) {
identityPrefix = `${identityPrefix}\nYou are ${this.options.modelLabel}`;
}
let promptPrefix = (this.options.promptPrefix || '').trim();
if (promptPrefix) {
// If the prompt prefix doesn't end with the end token, add it.
if (!promptPrefix.endsWith(`${this.endToken}`)) {
promptPrefix = `${promptPrefix.trim()}${this.endToken}\n\n`;
}
promptPrefix = `\nContext:\n${promptPrefix}`;
}
if (identityPrefix) {
promptPrefix = `${identityPrefix}${promptPrefix}`;
}
// Prompt AI to respond, empty if last message was from AI
let isEdited = lastAuthor === this.modelLabel;
const promptSuffix = isEdited ? '' : `${promptPrefix}\n\n${this.modelLabel}:\n`;
let currentTokenCount = isEdited
? this.getTokenCount(promptPrefix)
: this.getTokenCount(promptSuffix);
let promptBody = '';
const maxTokenCount = this.maxPromptTokens;
const context = [];
// Iterate backwards through the messages, adding them to the prompt until we reach the max token count.
// Do this within a recursive async function so that it doesn't block the event loop for too long.
// Also, remove the next message when the message that puts us over the token limit is created by the user.
// Otherwise, remove only the exceeding message. This is due to Anthropic's strict payload rule to start with "Human:".
const nextMessage = {
remove: false,
tokenCount: 0,
messageString: '',
};
const buildPromptBody = async () => {
if (currentTokenCount < maxTokenCount && groupedMessages.length > 0) {
const message = groupedMessages.pop();
const isCreatedByUser = message.author === this.userLabel;
// Use promptPrefix if message is edited assistant'
const messagePrefix =
isCreatedByUser || !isEdited
? `\n\n${message.author}:`
: `${promptPrefix}\n\n${message.author}:`;
const messageString = `${messagePrefix}\n${message.content}${this.endToken}\n`;
let newPromptBody = `${messageString}${promptBody}`;
context.unshift(message);
const tokenCountForMessage = this.getTokenCount(messageString);
const newTokenCount = currentTokenCount + tokenCountForMessage;
if (!isCreatedByUser) {
nextMessage.messageString = messageString;
nextMessage.tokenCount = tokenCountForMessage;
}
if (newTokenCount > maxTokenCount) {
if (!promptBody) {
// This is the first message, so we can't add it. Just throw an error.
throw new Error(
`Prompt is too long. Max token count is ${maxTokenCount}, but prompt is ${newTokenCount} tokens long.`,
);
}
// Otherwise, ths message would put us over the token limit, so don't add it.
// if created by user, remove next message, otherwise remove only this message
if (isCreatedByUser) {
nextMessage.remove = true;
}
return false;
}
promptBody = newPromptBody;
currentTokenCount = newTokenCount;
// Switch off isEdited after using it for the first time
if (isEdited) {
isEdited = false;
}
// wait for next tick to avoid blocking the event loop
await new Promise((resolve) => setImmediate(resolve));
return buildPromptBody();
}
return true;
};
await buildPromptBody();
if (nextMessage.remove) {
promptBody = promptBody.replace(nextMessage.messageString, '');
currentTokenCount -= nextMessage.tokenCount;
context.shift();
}
let prompt = `${promptBody}${promptSuffix}`;
// Add 2 tokens for metadata after all messages have been counted.
currentTokenCount += 2;
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
this.modelOptions.maxOutputTokens = Math.min(
this.maxContextTokens - currentTokenCount,
this.maxResponseTokens,
);
return { prompt, context };
}
async _getCompletion(payload, abortController = null) {
if (!abortController) { if (!abortController) {
abortController = new AbortController(); abortController = new AbortController();
} }
@ -212,6 +405,72 @@ class GoogleClient extends BaseClient {
return res.data; return res.data;
} }
async getCompletion(_payload, options = {}) {
const { onProgress, abortController } = options;
const { parameters, instances } = _payload;
const { messages: _messages, context, examples: _examples } = instances?.[0] ?? {};
let examples;
let clientOptions = {
authOptions: {
credentials: {
...this.credentials,
},
projectId: this.project_id,
},
...parameters,
};
if (!parameters) {
clientOptions = { ...clientOptions, ...this.modelOptions };
}
if (_examples && _examples.length) {
examples = _examples
.map((ex) => {
const { input, output } = ex;
if (!input || !output) {
return undefined;
}
return {
input: new HumanMessage(input.content),
output: new AIMessage(output.content),
};
})
.filter((ex) => ex);
clientOptions.examples = examples;
}
const model = this.isTextModel
? new GoogleVertexAI(clientOptions)
: new ChatGoogleVertexAI(clientOptions);
let reply = '';
const messages = this.isTextModel
? _payload.trim()
: _messages
.map((msg) => ({ ...msg, role: msg.author === 'User' ? 'user' : 'assistant' }))
.map((message) => formatMessage({ message, langChain: true }));
if (context && messages?.length > 0) {
messages.unshift(new SystemMessage(context));
}
const stream = await model.stream(messages, {
signal: abortController.signal,
timeout: 7000,
});
for await (const chunk of stream) {
await this.generateTextStream(chunk?.content ?? chunk, onProgress, { delay: 7 });
reply += chunk?.content ?? chunk;
}
return reply;
}
getSaveOptions() { getSaveOptions() {
return { return {
promptPrefix: this.options.promptPrefix, promptPrefix: this.options.promptPrefix,
@ -225,34 +484,18 @@ class GoogleClient extends BaseClient {
} }
async sendCompletion(payload, opts = {}) { async sendCompletion(payload, opts = {}) {
console.log('GoogleClient: sendcompletion', payload, opts);
let reply = ''; let reply = '';
let blocked = false;
try { try {
const result = await this.getCompletion(payload, opts.abortController); reply = await this.getCompletion(payload, opts);
blocked = result?.predictions?.[0]?.safetyAttributes?.blocked;
reply =
result?.predictions?.[0]?.candidates?.[0]?.content ||
result?.predictions?.[0]?.content ||
'';
if (blocked === true) {
reply = `Google blocked a proper response to your message:\n${JSON.stringify(
result.predictions[0].safetyAttributes,
)}${reply.length > 0 ? `\nAI Response:\n${reply}` : ''}`;
}
if (this.options.debug) { if (this.options.debug) {
console.debug('result'); console.debug('result');
console.debug(result); console.debug(reply);
} }
} catch (err) { } catch (err) {
console.error('Error: failed to send completion to Google'); console.error('Error: failed to send completion to Google');
console.error(err);
console.error(err.message); console.error(err.message);
} }
if (!blocked) {
await this.generateTextStream(reply, opts.onProgress, { delay: 0.5 });
}
return reply.trim(); return reply.trim();
} }

View file

@ -1,10 +1,10 @@
const OpenAI = require('openai'); const OpenAI = require('openai');
const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpsProxyAgent } = require('https-proxy-agent');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const { getResponseSender, EModelEndpoint } = require('~/server/services/Endpoints');
const { encodeAndFormat, validateVisionModel } = require('~/server/services/Files/images'); const { encodeAndFormat, validateVisionModel } = require('~/server/services/Files/images');
const { getModelMaxTokens, genAzureChatCompletion, extractBaseURL } = require('~/utils'); const { getModelMaxTokens, genAzureChatCompletion, extractBaseURL } = require('~/utils');
const { truncateText, formatMessage, CUT_OFF_PROMPT } = require('./prompts'); const { truncateText, formatMessage, CUT_OFF_PROMPT } = require('./prompts');
const { getResponseSender, EModelEndpoint } = require('~/server/routes/endpoints/schemas');
const { handleOpenAIErrors } = require('./tools/util'); const { handleOpenAIErrors } = require('./tools/util');
const spendTokens = require('~/models/spendTokens'); const spendTokens = require('~/models/spendTokens');
const { createLLM, RunManager } = require('./llm'); const { createLLM, RunManager } = require('./llm');

View file

@ -3,11 +3,12 @@ const { CallbackManager } = require('langchain/callbacks');
const { BufferMemory, ChatMessageHistory } = require('langchain/memory'); const { BufferMemory, ChatMessageHistory } = require('langchain/memory');
const { initializeCustomAgent, initializeFunctionsAgent } = require('./agents'); const { initializeCustomAgent, initializeFunctionsAgent } = require('./agents');
const { addImages, buildErrorInput, buildPromptPrefix } = require('./output_parsers'); const { addImages, buildErrorInput, buildPromptPrefix } = require('./output_parsers');
const checkBalance = require('../../models/checkBalance'); const { EModelEndpoint } = require('~/server/services/Endpoints');
const { formatLangChainMessages } = require('./prompts'); const { formatLangChainMessages } = require('./prompts');
const { isEnabled } = require('../../server/utils'); const checkBalance = require('~/models/checkBalance');
const { extractBaseURL } = require('../../utils');
const { SelfReflectionTool } = require('./tools'); const { SelfReflectionTool } = require('./tools');
const { isEnabled } = require('~/server/utils');
const { extractBaseURL } = require('~/utils');
const { loadTools } = require('./tools/util'); const { loadTools } = require('./tools/util');
class PluginsClient extends OpenAIClient { class PluginsClient extends OpenAIClient {
@ -304,6 +305,7 @@ class PluginsClient extends OpenAIClient {
amount: promptTokens, amount: promptTokens,
debug: this.options.debug, debug: this.options.debug,
model: this.modelOptions.model, model: this.modelOptions.model,
endpoint: EModelEndpoint.openAI,
}, },
}); });
} }

View file

@ -1,7 +1,8 @@
const { promptTokensEstimate } = require('openai-chat-tokens'); const { promptTokensEstimate } = require('openai-chat-tokens');
const checkBalance = require('../../../models/checkBalance'); const { EModelEndpoint } = require('~/server/services/Endpoints');
const { isEnabled } = require('../../../server/utils'); const { formatFromLangChain } = require('~/app/clients/prompts');
const { formatFromLangChain } = require('../prompts'); const checkBalance = require('~/models/checkBalance');
const { isEnabled } = require('~/server/utils');
const createStartHandler = ({ const createStartHandler = ({
context, context,
@ -55,6 +56,7 @@ const createStartHandler = ({
debug: manager.debug, debug: manager.debug,
generations, generations,
model, model,
endpoint: EModelEndpoint.openAI,
}, },
}); });
} }

View file

@ -0,0 +1,42 @@
/**
* Formats an object to match the struct_val, list_val, string_val, float_val, and int_val format.
*
* @param {Object} obj - The object to be formatted.
* @returns {Object} The formatted object.
*
* Handles different types:
* - Arrays are wrapped in list_val and each element is processed.
* - Objects are recursively processed.
* - Strings are wrapped in string_val.
* - Numbers are wrapped in float_val or int_val depending on whether they are floating-point or integers.
*/
function formatGoogleInputs(obj) {
const formattedObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];
// Handle arrays
if (Array.isArray(value)) {
formattedObj[key] = { list_val: value.map((item) => formatGoogleInputs(item)) };
}
// Handle objects
else if (typeof value === 'object' && value !== null) {
formattedObj[key] = formatGoogleInputs(value);
}
// Handle numbers
else if (typeof value === 'number') {
formattedObj[key] = Number.isInteger(value) ? { int_val: value } : { float_val: value };
}
// Handle other types (e.g., strings)
else {
formattedObj[key] = { string_val: [value] };
}
}
}
return { struct_val: formattedObj };
}
module.exports = formatGoogleInputs;

View file

@ -0,0 +1,274 @@
const formatGoogleInputs = require('./formatGoogleInputs');
describe('formatGoogleInputs', () => {
it('formats message correctly', () => {
const input = {
messages: [
{
content: 'hi',
author: 'user',
},
],
context: 'context',
examples: [
{
input: {
author: 'user',
content: 'user input',
},
output: {
author: 'bot',
content: 'bot output',
},
},
],
parameters: {
temperature: 0.2,
topP: 0.8,
topK: 40,
maxOutputTokens: 1024,
},
};
const expectedOutput = {
struct_val: {
messages: {
list_val: [
{
struct_val: {
content: {
string_val: ['hi'],
},
author: {
string_val: ['user'],
},
},
},
],
},
context: {
string_val: ['context'],
},
examples: {
list_val: [
{
struct_val: {
input: {
struct_val: {
author: {
string_val: ['user'],
},
content: {
string_val: ['user input'],
},
},
},
output: {
struct_val: {
author: {
string_val: ['bot'],
},
content: {
string_val: ['bot output'],
},
},
},
},
},
],
},
parameters: {
struct_val: {
temperature: {
float_val: 0.2,
},
topP: {
float_val: 0.8,
},
topK: {
int_val: 40,
},
maxOutputTokens: {
int_val: 1024,
},
},
},
},
};
const result = formatGoogleInputs(input);
expect(JSON.stringify(result)).toEqual(JSON.stringify(expectedOutput));
});
it('formats real payload parts', () => {
const input = {
instances: [
{
context: 'context',
examples: [
{
input: {
author: 'user',
content: 'user input',
},
output: {
author: 'bot',
content: 'user output',
},
},
],
messages: [
{
author: 'user',
content: 'hi',
},
],
},
],
parameters: {
candidateCount: 1,
maxOutputTokens: 1024,
temperature: 0.2,
topP: 0.8,
topK: 40,
},
};
const expectedOutput = {
struct_val: {
instances: {
list_val: [
{
struct_val: {
context: { string_val: ['context'] },
examples: {
list_val: [
{
struct_val: {
input: {
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['user input'] },
},
},
output: {
struct_val: {
author: { string_val: ['bot'] },
content: { string_val: ['user output'] },
},
},
},
},
],
},
messages: {
list_val: [
{
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['hi'] },
},
},
],
},
},
},
],
},
parameters: {
struct_val: {
candidateCount: { int_val: 1 },
maxOutputTokens: { int_val: 1024 },
temperature: { float_val: 0.2 },
topP: { float_val: 0.8 },
topK: { int_val: 40 },
},
},
},
};
const result = formatGoogleInputs(input);
expect(JSON.stringify(result)).toEqual(JSON.stringify(expectedOutput));
});
it('helps create valid payload parts', () => {
const instances = {
context: 'context',
examples: [
{
input: {
author: 'user',
content: 'user input',
},
output: {
author: 'bot',
content: 'user output',
},
},
],
messages: [
{
author: 'user',
content: 'hi',
},
],
};
const expectedInstances = {
struct_val: {
context: { string_val: ['context'] },
examples: {
list_val: [
{
struct_val: {
input: {
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['user input'] },
},
},
output: {
struct_val: {
author: { string_val: ['bot'] },
content: { string_val: ['user output'] },
},
},
},
},
],
},
messages: {
list_val: [
{
struct_val: {
author: { string_val: ['user'] },
content: { string_val: ['hi'] },
},
},
],
},
},
};
const parameters = {
candidateCount: 1,
maxOutputTokens: 1024,
temperature: 0.2,
topP: 0.8,
topK: 40,
};
const expectedParameters = {
struct_val: {
candidateCount: { int_val: 1 },
maxOutputTokens: { int_val: 1024 },
temperature: { float_val: 0.2 },
topP: { float_val: 0.8 },
topK: { int_val: 40 },
},
};
const instancesResult = formatGoogleInputs(instances);
const parametersResult = formatGoogleInputs(parameters);
expect(JSON.stringify(instancesResult)).toEqual(JSON.stringify(expectedInstances));
expect(JSON.stringify(parametersResult)).toEqual(JSON.stringify(expectedParameters));
});
});

View file

@ -2,8 +2,16 @@ const mongoose = require('mongoose');
const balanceSchema = require('./schema/balance'); const balanceSchema = require('./schema/balance');
const { getMultiplier } = require('./tx'); const { getMultiplier } = require('./tx');
balanceSchema.statics.check = async function ({ user, model, valueKey, tokenType, amount, debug }) { balanceSchema.statics.check = async function ({
const multiplier = getMultiplier({ valueKey, tokenType, model }); user,
model,
endpoint,
valueKey,
tokenType,
amount,
debug,
}) {
const multiplier = getMultiplier({ valueKey, tokenType, model, endpoint });
const tokenCost = amount * multiplier; const tokenCost = amount * multiplier;
const { tokenCredits: balance } = (await this.findOne({ user }, 'tokenCredits').lean()) ?? {}; const { tokenCredits: balance } = (await this.findOne({ user }, 'tokenCredits').lean()) ?? {};
@ -11,6 +19,7 @@ balanceSchema.statics.check = async function ({ user, model, valueKey, tokenType
console.log('balance check', { console.log('balance check', {
user, user,
model, model,
endpoint,
valueKey, valueKey,
tokenType, tokenType,
amount, amount,

View file

@ -18,10 +18,11 @@ const tokenValues = {
* Retrieves the key associated with a given model name. * Retrieves the key associated with a given model name.
* *
* @param {string} model - The model name to match. * @param {string} model - The model name to match.
* @param {string} endpoint - The endpoint name to match.
* @returns {string|undefined} The key corresponding to the model name, or undefined if no match is found. * @returns {string|undefined} The key corresponding to the model name, or undefined if no match is found.
*/ */
const getValueKey = (model) => { const getValueKey = (model, endpoint) => {
const modelName = matchModelName(model); const modelName = matchModelName(model, endpoint);
if (!modelName) { if (!modelName) {
return undefined; return undefined;
} }
@ -51,9 +52,10 @@ const getValueKey = (model) => {
* @param {string} [params.valueKey] - The key corresponding to the model name. * @param {string} [params.valueKey] - The key corresponding to the model name.
* @param {string} [params.tokenType] - The type of token (e.g., 'prompt' or 'completion'). * @param {string} [params.tokenType] - The type of token (e.g., 'prompt' or 'completion').
* @param {string} [params.model] - The model name to derive the value key from if not provided. * @param {string} [params.model] - The model name to derive the value key from if not provided.
* @param {string} [params.endpoint] - The endpoint name to derive the value key from if not provided.
* @returns {number} The multiplier for the given parameters, or a default value if not found. * @returns {number} The multiplier for the given parameters, or a default value if not found.
*/ */
const getMultiplier = ({ valueKey, tokenType, model }) => { const getMultiplier = ({ valueKey, tokenType, model, endpoint }) => {
if (valueKey && tokenType) { if (valueKey && tokenType) {
return tokenValues[valueKey][tokenType] ?? defaultRate; return tokenValues[valueKey][tokenType] ?? defaultRate;
} }
@ -62,7 +64,7 @@ const getMultiplier = ({ valueKey, tokenType, model }) => {
return 1; return 1;
} }
valueKey = getValueKey(model); valueKey = getValueKey(model, endpoint);
if (!valueKey) { if (!valueKey) {
return defaultRate; return defaultRate;
} }

View file

@ -0,0 +1,132 @@
const { sendMessage, createOnProgress } = require('~/server/utils');
const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { getResponseSender } = require('~/server/services/Endpoints');
const { createAbortController, handleAbortError } = require('~/server/middleware');
const AskController = async (req, res, next, initializeClient) => {
let {
text,
endpointOption,
conversationId,
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
console.log('ask log');
console.dir({ text, conversationId, endpointOption }, { depth: null });
let metadata;
let userMessage;
let promptTokens;
let userMessageId;
let responseMessageId;
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const user = req.user.id;
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
userMessageId = data[key].messageId;
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
} else if (key === 'promptTokens') {
promptTokens = data[key];
} else if (!conversationId && key === 'conversationId') {
conversationId = data[key];
}
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender,
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
error: false,
user,
});
}
if (saveDelay < 500) {
saveDelay = 500;
}
},
});
try {
const addMetadata = (data) => (metadata = data);
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
// debug: true,
user,
conversationId,
parentMessageId,
overrideParentMessageId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId ?? userMessageId,
}),
onStart,
getReqData,
addMetadata,
abortController,
});
if (metadata) {
response = { ...response, ...metadata };
}
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
await saveMessage({ ...response, user });
await saveMessage(userMessage);
// TODO: add title service
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {
partialText,
conversationId,
sender,
messageId: responseMessageId,
parentMessageId: userMessageId ?? parentMessageId,
});
}
};
module.exports = AskController;

View file

@ -0,0 +1,135 @@
const { sendMessage, createOnProgress } = require('~/server/utils');
const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { getResponseSender } = require('~/server/services/Endpoints');
const { createAbortController, handleAbortError } = require('~/server/middleware');
const EditController = async (req, res, next, initializeClient) => {
let {
text,
generation,
endpointOption,
conversationId,
responseMessageId,
isContinued = false,
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
console.log('edit log');
console.dir({ text, generation, isContinued, conversationId, endpointOption }, { depth: null });
let metadata;
let userMessage;
let promptTokens;
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const userMessageId = parentMessageId;
const user = req.user.id;
const addMetadata = (data) => (metadata = data);
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
} else if (key === 'promptTokens') {
promptTokens = data[key];
}
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
generation,
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender,
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
isEdited: true,
error: false,
user,
});
}
if (saveDelay < 500) {
saveDelay = 500;
}
},
});
try {
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
user,
generation,
isContinued,
isEdited: true,
conversationId,
parentMessageId,
responseMessageId,
overrideParentMessageId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId ?? userMessageId,
}),
getReqData,
onStart,
addMetadata,
abortController,
});
if (metadata) {
response = { ...response, ...metadata };
}
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
await saveMessage({ ...response, user });
await saveMessage(userMessage);
// TODO: add title service
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {
partialText,
conversationId,
sender,
messageId: responseMessageId,
parentMessageId: userMessageId ?? parentMessageId,
});
}
};
module.exports = EditController;

View file

@ -1,14 +1,16 @@
const openAI = require('~/server/routes/endpoints/openAI');
const gptPlugins = require('~/server/routes/endpoints/gptPlugins');
const anthropic = require('~/server/routes/endpoints/anthropic');
const { parseConvo, EModelEndpoint } = require('~/server/routes/endpoints/schemas');
const { processFiles } = require('~/server/services/Files'); const { processFiles } = require('~/server/services/Files');
const openAI = require('~/server/services/Endpoints/openAI');
const google = require('~/server/services/Endpoints/google');
const anthropic = require('~/server/services/Endpoints/anthropic');
const gptPlugins = require('~/server/services/Endpoints/gptPlugins');
const { parseConvo, EModelEndpoint } = require('~/server/services/Endpoints');
const buildFunction = { const buildFunction = {
[EModelEndpoint.openAI]: openAI.buildOptions, [EModelEndpoint.openAI]: openAI.buildOptions,
[EModelEndpoint.google]: google.buildOptions,
[EModelEndpoint.azureOpenAI]: openAI.buildOptions, [EModelEndpoint.azureOpenAI]: openAI.buildOptions,
[EModelEndpoint.gptPlugins]: gptPlugins.buildOptions,
[EModelEndpoint.anthropic]: anthropic.buildOptions, [EModelEndpoint.anthropic]: anthropic.buildOptions,
[EModelEndpoint.gptPlugins]: gptPlugins.buildOptions,
}; };
function buildEndpointOption(req, res, next) { function buildEndpointOption(req, res, next) {

View file

@ -1,7 +1,7 @@
const crypto = require('crypto'); const crypto = require('crypto');
const { sendMessage, sendError } = require('../utils'); const { saveMessage } = require('~/models');
const { getResponseSender } = require('../routes/endpoints/schemas'); const { sendMessage, sendError } = require('~/server/utils');
const { saveMessage } = require('../../models'); const { getResponseSender } = require('~/server/services/Endpoints');
/** /**
* Denies a request by sending an error message and optionally saves the user's message. * Denies a request by sending an error message and optionally saves the user's message.

View file

@ -1,137 +1,19 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const AskController = require('~/server/controllers/AskController');
const { getResponseSender } = require('../endpoints/schemas'); const { initializeClient } = require('~/server/services/Endpoints/anthropic');
const { initializeClient } = require('../endpoints/anthropic');
const { const {
handleAbort,
createAbortController,
handleAbortError,
setHeaders, setHeaders,
handleAbort,
validateEndpoint, validateEndpoint,
buildEndpointOption, buildEndpointOption,
} = require('~/server/middleware'); } = require('~/server/middleware');
const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { sendMessage, createOnProgress } = require('~/server/utils'); const router = express.Router();
router.post('/abort', handleAbort()); router.post('/abort', handleAbort());
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => { router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => {
let { await AskController(req, res, next, initializeClient);
text,
endpointOption,
conversationId,
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
console.log('ask log');
console.dir({ text, conversationId, endpointOption }, { depth: null });
let userMessage;
let promptTokens;
let userMessageId;
let responseMessageId;
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const user = req.user.id;
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
userMessageId = data[key].messageId;
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
} else if (key === 'promptTokens') {
promptTokens = data[key];
} else if (!conversationId && key === 'conversationId') {
conversationId = data[key];
}
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender,
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
error: false,
user,
});
}
if (saveDelay < 500) {
saveDelay = 500;
}
},
});
try {
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
getReqData,
// debug: true,
user,
conversationId,
parentMessageId,
overrideParentMessageId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId ?? userMessageId,
}),
onStart,
abortController,
});
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
await saveMessage({ ...response, user });
await saveMessage(userMessage);
// TODO: add anthropic titling
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {
partialText,
conversationId,
sender,
messageId: responseMessageId,
parentMessageId: userMessageId ?? parentMessageId,
});
}
}); });
module.exports = router; module.exports = router;

View file

@ -1,181 +1,19 @@
const express = require('express'); const express = require('express');
const AskController = require('~/server/controllers/AskController');
const { initializeClient } = require('~/server/services/Endpoints/google');
const {
setHeaders,
handleAbort,
validateEndpoint,
buildEndpointOption,
} = require('~/server/middleware');
const router = express.Router(); const router = express.Router();
const crypto = require('crypto');
const { GoogleClient } = require('../../../app');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress } = require('../../utils');
const { getUserKey, checkUserKeyExpiry } = require('../../services/UserService');
const { setHeaders } = require('../../middleware');
router.post('/', setHeaders, async (req, res) => { router.post('/abort', handleAbort());
const { endpoint, text, parentMessageId, conversationId: oldConversationId } = req.body;
if (text.length === 0) {
return handleError(res, { text: 'Prompt empty or too short' });
}
if (endpoint !== 'google') {
return handleError(res, { text: 'Illegal request' });
}
// build endpoint option router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => {
const endpointOption = { await AskController(req, res, next, initializeClient);
examples: req.body?.examples ?? [{ input: { content: '' }, output: { content: '' } }],
promptPrefix: req.body?.promptPrefix ?? null,
key: req.body?.key ?? null,
modelOptions: {
model: req.body?.model ?? 'chat-bison',
modelLabel: req.body?.modelLabel ?? null,
temperature: req.body?.temperature ?? 0.2,
maxOutputTokens: req.body?.maxOutputTokens ?? 1024,
topP: req.body?.topP ?? 0.95,
topK: req.body?.topK ?? 40,
},
};
const availableModels = ['chat-bison', 'text-bison', 'codechat-bison'];
if (availableModels.find((model) => model === endpointOption.modelOptions.model) === undefined) {
return handleError(res, { text: 'Illegal request: model' });
}
const conversationId = oldConversationId || crypto.randomUUID();
// eslint-disable-next-line no-use-before-define
return await ask({
text,
endpointOption,
conversationId,
parentMessageId,
req,
res,
});
}); });
const ask = async ({ text, endpointOption, parentMessageId = null, conversationId, req, res }) => {
let userMessage;
let userMessageId;
// let promptTokens;
let responseMessageId;
let lastSavedTimestamp = 0;
const { overrideParentMessageId = null } = req.body;
const user = req.user.id;
try {
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
userMessageId = data[key].messageId;
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
// } else if (key === 'promptTokens') {
// promptTokens = data[key];
} else if (!conversationId && key === 'conversationId') {
conversationId = data[key];
}
}
sendMessage(res, { message: userMessage, created: true });
};
const { onProgress: progressCallback } = createOnProgress({
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > 500) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender: 'PaLM2',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
error: false,
user,
});
}
},
});
const abortController = new AbortController();
const isUserProvided = process.env.PALM_KEY === 'user_provided';
let key;
if (endpointOption.key && isUserProvided) {
checkUserKeyExpiry(
endpointOption.key,
'Your GOOGLE_TOKEN has expired. Please provide your token again.',
);
key = await getUserKey({ userId: user, name: 'google' });
key = JSON.parse(key);
delete endpointOption.key;
console.log('Using service account key provided by User for PaLM models');
}
try {
key = require('../../../data/auth.json');
} catch (e) {
console.log('No \'auth.json\' file (service account key) found in /api/data/ for PaLM models');
}
const clientOptions = {
// debug: true, // for testing
reverseProxyUrl: process.env.GOOGLE_REVERSE_PROXY || null,
proxy: process.env.PROXY || null,
...endpointOption,
};
const client = new GoogleClient(key, clientOptions);
let response = await client.sendMessage(text, {
getReqData,
user,
conversationId,
parentMessageId,
overrideParentMessageId,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId,
}),
abortController,
});
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
await saveConvo(user, {
...endpointOption,
...endpointOption.modelOptions,
conversationId,
endpoint: 'google',
});
await saveMessage({ ...response, user });
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
} catch (error) {
console.error(error);
const errorMessage = {
messageId: responseMessageId,
sender: 'PaLM2',
conversationId,
parentMessageId,
unfinished: false,
cancelled: false,
error: true,
text: error.message,
};
await saveMessage({ ...errorMessage, user });
handleError(res, errorMessage);
}
};
module.exports = router; module.exports = router;

View file

@ -1,11 +1,11 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { getResponseSender } = require('../endpoints/schemas'); const { getResponseSender } = require('~/server/services/Endpoints');
const { validateTools } = require('../../../app'); const { validateTools } = require('~/app');
const { addTitle } = require('../endpoints/openAI'); const { addTitle } = require('~/server/services/Endpoints/openAI');
const { initializeClient } = require('../endpoints/gptPlugins'); const { initializeClient } = require('~/server/services/Endpoints/gptPlugins');
const { saveMessage, getConvoTitle, getConvo } = require('../../../models'); const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { sendMessage, createOnProgress } = require('../../utils'); const { sendMessage, createOnProgress } = require('~/server/utils');
const { const {
handleAbort, handleAbort,
createAbortController, createAbortController,
@ -13,7 +13,7 @@ const {
setHeaders, setHeaders,
validateEndpoint, validateEndpoint,
buildEndpointOption, buildEndpointOption,
} = require('../../middleware'); } = require('~/server/middleware');
router.post('/abort', handleAbort()); router.post('/abort', handleAbort());

View file

@ -1,11 +1,12 @@
const express = require('express'); const express = require('express');
const router = express.Router();
const openAI = require('./openAI'); const openAI = require('./openAI');
const google = require('./google'); const google = require('./google');
const bingAI = require('./bingAI'); const bingAI = require('./bingAI');
const anthropic = require('./anthropic');
const gptPlugins = require('./gptPlugins'); const gptPlugins = require('./gptPlugins');
const askChatGPTBrowser = require('./askChatGPTBrowser'); const askChatGPTBrowser = require('./askChatGPTBrowser');
const anthropic = require('./anthropic'); const { isEnabled } = require('~/server/utils');
const { EModelEndpoint } = require('~/server/services/Endpoints');
const { const {
uaParser, uaParser,
checkBan, checkBan,
@ -13,12 +14,12 @@ const {
concurrentLimiter, concurrentLimiter,
messageIpLimiter, messageIpLimiter,
messageUserLimiter, messageUserLimiter,
} = require('../../middleware'); } = require('~/server/middleware');
const { isEnabled } = require('../../utils');
const { EModelEndpoint } = require('../endpoints/schemas');
const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {}; const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {};
const router = express.Router();
router.use(requireJwtAuth); router.use(requireJwtAuth);
router.use(checkBan); router.use(checkBan);
router.use(uaParser); router.use(uaParser);
@ -36,10 +37,10 @@ if (isEnabled(LIMIT_MESSAGE_USER)) {
} }
router.use([`/${EModelEndpoint.azureOpenAI}`, `/${EModelEndpoint.openAI}`], openAI); router.use([`/${EModelEndpoint.azureOpenAI}`, `/${EModelEndpoint.openAI}`], openAI);
router.use(`/${EModelEndpoint.google}`, google);
router.use(`/${EModelEndpoint.bingAI}`, bingAI);
router.use(`/${EModelEndpoint.chatGPTBrowser}`, askChatGPTBrowser); router.use(`/${EModelEndpoint.chatGPTBrowser}`, askChatGPTBrowser);
router.use(`/${EModelEndpoint.gptPlugins}`, gptPlugins); router.use(`/${EModelEndpoint.gptPlugins}`, gptPlugins);
router.use(`/${EModelEndpoint.anthropic}`, anthropic); router.use(`/${EModelEndpoint.anthropic}`, anthropic);
router.use(`/${EModelEndpoint.google}`, google);
router.use(`/${EModelEndpoint.bingAI}`, bingAI);
module.exports = router; module.exports = router;

View file

@ -2,8 +2,8 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const { sendMessage, createOnProgress } = require('~/server/utils'); const { sendMessage, createOnProgress } = require('~/server/utils');
const { saveMessage, getConvoTitle, getConvo } = require('~/models'); const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { getResponseSender } = require('~/server/routes/endpoints/schemas'); const { getResponseSender } = require('~/server/services/Endpoints');
const { addTitle, initializeClient } = require('~/server/routes/endpoints/openAI'); const { addTitle, initializeClient } = require('~/server/services/Endpoints/openAI');
const { const {
handleAbort, handleAbort,
createAbortController, createAbortController,

View file

@ -1,147 +1,19 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const EditController = require('~/server/controllers/EditController');
const { getResponseSender } = require('../endpoints/schemas'); const { initializeClient } = require('~/server/services/Endpoints/anthropic');
const { initializeClient } = require('../endpoints/anthropic');
const { const {
handleAbort,
createAbortController,
handleAbortError,
setHeaders, setHeaders,
handleAbort,
validateEndpoint, validateEndpoint,
buildEndpointOption, buildEndpointOption,
} = require('~/server/middleware'); } = require('~/server/middleware');
const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { sendMessage, createOnProgress } = require('~/server/utils'); const router = express.Router();
router.post('/abort', handleAbort()); router.post('/abort', handleAbort());
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res) => { router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => {
let { await EditController(req, res, next, initializeClient);
text,
generation,
endpointOption,
conversationId,
responseMessageId,
isContinued = false,
parentMessageId = null,
overrideParentMessageId = null,
} = req.body;
console.log('edit log');
console.dir({ text, generation, isContinued, conversationId, endpointOption }, { depth: null });
let metadata;
let userMessage;
let promptTokens;
let lastSavedTimestamp = 0;
let saveDelay = 100;
const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model });
const userMessageId = parentMessageId;
const user = req.user.id;
const addMetadata = (data) => (metadata = data);
const getReqData = (data = {}) => {
for (let key in data) {
if (key === 'userMessage') {
userMessage = data[key];
} else if (key === 'responseMessageId') {
responseMessageId = data[key];
} else if (key === 'promptTokens') {
promptTokens = data[key];
}
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
generation,
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > saveDelay) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender,
conversationId,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
isEdited: true,
error: false,
user,
});
}
if (saveDelay < 500) {
saveDelay = 500;
}
},
});
try {
const getAbortData = () => ({
conversationId,
messageId: responseMessageId,
sender,
parentMessageId: overrideParentMessageId ?? userMessageId,
text: getPartialText(),
userMessage,
promptTokens,
});
const { abortController, onStart } = createAbortController(req, res, getAbortData);
const { client } = await initializeClient({ req, res, endpointOption });
let response = await client.sendMessage(text, {
user,
generation,
isContinued,
isEdited: true,
conversationId,
parentMessageId,
responseMessageId,
overrideParentMessageId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId ?? userMessageId,
}),
getReqData,
onStart,
addMetadata,
abortController,
});
if (metadata) {
response = { ...response, ...metadata };
}
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
sendMessage(res, {
title: await getConvoTitle(user, conversationId),
final: true,
conversation: await getConvo(user, conversationId),
requestMessage: userMessage,
responseMessage: response,
});
res.end();
await saveMessage({ ...response, user });
await saveMessage(userMessage);
// TODO: add anthropic titling
} catch (error) {
const partialText = getPartialText();
handleAbortError(res, req, error, {
partialText,
conversationId,
sender,
messageId: responseMessageId,
parentMessageId: userMessageId ?? parentMessageId,
});
}
}); });
module.exports = router; module.exports = router;

View file

@ -0,0 +1,19 @@
const express = require('express');
const EditController = require('~/server/controllers/EditController');
const { initializeClient } = require('~/server/services/Endpoints/google');
const {
setHeaders,
handleAbort,
validateEndpoint,
buildEndpointOption,
} = require('~/server/middleware');
const router = express.Router();
router.post('/abort', handleAbort());
router.post('/', validateEndpoint, buildEndpointOption, setHeaders, async (req, res, next) => {
await EditController(req, res, next, initializeClient);
});
module.exports = router;

View file

@ -1,10 +1,10 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { getResponseSender } = require('../endpoints/schemas'); const { validateTools } = require('~/app');
const { validateTools } = require('../../../app'); const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { initializeClient } = require('../endpoints/gptPlugins'); const { getResponseSender } = require('~/server/services/Endpoints');
const { saveMessage, getConvoTitle, getConvo } = require('../../../models'); const { initializeClient } = require('~/server/services/Endpoints/gptPlugins');
const { sendMessage, createOnProgress, formatSteps, formatAction } = require('../../utils'); const { sendMessage, createOnProgress, formatSteps, formatAction } = require('~/server/utils');
const { const {
handleAbort, handleAbort,
createAbortController, createAbortController,
@ -12,7 +12,7 @@ const {
setHeaders, setHeaders,
validateEndpoint, validateEndpoint,
buildEndpointOption, buildEndpointOption,
} = require('../../middleware'); } = require('~/server/middleware');
router.post('/abort', handleAbort()); router.post('/abort', handleAbort());

View file

@ -1,20 +1,23 @@
const express = require('express'); const express = require('express');
const router = express.Router();
const openAI = require('./openAI'); const openAI = require('./openAI');
const gptPlugins = require('./gptPlugins'); const google = require('./google');
const anthropic = require('./anthropic'); const anthropic = require('./anthropic');
const gptPlugins = require('./gptPlugins');
const { isEnabled } = require('~/server/utils');
const { EModelEndpoint } = require('~/server/services/Endpoints');
const { const {
checkBan, checkBan,
uaParser, uaParser,
requireJwtAuth, requireJwtAuth,
concurrentLimiter,
messageIpLimiter, messageIpLimiter,
concurrentLimiter,
messageUserLimiter, messageUserLimiter,
} = require('../../middleware'); } = require('~/server/middleware');
const { isEnabled } = require('../../utils');
const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {}; const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {};
const router = express.Router();
router.use(requireJwtAuth); router.use(requireJwtAuth);
router.use(checkBan); router.use(checkBan);
router.use(uaParser); router.use(uaParser);
@ -31,8 +34,9 @@ if (isEnabled(LIMIT_MESSAGE_USER)) {
router.use(messageUserLimiter); router.use(messageUserLimiter);
} }
router.use(['/azureOpenAI', '/openAI'], openAI); router.use([`/${EModelEndpoint.azureOpenAI}`, `/${EModelEndpoint.openAI}`], openAI);
router.use('/gptPlugins', gptPlugins); router.use(`/${EModelEndpoint.gptPlugins}`, gptPlugins);
router.use('/anthropic', anthropic); router.use(`/${EModelEndpoint.anthropic}`, anthropic);
router.use(`/${EModelEndpoint.google}`, google);
module.exports = router; module.exports = router;

View file

@ -1,9 +1,9 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { getResponseSender } = require('../endpoints/schemas'); const { getResponseSender } = require('~/server/services/Endpoints');
const { initializeClient } = require('../endpoints/openAI'); const { initializeClient } = require('~/server/services/Endpoints/openAI');
const { saveMessage, getConvoTitle, getConvo } = require('../../../models'); const { saveMessage, getConvoTitle, getConvo } = require('~/models');
const { sendMessage, createOnProgress } = require('../../utils'); const { sendMessage, createOnProgress } = require('~/server/utils');
const { const {
handleAbort, handleAbort,
createAbortController, createAbortController,
@ -11,7 +11,7 @@ const {
setHeaders, setHeaders,
validateEndpoint, validateEndpoint,
buildEndpointOption, buildEndpointOption,
} = require('../../middleware'); } = require('~/server/middleware');
router.post('/abort', handleAbort()); router.post('/abort', handleAbort());

View file

@ -1,4 +1,4 @@
const { EModelEndpoint } = require('~/server/routes/endpoints/schemas'); const { EModelEndpoint } = require('~/server/services/Endpoints');
const { const {
OPENAI_API_KEY: openAIApiKey, OPENAI_API_KEY: openAIApiKey,
@ -7,7 +7,7 @@ const {
CHATGPT_TOKEN: chatGPTToken, CHATGPT_TOKEN: chatGPTToken,
BINGAI_TOKEN: bingToken, BINGAI_TOKEN: bingToken,
PLUGINS_USE_AZURE, PLUGINS_USE_AZURE,
PALM_KEY: palmKey, GOOGLE_KEY: googleKey,
} = process.env ?? {}; } = process.env ?? {};
const useAzurePlugins = !!PLUGINS_USE_AZURE; const useAzurePlugins = !!PLUGINS_USE_AZURE;
@ -26,7 +26,7 @@ module.exports = {
azureOpenAIApiKey, azureOpenAIApiKey,
useAzurePlugins, useAzurePlugins,
userProvidedOpenAI, userProvidedOpenAI,
palmKey, googleKey,
[EModelEndpoint.openAI]: isUserProvided(openAIApiKey), [EModelEndpoint.openAI]: isUserProvided(openAIApiKey),
[EModelEndpoint.assistant]: isUserProvided(openAIApiKey), [EModelEndpoint.assistant]: isUserProvided(openAIApiKey),
[EModelEndpoint.azureOpenAI]: isUserProvided(azureOpenAIApiKey), [EModelEndpoint.azureOpenAI]: isUserProvided(azureOpenAIApiKey),

View file

@ -1,6 +1,6 @@
const { availableTools } = require('~/app/clients/tools'); const { availableTools } = require('~/app/clients/tools');
const { addOpenAPISpecs } = require('~/app/clients/tools/util/addOpenAPISpecs'); const { addOpenAPISpecs } = require('~/app/clients/tools/util/addOpenAPISpecs');
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, palmKey } = const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } =
require('./EndpointService').config; require('./EndpointService').config;
/** /**
@ -8,7 +8,7 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, pa
*/ */
async function loadAsyncEndpoints() { async function loadAsyncEndpoints() {
let i = 0; let i = 0;
let key, palmUser; let key, googleUserProvides;
try { try {
key = require('~/data/auth.json'); key = require('~/data/auth.json');
} catch (e) { } catch (e) {
@ -17,8 +17,8 @@ async function loadAsyncEndpoints() {
} }
} }
if (palmKey === 'user_provided') { if (googleKey === 'user_provided') {
palmUser = true; googleUserProvides = true;
if (i <= 1) { if (i <= 1) {
i++; i++;
} }
@ -33,7 +33,7 @@ async function loadAsyncEndpoints() {
} }
const plugins = transformToolsToMap(tools); const plugins = transformToolsToMap(tools);
const google = key || palmUser ? { userProvide: palmUser } : false; const google = key || googleUserProvides ? { userProvide: googleUserProvides } : false;
const gptPlugins = const gptPlugins =
openAIApiKey || azureOpenAIApiKey openAIApiKey || azureOpenAIApiKey

View file

@ -1,4 +1,4 @@
const { EModelEndpoint } = require('~/server/routes/endpoints/schemas'); const { EModelEndpoint } = require('~/server/services/Endpoints');
const loadAsyncEndpoints = require('./loadAsyncEndpoints'); const loadAsyncEndpoints = require('./loadAsyncEndpoints');
const { config } = require('./EndpointService'); const { config } = require('./EndpointService');

View file

@ -3,7 +3,7 @@ const {
getChatGPTBrowserModels, getChatGPTBrowserModels,
getAnthropicModels, getAnthropicModels,
} = require('~/server/services/ModelService'); } = require('~/server/services/ModelService');
const { EModelEndpoint } = require('~/server/routes/endpoints/schemas'); const { EModelEndpoint } = require('~/server/services/Endpoints');
const { useAzurePlugins } = require('~/server/services/Config/EndpointService').config; const { useAzurePlugins } = require('~/server/services/Config/EndpointService').config;
const fitlerAssistantModels = (str) => { const fitlerAssistantModels = (str) => {
@ -21,7 +21,18 @@ async function loadDefaultModels() {
[EModelEndpoint.openAI]: openAI, [EModelEndpoint.openAI]: openAI,
[EModelEndpoint.azureOpenAI]: azureOpenAI, [EModelEndpoint.azureOpenAI]: azureOpenAI,
[EModelEndpoint.assistant]: openAI.filter(fitlerAssistantModels), [EModelEndpoint.assistant]: openAI.filter(fitlerAssistantModels),
[EModelEndpoint.google]: ['chat-bison', 'text-bison', 'codechat-bison'], [EModelEndpoint.google]: [
'chat-bison',
'chat-bison-32k',
'codechat-bison',
'codechat-bison-32k',
'text-bison',
'text-bison-32k',
'text-unicorn',
'code-gecko',
'code-bison',
'code-bison-32k',
],
[EModelEndpoint.bingAI]: ['BingAI', 'Sydney'], [EModelEndpoint.bingAI]: ['BingAI', 'Sydney'],
[EModelEndpoint.chatGPTBrowser]: chatGPTBrowser, [EModelEndpoint.chatGPTBrowser]: chatGPTBrowser,
[EModelEndpoint.gptPlugins]: gptPlugins, [EModelEndpoint.gptPlugins]: gptPlugins,

View file

@ -2,7 +2,7 @@ const { AnthropicClient } = require('~/app');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const initializeClient = async ({ req, res, endpointOption }) => { const initializeClient = async ({ req, res, endpointOption }) => {
const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY } = process.env; const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env;
const expiresAt = req.body.key; const expiresAt = req.body.key;
const isUserProvided = ANTHROPIC_API_KEY === 'user_provided'; const isUserProvided = ANTHROPIC_API_KEY === 'user_provided';
@ -21,6 +21,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
req, req,
res, res,
reverseProxyUrl: ANTHROPIC_REVERSE_PROXY ?? null, reverseProxyUrl: ANTHROPIC_REVERSE_PROXY ?? null,
proxy: PROXY ?? null,
...endpointOption, ...endpointOption,
}); });

View file

@ -0,0 +1,16 @@
const buildOptions = (endpoint, parsedBody) => {
const { examples, modelLabel, promptPrefix, ...rest } = parsedBody;
const endpointOption = {
examples,
endpoint,
modelLabel,
promptPrefix,
modelOptions: {
...rest,
},
};
return endpointOption;
};
module.exports = buildOptions;

View file

@ -0,0 +1,8 @@
const buildOptions = require('./buildOptions');
const initializeClient = require('./initializeClient');
module.exports = {
// addTitle, // todo
buildOptions,
initializeClient,
};

View file

@ -0,0 +1,35 @@
const { GoogleClient } = require('~/app');
const { EModelEndpoint } = require('~/server/services/Endpoints');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const initializeClient = async ({ req, res, endpointOption }) => {
const { GOOGLE_KEY, GOOGLE_REVERSE_PROXY, PROXY } = process.env;
const isUserProvided = GOOGLE_KEY === 'user_provided';
const { key: expiresAt } = req.body;
let userKey = null;
if (expiresAt && isUserProvided) {
checkUserKeyExpiry(
expiresAt,
'Your Google key has expired. Please provide your JSON credentials again.',
);
userKey = await getUserKey({ userId: req.user.id, name: EModelEndpoint.google });
}
const apiKey = isUserProvided ? userKey : require('~/data/auth.json');
const client = new GoogleClient(apiKey, {
req,
res,
reverseProxyUrl: GOOGLE_REVERSE_PROXY ?? null,
proxy: PROXY ?? null,
...endpointOption,
});
return {
client,
apiKey,
};
};
module.exports = initializeClient;

View file

@ -1,7 +1,7 @@
const { PluginsClient } = require('../../../../app'); const { PluginsClient } = require('~/app');
const { isEnabled } = require('../../../utils'); const { isEnabled } = require('~/server/utils');
const { getAzureCredentials } = require('../../../../utils'); const { getAzureCredentials } = require('~/utils');
const { getUserKey, checkUserKeyExpiry } = require('../../../services/UserService'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const initializeClient = async ({ req, res, endpointOption }) => { const initializeClient = async ({ req, res, endpointOption }) => {
const { const {

View file

@ -1,12 +1,12 @@
// gptPlugins/initializeClient.spec.js // gptPlugins/initializeClient.spec.js
const { PluginsClient } = require('~/app');
const initializeClient = require('./initializeClient'); const initializeClient = require('./initializeClient');
const { PluginsClient } = require('../../../../app'); const { getUserKey } = require('../../UserService');
const { getUserKey } = require('../../../services/UserService');
// Mock getUserKey since it's the only function we want to mock // Mock getUserKey since it's the only function we want to mock
jest.mock('../../../services/UserService', () => ({ jest.mock('~/server/services/UserService', () => ({
getUserKey: jest.fn(), getUserKey: jest.fn(),
checkUserKeyExpiry: jest.requireActual('../../../services/UserService').checkUserKeyExpiry, checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry,
})); }));
describe('gptPlugins/initializeClient', () => { describe('gptPlugins/initializeClient', () => {

View file

@ -0,0 +1,5 @@
const schemas = require('./schemas');
module.exports = {
...schemas,
};

View file

@ -1,11 +1,11 @@
const { OpenAIClient } = require('~/app');
const initializeClient = require('./initializeClient'); const initializeClient = require('./initializeClient');
const { OpenAIClient } = require('../../../../app'); const { getUserKey } = require('~/server/services/UserService');
const { getUserKey } = require('../../../services/UserService');
// Mock getUserKey since it's the only function we want to mock // Mock getUserKey since it's the only function we want to mock
jest.mock('../../../services/UserService', () => ({ jest.mock('~/server/services/UserService', () => ({
getUserKey: jest.fn(), getUserKey: jest.fn(),
checkUserKeyExpiry: jest.requireActual('../../../services/UserService').checkUserKeyExpiry, checkUserKeyExpiry: jest.requireActual('~/server/services/UserService').checkUserKeyExpiry,
})); }));
describe('initializeClient', () => { describe('initializeClient', () => {

View file

@ -18,10 +18,44 @@ const alternateName = {
[EModelEndpoint.bingAI]: 'Bing', [EModelEndpoint.bingAI]: 'Bing',
[EModelEndpoint.chatGPTBrowser]: 'ChatGPT', [EModelEndpoint.chatGPTBrowser]: 'ChatGPT',
[EModelEndpoint.gptPlugins]: 'Plugins', [EModelEndpoint.gptPlugins]: 'Plugins',
[EModelEndpoint.google]: 'PaLM', [EModelEndpoint.google]: 'Google',
[EModelEndpoint.anthropic]: 'Anthropic', [EModelEndpoint.anthropic]: 'Anthropic',
}; };
const endpointSettings = {
[EModelEndpoint.google]: {
model: {
default: 'chat-bison',
},
maxOutputTokens: {
min: 1,
max: 2048,
step: 1,
default: 1024,
},
temperature: {
min: 0,
max: 1,
step: 0.01,
default: 0.2,
},
topP: {
min: 0,
max: 1,
step: 0.01,
default: 0.8,
},
topK: {
min: 1,
max: 40,
step: 0.01,
default: 40,
},
},
};
const google = endpointSettings[EModelEndpoint.google];
const supportsFiles = { const supportsFiles = {
[EModelEndpoint.openAI]: true, [EModelEndpoint.openAI]: true,
[EModelEndpoint.assistant]: true, [EModelEndpoint.assistant]: true,
@ -158,22 +192,24 @@ const googleSchema = tConversationSchema
}) })
.transform((obj) => ({ .transform((obj) => ({
...obj, ...obj,
model: obj.model ?? 'chat-bison', model: obj.model ?? google.model.default,
modelLabel: obj.modelLabel ?? null, modelLabel: obj.modelLabel ?? null,
promptPrefix: obj.promptPrefix ?? null, promptPrefix: obj.promptPrefix ?? null,
temperature: obj.temperature ?? 0.2, examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }],
maxOutputTokens: obj.maxOutputTokens ?? 1024, temperature: obj.temperature ?? google.temperature.default,
topP: obj.topP ?? 0.95, maxOutputTokens: obj.maxOutputTokens ?? google.maxOutputTokens.default,
topK: obj.topK ?? 40, topP: obj.topP ?? google.topP.default,
topK: obj.topK ?? google.topK.default,
})) }))
.catch(() => ({ .catch(() => ({
model: 'chat-bison', model: google.model.default,
modelLabel: null, modelLabel: null,
promptPrefix: null, promptPrefix: null,
temperature: 0.2, examples: [{ input: { content: '' }, output: { content: '' } }],
maxOutputTokens: 1024, temperature: google.temperature.default,
topP: 0.95, maxOutputTokens: google.maxOutputTokens.default,
topK: 40, topP: google.topP.default,
topK: google.topK.default,
})); }));
const bingAISchema = tConversationSchema const bingAISchema = tConversationSchema
@ -385,7 +421,13 @@ const getResponseSender = (endpointOption) => {
} }
if (endpoint === EModelEndpoint.google) { if (endpoint === EModelEndpoint.google) {
return modelLabel ?? 'PaLM2'; if (modelLabel) {
return modelLabel;
} else if (model && model.includes('code')) {
return 'Codey';
}
return 'PaLM2';
} }
return ''; return '';
@ -399,4 +441,5 @@ module.exports = {
openAIModels, openAIModels,
visionModels, visionModels,
alternateName, alternateName,
endpointSettings,
}; };

View file

@ -1,4 +1,4 @@
const { visionModels } = require('~/server/routes/endpoints/schemas'); const { visionModels } = require('~/server/services/Endpoints');
function validateVisionModel(model) { function validateVisionModel(model) {
if (!model) { if (!model) {

View file

@ -247,7 +247,7 @@
* @property {string} azureOpenAIApiKey - The API key for Azure OpenAI. * @property {string} azureOpenAIApiKey - The API key for Azure OpenAI.
* @property {boolean} useAzurePlugins - Flag to indicate if Azure plugins are used. * @property {boolean} useAzurePlugins - Flag to indicate if Azure plugins are used.
* @property {boolean} userProvidedOpenAI - Flag to indicate if OpenAI API key is user provided. * @property {boolean} userProvidedOpenAI - Flag to indicate if OpenAI API key is user provided.
* @property {string} palmKey - The Palm key. * @property {string} googleKey - The Palm key.
* @property {boolean|{userProvide: boolean}} [openAI] - Flag to indicate if OpenAI endpoint is user provided, or its configuration. * @property {boolean|{userProvide: boolean}} [openAI] - Flag to indicate if OpenAI endpoint is user provided, or its configuration.
* @property {boolean|{userProvide: boolean}} [assistant] - Flag to indicate if Assistant endpoint is user provided, or its configuration. * @property {boolean|{userProvide: boolean}} [assistant] - Flag to indicate if Assistant endpoint is user provided, or its configuration.
* @property {boolean|{userProvide: boolean}} [azureOpenAI] - Flag to indicate if Azure OpenAI endpoint is user provided, or its configuration. * @property {boolean|{userProvide: boolean}} [azureOpenAI] - Flag to indicate if Azure OpenAI endpoint is user provided, or its configuration.

View file

@ -1,3 +1,5 @@
const { EModelEndpoint } = require('~/server/services/Endpoints');
const models = [ const models = [
'text-davinci-003', 'text-davinci-003',
'text-davinci-002', 'text-davinci-002',
@ -39,20 +41,37 @@ const models = [
// Order is important here: by model series and context size (gpt-4 then gpt-3, ascending) // Order is important here: by model series and context size (gpt-4 then gpt-3, ascending)
const maxTokensMap = { const maxTokensMap = {
'gpt-4': 8191, [EModelEndpoint.openAI]: {
'gpt-4-0613': 8191, 'gpt-4': 8191,
'gpt-4-32k': 32767, 'gpt-4-0613': 8191,
'gpt-4-32k-0314': 32767, 'gpt-4-32k': 32767,
'gpt-4-32k-0613': 32767, 'gpt-4-32k-0314': 32767,
'gpt-3.5-turbo': 4095, 'gpt-4-32k-0613': 32767,
'gpt-3.5-turbo-0613': 4095, 'gpt-3.5-turbo': 4095,
'gpt-3.5-turbo-0301': 4095, 'gpt-3.5-turbo-0613': 4095,
'gpt-3.5-turbo-16k': 15999, 'gpt-3.5-turbo-0301': 4095,
'gpt-3.5-turbo-16k-0613': 15999, 'gpt-3.5-turbo-16k': 15999,
'gpt-3.5-turbo-1106': 16380, // -5 from max 'gpt-3.5-turbo-16k-0613': 15999,
'gpt-4-1106': 127995, // -5 from max 'gpt-3.5-turbo-1106': 16380, // -5 from max
'claude-2.1': 200000, 'gpt-4-1106': 127995, // -5 from max
'claude-': 100000, },
[EModelEndpoint.google]: {
/* Max I/O is 32k combined, so -1000 to leave room for response */
'text-bison-32k': 31000,
'chat-bison-32k': 31000,
'code-bison-32k': 31000,
'codechat-bison-32k': 31000,
/* Codey, -5 from max: 6144 */
'code-': 6139,
'codechat-': 6139,
/* PaLM2, -5 from max: 8192 */
'text-': 8187,
'chat-': 8187,
},
[EModelEndpoint.anthropic]: {
'claude-2.1': 200000,
'claude-': 100000,
},
}; };
/** /**
@ -60,6 +79,7 @@ const maxTokensMap = {
* it searches for partial matches within the model name, checking keys in reverse order. * it searches for partial matches within the model name, checking keys in reverse order.
* *
* @param {string} modelName - The name of the model to look up. * @param {string} modelName - The name of the model to look up.
* @param {string} endpoint - The endpoint (default is 'openAI').
* @returns {number|undefined} The maximum tokens for the given model or undefined if no match is found. * @returns {number|undefined} The maximum tokens for the given model or undefined if no match is found.
* *
* @example * @example
@ -67,19 +87,24 @@ const maxTokensMap = {
* getModelMaxTokens('gpt-4-32k-unknown'); // Returns 32767 * getModelMaxTokens('gpt-4-32k-unknown'); // Returns 32767
* getModelMaxTokens('unknown-model'); // Returns undefined * getModelMaxTokens('unknown-model'); // Returns undefined
*/ */
function getModelMaxTokens(modelName) { function getModelMaxTokens(modelName, endpoint = EModelEndpoint.openAI) {
if (typeof modelName !== 'string') { if (typeof modelName !== 'string') {
return undefined; return undefined;
} }
if (maxTokensMap[modelName]) { const tokensMap = maxTokensMap[endpoint];
return maxTokensMap[modelName]; if (!tokensMap) {
return undefined;
} }
const keys = Object.keys(maxTokensMap); if (tokensMap[modelName]) {
return tokensMap[modelName];
}
const keys = Object.keys(tokensMap);
for (let i = keys.length - 1; i >= 0; i--) { for (let i = keys.length - 1; i >= 0; i--) {
if (modelName.includes(keys[i])) { if (modelName.includes(keys[i])) {
return maxTokensMap[keys[i]]; return tokensMap[keys[i]];
} }
} }
@ -91,6 +116,7 @@ function getModelMaxTokens(modelName) {
* it searches for partial matches within the model name, checking keys in reverse order. * it searches for partial matches within the model name, checking keys in reverse order.
* *
* @param {string} modelName - The name of the model to look up. * @param {string} modelName - The name of the model to look up.
* @param {string} endpoint - The endpoint (default is 'openAI').
* @returns {string|undefined} The model name key for the given model; returns input if no match is found and is string. * @returns {string|undefined} The model name key for the given model; returns input if no match is found and is string.
* *
* @example * @example
@ -98,16 +124,21 @@ function getModelMaxTokens(modelName) {
* matchModelName('gpt-4-32k-unknown'); // Returns 'gpt-4-32k' * matchModelName('gpt-4-32k-unknown'); // Returns 'gpt-4-32k'
* matchModelName('unknown-model'); // Returns undefined * matchModelName('unknown-model'); // Returns undefined
*/ */
function matchModelName(modelName) { function matchModelName(modelName, endpoint = EModelEndpoint.openAI) {
if (typeof modelName !== 'string') { if (typeof modelName !== 'string') {
return undefined; return undefined;
} }
if (maxTokensMap[modelName]) { const tokensMap = maxTokensMap[endpoint];
if (!tokensMap) {
return modelName; return modelName;
} }
const keys = Object.keys(maxTokensMap); if (tokensMap[modelName]) {
return modelName;
}
const keys = Object.keys(tokensMap);
for (let i = keys.length - 1; i >= 0; i--) { for (let i = keys.length - 1; i >= 0; i--) {
if (modelName.includes(keys[i])) { if (modelName.includes(keys[i])) {
return keys[i]; return keys[i];

View file

@ -1,16 +1,23 @@
const { EModelEndpoint } = require('~/server/services/Endpoints');
const { getModelMaxTokens, matchModelName, maxTokensMap } = require('./tokens'); const { getModelMaxTokens, matchModelName, maxTokensMap } = require('./tokens');
describe('getModelMaxTokens', () => { describe('getModelMaxTokens', () => {
test('should return correct tokens for exact match', () => { test('should return correct tokens for exact match', () => {
expect(getModelMaxTokens('gpt-4-32k-0613')).toBe(maxTokensMap['gpt-4-32k-0613']); expect(getModelMaxTokens('gpt-4-32k-0613')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4-32k-0613'],
);
}); });
test('should return correct tokens for partial match', () => { test('should return correct tokens for partial match', () => {
expect(getModelMaxTokens('gpt-4-32k-unknown')).toBe(maxTokensMap['gpt-4-32k']); expect(getModelMaxTokens('gpt-4-32k-unknown')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4-32k'],
);
}); });
test('should return correct tokens for partial match (OpenRouter)', () => { test('should return correct tokens for partial match (OpenRouter)', () => {
expect(getModelMaxTokens('openai/gpt-4-32k')).toBe(maxTokensMap['gpt-4-32k']); expect(getModelMaxTokens('openai/gpt-4-32k')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4-32k'],
);
}); });
test('should return undefined for no match', () => { test('should return undefined for no match', () => {
@ -19,12 +26,14 @@ describe('getModelMaxTokens', () => {
test('should return correct tokens for another exact match', () => { test('should return correct tokens for another exact match', () => {
expect(getModelMaxTokens('gpt-3.5-turbo-16k-0613')).toBe( expect(getModelMaxTokens('gpt-3.5-turbo-16k-0613')).toBe(
maxTokensMap['gpt-3.5-turbo-16k-0613'], maxTokensMap[EModelEndpoint.openAI]['gpt-3.5-turbo-16k-0613'],
); );
}); });
test('should return correct tokens for another partial match', () => { test('should return correct tokens for another partial match', () => {
expect(getModelMaxTokens('gpt-3.5-turbo-unknown')).toBe(maxTokensMap['gpt-3.5-turbo']); expect(getModelMaxTokens('gpt-3.5-turbo-unknown')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-3.5-turbo'],
);
}); });
test('should return undefined for undefined input', () => { test('should return undefined for undefined input', () => {
@ -41,26 +50,34 @@ describe('getModelMaxTokens', () => {
// 11/06 Update // 11/06 Update
test('should return correct tokens for gpt-3.5-turbo-1106 exact match', () => { test('should return correct tokens for gpt-3.5-turbo-1106 exact match', () => {
expect(getModelMaxTokens('gpt-3.5-turbo-1106')).toBe(maxTokensMap['gpt-3.5-turbo-1106']); expect(getModelMaxTokens('gpt-3.5-turbo-1106')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-3.5-turbo-1106'],
);
}); });
test('should return correct tokens for gpt-4-1106 exact match', () => { test('should return correct tokens for gpt-4-1106 exact match', () => {
expect(getModelMaxTokens('gpt-4-1106')).toBe(maxTokensMap['gpt-4-1106']); expect(getModelMaxTokens('gpt-4-1106')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-4-1106']);
}); });
test('should return correct tokens for gpt-3.5-turbo-1106 partial match', () => { test('should return correct tokens for gpt-3.5-turbo-1106 partial match', () => {
expect(getModelMaxTokens('something-/gpt-3.5-turbo-1106')).toBe( expect(getModelMaxTokens('something-/gpt-3.5-turbo-1106')).toBe(
maxTokensMap['gpt-3.5-turbo-1106'], maxTokensMap[EModelEndpoint.openAI]['gpt-3.5-turbo-1106'],
); );
expect(getModelMaxTokens('gpt-3.5-turbo-1106/something-/')).toBe( expect(getModelMaxTokens('gpt-3.5-turbo-1106/something-/')).toBe(
maxTokensMap['gpt-3.5-turbo-1106'], maxTokensMap[EModelEndpoint.openAI]['gpt-3.5-turbo-1106'],
); );
}); });
test('should return correct tokens for gpt-4-1106 partial match', () => { test('should return correct tokens for gpt-4-1106 partial match', () => {
expect(getModelMaxTokens('gpt-4-1106/something')).toBe(maxTokensMap['gpt-4-1106']); expect(getModelMaxTokens('gpt-4-1106/something')).toBe(
expect(getModelMaxTokens('gpt-4-1106-preview')).toBe(maxTokensMap['gpt-4-1106']); maxTokensMap[EModelEndpoint.openAI]['gpt-4-1106'],
expect(getModelMaxTokens('gpt-4-1106-vision-preview')).toBe(maxTokensMap['gpt-4-1106']); );
expect(getModelMaxTokens('gpt-4-1106-preview')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4-1106'],
);
expect(getModelMaxTokens('gpt-4-1106-vision-preview')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4-1106'],
);
}); });
test('should return correct tokens for Anthropic models', () => { test('should return correct tokens for Anthropic models', () => {
@ -74,13 +91,36 @@ describe('getModelMaxTokens', () => {
'claude-instant-1-100k', 'claude-instant-1-100k',
]; ];
const claude21MaxTokens = maxTokensMap['claude-2.1']; const claudeMaxTokens = maxTokensMap[EModelEndpoint.anthropic]['claude-'];
const claudeMaxTokens = maxTokensMap['claude-']; const claude21MaxTokens = maxTokensMap[EModelEndpoint.anthropic]['claude-2.1'];
models.forEach((model) => { models.forEach((model) => {
const expectedTokens = model === 'claude-2.1' ? claude21MaxTokens : claudeMaxTokens; const expectedTokens = model === 'claude-2.1' ? claude21MaxTokens : claudeMaxTokens;
expect(getModelMaxTokens(model)).toEqual(expectedTokens); expect(getModelMaxTokens(model, EModelEndpoint.anthropic)).toEqual(expectedTokens);
}); });
}); });
// Tests for Google models
test('should return correct tokens for exact match - Google models', () => {
expect(getModelMaxTokens('text-bison-32k', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['text-bison-32k'],
);
expect(getModelMaxTokens('codechat-bison-32k', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['codechat-bison-32k'],
);
});
test('should return undefined for no match - Google models', () => {
expect(getModelMaxTokens('unknown-google-model', EModelEndpoint.google)).toBeUndefined();
});
test('should return correct tokens for partial match - Google models', () => {
expect(getModelMaxTokens('code-', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['code-'],
);
expect(getModelMaxTokens('chat-', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['chat-'],
);
});
}); });
describe('matchModelName', () => { describe('matchModelName', () => {
@ -122,4 +162,21 @@ describe('matchModelName', () => {
expect(matchModelName('gpt-4-1106-preview')).toBe('gpt-4-1106'); expect(matchModelName('gpt-4-1106-preview')).toBe('gpt-4-1106');
expect(matchModelName('gpt-4-1106-vision-preview')).toBe('gpt-4-1106'); expect(matchModelName('gpt-4-1106-vision-preview')).toBe('gpt-4-1106');
}); });
// Tests for Google models
it('should return the exact model name if it exists in maxTokensMap - Google models', () => {
expect(matchModelName('text-bison-32k', EModelEndpoint.google)).toBe('text-bison-32k');
expect(matchModelName('codechat-bison-32k', EModelEndpoint.google)).toBe('codechat-bison-32k');
});
it('should return the input model name if no match is found - Google models', () => {
expect(matchModelName('unknown-google-model', EModelEndpoint.google)).toBe(
'unknown-google-model',
);
});
it('should return the closest matching key for partial matches - Google models', () => {
expect(matchModelName('code-', EModelEndpoint.google)).toBe('code-');
expect(matchModelName('chat-', EModelEndpoint.google)).toBe('chat-');
});
}); });

View file

@ -5,7 +5,7 @@ import {
AnthropicIcon, AnthropicIcon,
AzureMinimalIcon, AzureMinimalIcon,
BingAIMinimalIcon, BingAIMinimalIcon,
PaLMinimalIcon, GoogleMinimalIcon,
LightningIcon, LightningIcon,
} from '~/components/svg'; } from '~/components/svg';
import { cn } from '~/utils'; import { cn } from '~/utils';
@ -16,7 +16,7 @@ export const icons = {
[EModelEndpoint.gptPlugins]: MinimalPlugin, [EModelEndpoint.gptPlugins]: MinimalPlugin,
[EModelEndpoint.anthropic]: AnthropicIcon, [EModelEndpoint.anthropic]: AnthropicIcon,
[EModelEndpoint.chatGPTBrowser]: LightningIcon, [EModelEndpoint.chatGPTBrowser]: LightningIcon,
[EModelEndpoint.google]: PaLMinimalIcon, [EModelEndpoint.google]: GoogleMinimalIcon,
[EModelEndpoint.bingAI]: BingAIMinimalIcon, [EModelEndpoint.bingAI]: BingAIMinimalIcon,
[EModelEndpoint.assistant]: ({ className = '' }) => ( [EModelEndpoint.assistant]: ({ className = '' }) => (
<svg <svg

View file

@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
import { useGetEndpointsQuery } from 'librechat-data-provider'; import { useGetEndpointsQuery } from 'librechat-data-provider';
import { cn, defaultTextProps, removeFocusOutlines, mapEndpoints } from '~/utils'; import { cn, defaultTextProps, removeFocusOutlines, mapEndpoints } from '~/utils';
import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/'; import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/';
import PopoverButtons from '~/components/Endpoints/PopoverButtons'; import PopoverButtons from '~/components/Chat/Input/PopoverButtons';
import DialogTemplate from '~/components/ui/DialogTemplate'; import DialogTemplate from '~/components/ui/DialogTemplate';
import { useSetIndexOptions, useLocalize } from '~/hooks'; import { useSetIndexOptions, useLocalize } from '~/hooks';
import { EndpointSettings } from '~/components/Endpoints'; import { EndpointSettings } from '~/components/Endpoints';
@ -90,6 +90,7 @@ const EditPresetDialog = ({
conversation={preset} conversation={preset}
setOption={setOption} setOption={setOption}
isPreset={true} isPreset={true}
isMultiChat={true}
className="h-full md:mb-4 md:h-[440px]" className="h-full md:mb-4 md:h-[440px]"
/> />
</div> </div>

View file

@ -1,5 +1,5 @@
import { useRef } from 'react'; import { useRef } from 'react';
import { useUpdateMessageMutation } from 'librechat-data-provider'; import { useUpdateMessageMutation, EModelEndpoint } from 'librechat-data-provider';
import Container from '~/components/Messages/Content/Container'; import Container from '~/components/Messages/Content/Container';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import type { TEditProps } from '~/common'; import type { TEditProps } from '~/common';
@ -18,6 +18,7 @@ const EditMessage = ({
const textEditor = useRef<HTMLDivElement | null>(null); const textEditor = useRef<HTMLDivElement | null>(null);
const { conversationId, parentMessageId, messageId } = message; const { conversationId, parentMessageId, messageId } = message;
const { endpoint } = conversation ?? { endpoint: null };
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? ''); const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
const localize = useLocalize(); const localize = useLocalize();
@ -94,7 +95,9 @@ const EditMessage = ({
<div className="mt-2 flex w-full justify-center text-center"> <div className="mt-2 flex w-full justify-center text-center">
<button <button
className="btn btn-primary relative mr-2" className="btn btn-primary relative mr-2"
disabled={isSubmitting} disabled={
isSubmitting || (endpoint === EModelEndpoint.google && !message.isCreatedByUser)
}
onClick={resubmitMessage} onClick={resubmitMessage}
> >
{localize('com_ui_save')} {'&'} {localize('com_ui_submit')} {localize('com_ui_save')} {'&'} {localize('com_ui_submit')}

View file

@ -1,11 +1,18 @@
import { EModelEndpoint } from 'librechat-data-provider'; import { EModelEndpoint } from 'librechat-data-provider';
import { Plugin, GPTIcon, AnthropicIcon, AzureMinimalIcon } from '~/components/svg'; import {
Plugin,
GPTIcon,
AnthropicIcon,
AzureMinimalIcon,
PaLMIcon,
CodeyIcon,
} from '~/components/svg';
import { useAuthContext } from '~/hooks/AuthContext'; import { useAuthContext } from '~/hooks/AuthContext';
import { IconProps } from '~/common'; import { IconProps } from '~/common';
import { cn } from '~/utils'; import { cn } from '~/utils';
const Icon: React.FC<IconProps> = (props) => { const Icon: React.FC<IconProps> = (props) => {
const { size = 30, isCreatedByUser, button, model = true, endpoint, error, jailbreak } = props; const { size = 30, isCreatedByUser, button, model = '', endpoint, error, jailbreak } = props;
const { user } = useAuthContext(); const { user } = useAuthContext();
@ -52,8 +59,12 @@ const Icon: React.FC<IconProps> = (props) => {
name: 'Plugins', name: 'Plugins',
}, },
[EModelEndpoint.google]: { [EModelEndpoint.google]: {
icon: <img src="/assets/google-palm.svg" alt="Palm Icon" />, icon: model?.includes('code') ? (
name: 'PaLM2', <CodeyIcon size={size * 0.75} />
) : (
<PaLMIcon size={size * 0.7} />
),
name: model?.includes('code') ? 'Codey' : 'PaLM2',
}, },
[EModelEndpoint.anthropic]: { [EModelEndpoint.anthropic]: {
icon: <AnthropicIcon size={size * 0.5555555555555556} />, icon: <AnthropicIcon size={size * 0.5555555555555556} />,

View file

@ -5,7 +5,7 @@ import {
LightningIcon, LightningIcon,
PluginMinimalIcon, PluginMinimalIcon,
BingAIMinimalIcon, BingAIMinimalIcon,
PaLMinimalIcon, GoogleMinimalIcon,
AnthropicIcon, AnthropicIcon,
} from '~/components/svg'; } from '~/components/svg';
import { cn } from '~/utils'; import { cn } from '~/utils';
@ -27,7 +27,7 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
}, },
[EModelEndpoint.openAI]: { icon: <OpenAIMinimalIcon />, name: props.chatGptLabel || 'ChatGPT' }, [EModelEndpoint.openAI]: { icon: <OpenAIMinimalIcon />, name: props.chatGptLabel || 'ChatGPT' },
[EModelEndpoint.gptPlugins]: { icon: <PluginMinimalIcon />, name: 'Plugins' }, [EModelEndpoint.gptPlugins]: { icon: <PluginMinimalIcon />, name: 'Plugins' },
[EModelEndpoint.google]: { icon: <PaLMinimalIcon />, name: props.modelLabel || 'PaLM2' }, [EModelEndpoint.google]: { icon: <GoogleMinimalIcon />, name: props.modelLabel || 'Google' },
[EModelEndpoint.anthropic]: { [EModelEndpoint.anthropic]: {
icon: <AnthropicIcon className="icon-md shrink-0 dark:text-white" />, icon: <AnthropicIcon className="icon-md shrink-0 dark:text-white" />,
name: props.modelLabel || 'Claude', name: props.modelLabel || 'Claude',

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { EModelEndpoint, endpointSettings } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common'; import type { TModelSelectProps } from '~/common';
import { ESide } from '~/common'; import { ESide } from '~/common';
import { import {
@ -31,7 +32,8 @@ export default function Settings({ conversation, setOption, models, readonly }:
const setTopK = setOption('topK'); const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens'); const setMaxOutputTokens = setOption('maxOutputTokens');
const codeChat = model?.startsWith('codechat-'); const isTextModel = !model?.includes('chat') && /code|text/.test(model ?? '');
const google = endpointSettings[EModelEndpoint.google];
return ( return (
<div className="grid grid-cols-5 gap-6"> <div className="grid grid-cols-5 gap-6">
@ -46,45 +48,41 @@ export default function Settings({ conversation, setOption, models, readonly }:
containerClassName="flex w-full resize-none" containerClassName="flex w-full resize-none"
/> />
</div> </div>
{!codeChat && ( <div className="grid w-full items-center gap-2">
<> <Label htmlFor="modelLabel" className="text-left text-sm font-medium">
<div className="grid w-full items-center gap-2"> {localize('com_endpoint_custom_name')}{' '}
<Label htmlFor="modelLabel" className="text-left text-sm font-medium"> <small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
{localize('com_endpoint_custom_name')}{' '} </Label>
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small> <Input
</Label> id="modelLabel"
<Input disabled={readonly}
id="modelLabel" value={modelLabel || ''}
disabled={readonly} onChange={(e) => setModelLabel(e.target.value ?? null)}
value={modelLabel || ''} placeholder={localize('com_endpoint_google_custom_name_placeholder')}
onChange={(e) => setModelLabel(e.target.value ?? null)} className={cn(
placeholder={localize('com_endpoint_google_custom_name_placeholder')} defaultTextProps,
className={cn( 'flex h-10 max-h-10 w-full resize-none px-3 py-2',
defaultTextProps, removeFocusOutlines,
'flex h-10 max-h-10 w-full resize-none px-3 py-2', )}
removeFocusOutlines, />
)} </div>
/> <div className="grid w-full items-center gap-2">
</div> <Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
<div className="grid w-full items-center gap-2"> {localize('com_endpoint_prompt_prefix')}{' '}
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium"> <small className="opacity-40">({localize('com_endpoint_default_blank')})</small>
{localize('com_endpoint_prompt_prefix')}{' '} </Label>
<small className="opacity-40">({localize('com_endpoint_default_blank')})</small> <TextareaAutosize
</Label> id="promptPrefix"
<TextareaAutosize disabled={readonly}
id="promptPrefix" value={promptPrefix || ''}
disabled={readonly} onChange={(e) => setPromptPrefix(e.target.value ?? null)}
value={promptPrefix || ''} placeholder={localize('com_endpoint_prompt_prefix_placeholder')}
onChange={(e) => setPromptPrefix(e.target.value ?? null)} className={cn(
placeholder={localize('com_endpoint_prompt_prefix_placeholder')} defaultTextProps,
className={cn( 'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2 ',
defaultTextProps, )}
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2 ', />
)} </div>
/>
</div>
</>
)}
</div> </div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2"> <div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
@ -92,16 +90,18 @@ export default function Settings({ conversation, setOption, models, readonly }:
<div className="flex justify-between"> <div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium"> <Label htmlFor="temp-int" className="text-left text-sm font-medium">
{localize('com_endpoint_temperature')}{' '} {localize('com_endpoint_temperature')}{' '}
<small className="opacity-40">({localize('com_endpoint_default')}: 0.2)</small> <small className="opacity-40">
({localize('com_endpoint_default')}: {google.temperature.default})
</small>
</Label> </Label>
<InputNumber <InputNumber
id="temp-int" id="temp-int"
disabled={readonly} disabled={readonly}
value={temperature} value={temperature}
onChange={(value) => setTemperature(value ?? 0.2)} onChange={(value) => setTemperature(value ?? google.temperature.default)}
max={1} max={google.temperature.max}
min={0} min={google.temperature.min}
step={0.01} step={google.temperature.step}
controls={false} controls={false}
className={cn( className={cn(
defaultTextProps, defaultTextProps,
@ -114,18 +114,18 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
<Slider <Slider
disabled={readonly} disabled={readonly}
value={[temperature ?? 0.2]} value={[temperature ?? google.temperature.default]}
onValueChange={(value) => setTemperature(value[0])} onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(0.2)} doubleClickHandler={() => setTemperature(google.temperature.default)}
max={1} max={google.temperature.max}
min={0} min={google.temperature.min}
step={0.01} step={google.temperature.step}
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={ESide.Left} /> <OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={ESide.Left} />
</HoverCard> </HoverCard>
{!codeChat && ( {!isTextModel && (
<> <>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
@ -133,17 +133,17 @@ export default function Settings({ conversation, setOption, models, readonly }:
<Label htmlFor="top-p-int" className="text-left text-sm font-medium"> <Label htmlFor="top-p-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_p')}{' '} {localize('com_endpoint_top_p')}{' '}
<small className="opacity-40"> <small className="opacity-40">
({localize('com_endpoint_default_with_num', '0.95')}) ({localize('com_endpoint_default_with_num', google.topP.default + '')})
</small> </small>
</Label> </Label>
<InputNumber <InputNumber
id="top-p-int" id="top-p-int"
disabled={readonly} disabled={readonly}
value={topP} value={topP}
onChange={(value) => setTopP(value ?? '0.95')} onChange={(value) => setTopP(value ?? google.topP.default)}
max={1} max={google.topP.max}
min={0} min={google.topP.min}
step={0.01} step={google.topP.step}
controls={false} controls={false}
className={cn( className={cn(
defaultTextProps, defaultTextProps,
@ -156,12 +156,12 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
<Slider <Slider
disabled={readonly} disabled={readonly}
value={[topP ?? 0.95]} value={[topP ?? google.topP.default]}
onValueChange={(value) => setTopP(value[0])} onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(0.95)} doubleClickHandler={() => setTopP(google.topP.default)}
max={1} max={google.topP.max}
min={0} min={google.topP.min}
step={0.01} step={google.topP.step}
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
@ -174,17 +174,17 @@ export default function Settings({ conversation, setOption, models, readonly }:
<Label htmlFor="top-k-int" className="text-left text-sm font-medium"> <Label htmlFor="top-k-int" className="text-left text-sm font-medium">
{localize('com_endpoint_top_k')}{' '} {localize('com_endpoint_top_k')}{' '}
<small className="opacity-40"> <small className="opacity-40">
({localize('com_endpoint_default_with_num', '40')}) ({localize('com_endpoint_default_with_num', google.topK.default + '')})
</small> </small>
</Label> </Label>
<InputNumber <InputNumber
id="top-k-int" id="top-k-int"
disabled={readonly} disabled={readonly}
value={topK} value={topK}
onChange={(value) => setTopK(value ?? 40)} onChange={(value) => setTopK(value ?? google.topK.default)}
max={40} max={google.topK.max}
min={1} min={google.topK.min}
step={0.01} step={google.topK.step}
controls={false} controls={false}
className={cn( className={cn(
defaultTextProps, defaultTextProps,
@ -197,12 +197,12 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
<Slider <Slider
disabled={readonly} disabled={readonly}
value={[topK ?? 40]} value={[topK ?? google.topK.default]}
onValueChange={(value) => setTopK(value[0])} onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(40)} doubleClickHandler={() => setTopK(google.topK.default)}
max={40} max={google.topK.max}
min={1} min={google.topK.min}
step={0.01} step={google.topK.step}
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
@ -216,17 +216,17 @@ export default function Settings({ conversation, setOption, models, readonly }:
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium"> <Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
{localize('com_endpoint_max_output_tokens')}{' '} {localize('com_endpoint_max_output_tokens')}{' '}
<small className="opacity-40"> <small className="opacity-40">
({localize('com_endpoint_default_with_num', '1024')}) ({localize('com_endpoint_default_with_num', google.maxOutputTokens.default + '')})
</small> </small>
</Label> </Label>
<InputNumber <InputNumber
id="max-tokens-int" id="max-tokens-int"
disabled={readonly} disabled={readonly}
value={maxOutputTokens} value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(value ?? 1024)} onChange={(value) => setMaxOutputTokens(value ?? google.maxOutputTokens.default)}
max={1024} max={google.maxOutputTokens.max}
min={1} min={google.maxOutputTokens.min}
step={1} step={google.maxOutputTokens.step}
controls={false} controls={false}
className={cn( className={cn(
defaultTextProps, defaultTextProps,
@ -239,12 +239,12 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
<Slider <Slider
disabled={readonly} disabled={readonly}
value={[maxOutputTokens ?? 1024]} value={[maxOutputTokens ?? google.maxOutputTokens.default]}
onValueChange={(value) => setMaxOutputTokens(value[0])} onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(1024)} doubleClickHandler={() => setMaxOutputTokens(google.maxOutputTokens.default)}
max={1024} max={google.maxOutputTokens.max}
min={1} min={google.maxOutputTokens.min}
step={1} step={google.maxOutputTokens.step}
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>

View file

@ -16,7 +16,7 @@ export default function GoogleView({ conversation, models, isPreset = false }) {
const { showExamples, isCodeChat } = optionSettings; const { showExamples, isCodeChat } = optionSettings;
return showExamples && !isCodeChat ? ( return showExamples && !isCodeChat ? (
<Examples <Examples
examples={examples ?? []} examples={examples ?? [{ input: { content: '' }, output: { content: '' } }]}
setExample={setExample} setExample={setExample}
addExample={addExample} addExample={addExample}
removeExample={removeExample} removeExample={removeExample}

View file

@ -0,0 +1,26 @@
import { cn } from '~/utils';
export default function CodeyIcon({
size = 25,
className = '',
}: {
size?: number;
className?: string;
}) {
return (
<svg
// width="100%"
// height="100%"
width={size}
height={size}
className={cn('dark:fill-white', className)}
viewBox="0 0 18 18"
preserveAspectRatio="xMidYMid meet"
focusable="false"
>
<path
d="M2 4.006C2 2.898 2.897 2 4.006 2h9.988C15.102 2 16 2.897 16 4.006v9.988A2.005 2.005 0 0 1 13.994 16H4.006A2.005 2.005 0 0 1 2 13.994V4.006zM13.992 9l.003-.003L10.997 6 9.75 7.247 11.503 9 9.75 10.753 10.997 12l2.997-2.997L13.992 9zm-9.99 0L4 8.997 6.997 6l1.247 1.247L6.492 9l1.753 1.753L6.997 12 4 9.003 4.003 9z"
fillRule="evenodd"
/>
</svg>
);
}

View file

@ -0,0 +1,15 @@
import { cn } from '~/utils';
export default function GoogleMinimalIcon({ className = '' }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width="800px"
height="800px"
viewBox="0 0 512 512"
className={cn('h-4 w-4', className)}
>
<path d="M473.16,221.48l-2.26-9.59H262.46v88.22H387c-12.93,61.4-72.93,93.72-121.94,93.72-35.66,0-73.25-15-98.13-39.11a140.08,140.08,0,0,1-41.8-98.88c0-37.16,16.7-74.33,41-98.78s61-38.13,97.49-38.13c41.79,0,71.74,22.19,82.94,32.31l62.69-62.36C390.86,72.72,340.34,32,261.6,32h0c-60.75,0-119,23.27-161.58,65.71C58,139.5,36.25,199.93,36.25,256S56.83,369.48,97.55,411.6C141.06,456.52,202.68,480,266.13,480c57.73,0,112.45-22.62,151.45-63.66,38.34-40.4,58.17-96.3,58.17-154.9C475.75,236.77,473.27,222.12,473.16,221.48Z" />
</svg>
);
}

View file

@ -0,0 +1,50 @@
export default function PaLMIcon({
size = 25,
className = '',
}: {
size?: number;
className?: string;
}) {
return (
<svg
// width="100%"
// height="100%"
width={size}
height={size}
className={className}
viewBox="0 0 19 17"
fill="none"
preserveAspectRatio="xMidYMid meet"
focusable="false"
>
<path
d="M9.62674 16.2202H9.7049C10.4225 16.2202 11.0016 15.6412 11.0016 14.9236V4.04224H8.33008V14.92C8.33008 15.6376 8.90914 16.2202 9.62674 16.2202Z"
fill="#F9AB00"
/>
<path
d="M14.6819 8.02813C13.3249 6.66752 11.2964 6.39398 9.66577 7.2004L15.0585 12.5931C15.2823 12.8169 15.6624 12.7281 15.7583 12.4297C16.2308 10.927 15.8756 9.21822 14.6819 8.02813Z"
fill="#5BB974"
/>
<path
d="M4.64953 8.02813C6.00659 6.66752 8.03507 6.39398 9.66567 7.2004L4.27297 12.5931C4.04916 12.8169 3.66904 12.7281 3.57312 12.4297C3.10064 10.927 3.45589 9.21822 4.64953 8.02813Z"
fill="#129EAF"
/>
<path
d="M14.284 3.84326C12.1383 3.84326 10.3159 5.25005 9.66577 7.20038H18.1918C18.5399 7.20038 18.7744 6.83092 18.6145 6.5183C17.8081 4.93033 16.1704 3.84326 14.284 3.84326Z"
fill="#AF5CF7"
/>
<path
d="M10.5574 1.55901C9.04053 3.07593 8.74567 5.36019 9.66577 7.20039L15.6944 1.17179C15.943 0.923113 15.8436 0.496814 15.5132 0.390239C13.8151 -0.1604 11.8896 0.226822 10.5574 1.55901Z"
fill="#FF8BCB"
/>
<path
d="M8.77408 1.55901C10.291 3.07593 10.5859 5.36019 9.66576 7.20039L3.63716 1.17179C3.38848 0.923113 3.48795 0.496814 3.81833 0.390239C5.51643 -0.1604 7.44189 0.226822 8.77408 1.55901Z"
fill="#FA7B17"
/>
<path
d="M5.04752 3.84326C7.19323 3.84326 9.01566 5.25005 9.66577 7.20038H1.13976C0.791616 7.20038 0.55715 6.83092 0.717013 6.5183C1.52343 4.93033 3.16114 3.84326 5.04752 3.84326Z"
fill="#4285F4"
/>
</svg>
);
}

View file

@ -1,6 +1,5 @@
import React from 'react'; import { cn } from '~/utils';
export default function PaLMinimalIcon({ className = '' }: { className?: string }) {
export default function PaLMinimalIcon() {
return ( return (
<svg <svg
stroke="currentColor" stroke="currentColor"
@ -9,7 +8,7 @@ export default function PaLMinimalIcon() {
viewBox="0 0 32 32" viewBox="0 0 32 32"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
className="h-4 w-4" className={cn('h-4 w-4', className)}
height="1em" height="1em"
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -34,5 +34,8 @@ export { default as ChatGPTMinimalIcon } from './ChatGPTMinimalIcon';
export { default as PluginMinimalIcon } from './PluginMinimalIcon'; export { default as PluginMinimalIcon } from './PluginMinimalIcon';
export { default as BingAIMinimalIcon } from './BingAIMinimalIcon'; export { default as BingAIMinimalIcon } from './BingAIMinimalIcon';
export { default as PaLMinimalIcon } from './PaLMinimalIcon'; export { default as PaLMinimalIcon } from './PaLMinimalIcon';
export { default as PaLMIcon } from './PaLMIcon';
export { default as CodeyIcon } from './CodeyIcon';
export { default as GoogleMinimalIcon } from './GoogleMinimalIcon';
export { default as AnthropicMinimalIcon } from './AnthropicMinimalIcon'; export { default as AnthropicMinimalIcon } from './AnthropicMinimalIcon';
export { default as SendMessageIcon } from './SendMessageIcon'; export { default as SendMessageIcon } from './SendMessageIcon';

View file

@ -22,12 +22,12 @@ export default function useGenerations({
const { error, messageId, searchResult, finish_reason, isCreatedByUser } = message ?? {}; const { error, messageId, searchResult, finish_reason, isCreatedByUser } = message ?? {};
const isEditableEndpoint = !![ const isEditableEndpoint = !![
EModelEndpoint.azureOpenAI,
EModelEndpoint.openAI, EModelEndpoint.openAI,
EModelEndpoint.google,
EModelEndpoint.assistant, EModelEndpoint.assistant,
EModelEndpoint.anthropic,
EModelEndpoint.gptPlugins, EModelEndpoint.gptPlugins,
EModelEndpoint.anthropic, EModelEndpoint.azureOpenAI,
EModelEndpoint.anthropic,
].find((e) => e === endpoint); ].find((e) => e === endpoint);
const continueSupported = const continueSupported =

View file

@ -18,12 +18,12 @@ export default function useGenerationsByLatest({
}: TUseGenerations) { }: TUseGenerations) {
const { error, messageId, searchResult, finish_reason, isCreatedByUser } = message ?? {}; const { error, messageId, searchResult, finish_reason, isCreatedByUser } = message ?? {};
const isEditableEndpoint = !![ const isEditableEndpoint = !![
EModelEndpoint.azureOpenAI,
EModelEndpoint.openAI, EModelEndpoint.openAI,
EModelEndpoint.google,
EModelEndpoint.assistant, EModelEndpoint.assistant,
EModelEndpoint.anthropic,
EModelEndpoint.gptPlugins, EModelEndpoint.gptPlugins,
EModelEndpoint.anthropic, EModelEndpoint.azureOpenAI,
EModelEndpoint.anthropic,
].find((e) => e === endpoint); ].find((e) => e === endpoint);
const continueSupported = const continueSupported =

View file

@ -133,7 +133,7 @@ export default {
'Top-k يغير كيفية اختيار النموذج للرموز للإخراج. top-k من 1 يعني أن الرمز المحدد هو الأكثر احتمالية بين جميع الرموز في مفردات النموذج (يسمى أيضًا الترميز الجشعي)، بينما top-k من 3 يعني أن الرمز التالي يتم اختياره من بين الرموز الثلاثة الأكثر احتمالية (باستخدام الحرارة).', 'Top-k يغير كيفية اختيار النموذج للرموز للإخراج. top-k من 1 يعني أن الرمز المحدد هو الأكثر احتمالية بين جميع الرموز في مفردات النموذج (يسمى أيضًا الترميز الجشعي)، بينما top-k من 3 يعني أن الرمز التالي يتم اختياره من بين الرموز الثلاثة الأكثر احتمالية (باستخدام الحرارة).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'الحد الأقصى لعدد الرموز التي يمكن إنشاؤها في الرد. حدد قيمة أقل للردود الأقصر وقيمة أعلى للردود الأطول.', 'الحد الأقصى لعدد الرموز التي يمكن إنشاؤها في الرد. حدد قيمة أقل للردود الأقصر وقيمة أعلى للردود الأطول.',
com_endpoint_google_custom_name_placeholder: 'قم بتعيين اسم مخصص لـ PaLM2', com_endpoint_google_custom_name_placeholder: 'قم بتعيين اسم مخصص لـ Google',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'قم بتعيين تعليمات مخصصة أو سياق. يتم تجاهله إذا كان فارغًا.', 'قم بتعيين تعليمات مخصصة أو سياق. يتم تجاهله إذا كان فارغًا.',
com_endpoint_custom_name: 'اسم مخصص', com_endpoint_custom_name: 'اسم مخصص',

View file

@ -133,7 +133,7 @@ export default {
'Top-k muda como o modelo seleciona tokens para a saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).', 'Top-k muda como o modelo seleciona tokens para a saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Número máximo de tokens que podem ser gerados na resposta. Especifique um valor menor para respostas mais curtas e um valor maior para respostas mais longas.', 'Número máximo de tokens que podem ser gerados na resposta. Especifique um valor menor para respostas mais curtas e um valor maior para respostas mais longas.',
com_endpoint_google_custom_name_placeholder: 'Defina um nome personalizado para o PaLM2', com_endpoint_google_custom_name_placeholder: 'Defina um nome personalizado para o Google',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Defina instruções ou contexto personalizados. Ignorado se vazio.', 'Defina instruções ou contexto personalizados. Ignorado se vazio.',
com_endpoint_custom_name: 'Nome Personalizado', com_endpoint_custom_name: 'Nome Personalizado',

View file

@ -107,7 +107,7 @@ export default {
'Top-k ändert, wie das Modell Token für die Ausgabe auswählt. Ein Top-k von 1 bedeutet, dass das ausgewählte Token das wahrscheinlichste unter allen Token im Vokabular des Modells ist (auch gierige Dekodierung genannt), während ein Top-k von 3 bedeutet, dass das nächste Token aus den drei wahrscheinlichsten Token ausgewählt wird (unter Verwendung der Temperatur).', 'Top-k ändert, wie das Modell Token für die Ausgabe auswählt. Ein Top-k von 1 bedeutet, dass das ausgewählte Token das wahrscheinlichste unter allen Token im Vokabular des Modells ist (auch gierige Dekodierung genannt), während ein Top-k von 3 bedeutet, dass das nächste Token aus den drei wahrscheinlichsten Token ausgewählt wird (unter Verwendung der Temperatur).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Maximale Anzahl von Token, die in der Antwort erzeugt werden können. Geben Sie einen niedrigeren Wert für kürzere Antworten und einen höheren Wert für längere Antworten an.', 'Maximale Anzahl von Token, die in der Antwort erzeugt werden können. Geben Sie einen niedrigeren Wert für kürzere Antworten und einen höheren Wert für längere Antworten an.',
com_endpoint_google_custom_name_placeholder: 'Benutzerdefinierter Name für PaLM2', com_endpoint_google_custom_name_placeholder: 'Benutzerdefinierter Name für Google',
com_endpoint_google_prompt_prefix_placeholder: com_endpoint_google_prompt_prefix_placeholder:
'Benutzerdefinierte Anweisungen oder Kontext festlegen. Wird ignoriert, wenn leer.', 'Benutzerdefinierte Anweisungen oder Kontext festlegen. Wird ignoriert, wenn leer.',
com_endpoint_custom_name: 'Benutzerdefinierter Name', com_endpoint_custom_name: 'Benutzerdefinierter Name',

View file

@ -137,7 +137,7 @@ export default {
'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).', 'Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model\'s vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
' Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.', ' Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.',
com_endpoint_google_custom_name_placeholder: 'Set a custom name for PaLM2', com_endpoint_google_custom_name_placeholder: 'Set a custom name for Google',
com_endpoint_prompt_prefix_placeholder: 'Set custom instructions or context. Ignored if empty.', com_endpoint_prompt_prefix_placeholder: 'Set custom instructions or context. Ignored if empty.',
com_endpoint_custom_name: 'Custom Name', com_endpoint_custom_name: 'Custom Name',
com_endpoint_prompt_prefix: 'Prompt Prefix', com_endpoint_prompt_prefix: 'Prompt Prefix',

View file

@ -138,7 +138,7 @@ export default {
'Establece instrucciones o contexto personalizado. Ignorado si está vacío.', 'Establece instrucciones o contexto personalizado. Ignorado si está vacío.',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Número máximo de tokens que se pueden generar en la respuesta. Especifica un valor menor para respuestas más cortas y un valor mayor para respuestas más largas.', 'Número máximo de tokens que se pueden generar en la respuesta. Especifica un valor menor para respuestas más cortas y un valor mayor para respuestas más largas.',
com_endpoint_google_custom_name_placeholder: 'Establece un nombre personalizado para PaLM2', com_endpoint_google_custom_name_placeholder: 'Establece un nombre personalizado para Google',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Establece instrucciones o contexto personalizados. Se ignora si está vacío.', 'Establece instrucciones o contexto personalizados. Se ignora si está vacío.',
com_endpoint_custom_name: 'Nombre personalizado', com_endpoint_custom_name: 'Nombre personalizado',

View file

@ -140,7 +140,7 @@ export default {
'Top-k change la façon dont le modèle sélectionne les jetons pour la sortie. Un top-k de 1 signifie que le jeton sélectionné est le plus probable parmi tous les jetons du vocabulaire du modèle (également appelé décodage glouton), tandis qu\'un top-k de 3 signifie que le jeton suivant est sélectionné parmi les 3 jetons les plus probables (en utilisant la température).', 'Top-k change la façon dont le modèle sélectionne les jetons pour la sortie. Un top-k de 1 signifie que le jeton sélectionné est le plus probable parmi tous les jetons du vocabulaire du modèle (également appelé décodage glouton), tandis qu\'un top-k de 3 signifie que le jeton suivant est sélectionné parmi les 3 jetons les plus probables (en utilisant la température).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Nombre maximum de jetons qui peuvent être générés dans la réponse. Spécifiez une valeur plus faible pour des réponses plus courtes et une valeur plus élevée pour des réponses plus longues.', 'Nombre maximum de jetons qui peuvent être générés dans la réponse. Spécifiez une valeur plus faible pour des réponses plus courtes et une valeur plus élevée pour des réponses plus longues.',
com_endpoint_google_custom_name_placeholder: 'Définir un nom personnalisé pour PaLM2', com_endpoint_google_custom_name_placeholder: 'Définir un nom personnalisé pour Google',
com_endpoint_google_prompt_prefix_placeholder: com_endpoint_google_prompt_prefix_placeholder:
'Définir des instructions ou un contexte personnalisés. Ignoré si vide.', 'Définir des instructions ou un contexte personnalisés. Ignoré si vide.',
com_endpoint_custom_name: 'Nom personnalisé', com_endpoint_custom_name: 'Nom personnalisé',

View file

@ -135,7 +135,7 @@ export default {
'Top-k cambia come il modello seleziona i token per l\'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (anche chiamato decodifica greedy), mentre un top-k di 3 significa che il token successivo è selezionato tra i 3 token più probabili (usando temperature).', 'Top-k cambia come il modello seleziona i token per l\'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (anche chiamato decodifica greedy), mentre un top-k di 3 significa che il token successivo è selezionato tra i 3 token più probabili (usando temperature).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Numero massimo di token che possono essere generati nella risposta. Specifica un valore più basso per risposte più corte e un valore più alto per risposte più lunghe.', 'Numero massimo di token che possono essere generati nella risposta. Specifica un valore più basso per risposte più corte e un valore più alto per risposte più lunghe.',
com_endpoint_google_custom_name_placeholder: 'Imposta un nome personalizzato per PaLM2', com_endpoint_google_custom_name_placeholder: 'Imposta un nome personalizzato per Google',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Imposta istruzioni o contesto personalizzati. Ignorato se vuoto.', 'Imposta istruzioni o contesto personalizzati. Ignorato se vuoto.',
com_endpoint_custom_name: 'Nome personalizzato', com_endpoint_custom_name: 'Nome personalizzato',

View file

@ -133,7 +133,7 @@ export default {
'Top-k はモデルがトークンをどのように選択して出力するかを変更します。top-kが1の場合はモデルの語彙に含まれるすべてのトークンの中で最も確率が高い1つが選択されます(greedy decodingと呼ばれている)。top-kが3の場合は上位3つのトークンの中から選択されます。(temperatureを使用)', 'Top-k はモデルがトークンをどのように選択して出力するかを変更します。top-kが1の場合はモデルの語彙に含まれるすべてのトークンの中で最も確率が高い1つが選択されます(greedy decodingと呼ばれている)。top-kが3の場合は上位3つのトークンの中から選択されます。(temperatureを使用)',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
' 生成されるレスポンスの最大トークン数。短いレスポンスには低い値を、長いレスポンスには高い値を指定します。', ' 生成されるレスポンスの最大トークン数。短いレスポンスには低い値を、長いレスポンスには高い値を指定します。',
com_endpoint_google_custom_name_placeholder: 'PaLM2のカスタム名を設定する', com_endpoint_google_custom_name_placeholder: 'Googleのカスタム名を設定する',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'custom instructions か context を設定する。空の場合は無視されます。', 'custom instructions か context を設定する。空の場合は無視されます。',
com_endpoint_custom_name: 'プリセット名', com_endpoint_custom_name: 'プリセット名',

View file

@ -124,7 +124,7 @@ export default {
'Top-k는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. top-k가 1인 경우 모델의 어휘 중 가장 확률이 높은 토큰이 선택됩니다(greedy decoding). top-k가 3인 경우 다음 토큰은 가장 확률이 높은 3개의 토큰 중에서 선택됩니다(temperature 사용).', 'Top-k는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. top-k가 1인 경우 모델의 어휘 중 가장 확률이 높은 토큰이 선택됩니다(greedy decoding). top-k가 3인 경우 다음 토큰은 가장 확률이 높은 3개의 토큰 중에서 선택됩니다(temperature 사용).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'응답에서 생성할 수 있는 최대 토큰 수입니다. 짧은 응답에는 낮은 값을, 긴 응답에는 높은 값을 지정하세요.', '응답에서 생성할 수 있는 최대 토큰 수입니다. 짧은 응답에는 낮은 값을, 긴 응답에는 높은 값을 지정하세요.',
com_endpoint_google_custom_name_placeholder: 'PaLM2에 대한 사용자 정의 이름 설정', com_endpoint_google_custom_name_placeholder: 'Google에 대한 사용자 정의 이름 설정',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'사용자 정의 지시사항 또는 컨텍스트를 설정하세요. 비어 있으면 무시됩니다.', '사용자 정의 지시사항 또는 컨텍스트를 설정하세요. 비어 있으면 무시됩니다.',
com_endpoint_custom_name: '사용자 정의 이름', com_endpoint_custom_name: '사용자 정의 이름',

View file

@ -134,7 +134,7 @@ export default {
'Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel \'greedy decoding\' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).', 'Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel \'greedy decoding\' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
' Maximum aantal tokens dat kan worden gegenereerd in de reactie. Geef een lagere waarde op voor kortere reacties en een hogere waarde voor langere reacties.', ' Maximum aantal tokens dat kan worden gegenereerd in de reactie. Geef een lagere waarde op voor kortere reacties en een hogere waarde voor langere reacties.',
com_endpoint_google_custom_name_placeholder: 'Stel een aangepaste naam in voor PaLM2', com_endpoint_google_custom_name_placeholder: 'Stel een aangepaste naam in voor Google',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Stel aangepaste instructies of context in. Wordt genegeerd indien leeg.', 'Stel aangepaste instructies of context in. Wordt genegeerd indien leeg.',
com_endpoint_custom_name: 'Aangepaste naam', com_endpoint_custom_name: 'Aangepaste naam',

View file

@ -106,7 +106,7 @@ export default {
'Top-k wpływa na sposób, w jaki model wybiera tokeny do wygenerowania odpowiedzi. Top-k 1 oznacza, że wybrany token jest najbardziej prawdopodobny spośród wszystkich tokenów w słowniku modelu (nazywane też dekodowaniem zachłannym), podczas gdy top-k 3 oznacza, że następny token jest wybierany spośród 3 najbardziej prawdopodobnych tokenów (z uwzględnieniem temperatury).', 'Top-k wpływa na sposób, w jaki model wybiera tokeny do wygenerowania odpowiedzi. Top-k 1 oznacza, że wybrany token jest najbardziej prawdopodobny spośród wszystkich tokenów w słowniku modelu (nazywane też dekodowaniem zachłannym), podczas gdy top-k 3 oznacza, że następny token jest wybierany spośród 3 najbardziej prawdopodobnych tokenów (z uwzględnieniem temperatury).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Maksymalna liczba tokenów, które mogą być wygenerowane w odpowiedzi. Wybierz niższą wartość dla krótszych odpowiedzi i wyższą wartość dla dłuższych odpowiedzi.', 'Maksymalna liczba tokenów, które mogą być wygenerowane w odpowiedzi. Wybierz niższą wartość dla krótszych odpowiedzi i wyższą wartość dla dłuższych odpowiedzi.',
com_endpoint_google_custom_name_placeholder: 'Ustaw niestandardową nazwę dla PaLM2', com_endpoint_google_custom_name_placeholder: 'Ustaw niestandardową nazwę dla Google',
com_endpoint_google_prompt_prefix_placeholder: com_endpoint_google_prompt_prefix_placeholder:
'Ustaw niestandardowe instrukcje lub kontekst. Jeśli puste, zostanie zignorowane.', 'Ustaw niestandardowe instrukcje lub kontekst. Jeśli puste, zostanie zignorowane.',
com_endpoint_custom_name: 'Niestandardowa nazwa', com_endpoint_custom_name: 'Niestandardowa nazwa',

View file

@ -120,7 +120,7 @@ export default {
'Top K изменяет то, как модель выбирает токены для вывода. Top K равное 1 означает, что выбирается наиболее вероятный токен из всего словаря модели (так называемое жадное декодирование), а Top K равное 3 означает, что следующий токен выбирается из трех наиболее вероятных токенов (с использованием температуры).', 'Top K изменяет то, как модель выбирает токены для вывода. Top K равное 1 означает, что выбирается наиболее вероятный токен из всего словаря модели (так называемое жадное декодирование), а Top K равное 3 означает, что следующий токен выбирается из трех наиболее вероятных токенов (с использованием температуры).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Максимальное количество токенов, которые могут быть сгенерированы в ответе. Укажите меньшее значение для более коротких ответов и большее значение для более длинных ответов.', 'Максимальное количество токенов, которые могут быть сгенерированы в ответе. Укажите меньшее значение для более коротких ответов и большее значение для более длинных ответов.',
com_endpoint_google_custom_name_placeholder: 'Установите пользовательское имя для PaLM2', com_endpoint_google_custom_name_placeholder: 'Установите пользовательское имя для Google',
com_endpoint_google_prompt_prefix_placeholder: com_endpoint_google_prompt_prefix_placeholder:
'Установите пользовательские инструкции или контекст. Игнорируется, если пусто.', 'Установите пользовательские инструкции или контекст. Игнорируется, если пусто.',
com_endpoint_custom_name: 'Пользовательское имя', com_endpoint_custom_name: 'Пользовательское имя',
@ -180,7 +180,7 @@ export default {
com_endpoint_view_options: 'Просмотреть параметры', com_endpoint_view_options: 'Просмотреть параметры',
com_endpoint_save_convo_as_preset: 'Сохранить разговор как предустановку', com_endpoint_save_convo_as_preset: 'Сохранить разговор как предустановку',
com_endpoint_presets_clear_warning: com_endpoint_presets_clear_warning:
'Вы уверены, что хотите очистить все предустановки? Эти действия необратимы, и восстановление невозможно.', 'Вы уверены, что хотите очистить все предустановки? Эти действия необратимы, и восстановление невозможно.',
com_endpoint_presets: 'предустановки', com_endpoint_presets: 'предустановки',
com_endpoint_my_preset: 'Моя предустановка', com_endpoint_my_preset: 'Моя предустановка',
com_endpoint_config_key: 'Установить ключ API', com_endpoint_config_key: 'Установить ключ API',

View file

@ -129,7 +129,7 @@ export default {
'Top-k ändrar hur modellen väljer tokens för utdata. Ett top-k av 1 innebär att den valda token är den mest sannolika bland alla tokens i modellens vokabulär (kallas också girig avkodning), medan ett top-k av 3 innebär att nästa token väljs bland de 3 mest sannolika tokens (med temperatur).', // Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature). 'Top-k ändrar hur modellen väljer tokens för utdata. Ett top-k av 1 innebär att den valda token är den mest sannolika bland alla tokens i modellens vokabulär (kallas också girig avkodning), medan ett top-k av 3 innebär att nästa token väljs bland de 3 mest sannolika tokens (med temperatur).', // Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Maximalt antal tokens som kan genereras i svaret. Ange ett lägre värde för kortare svar och ett högre värde för längre svar.', // Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses. 'Maximalt antal tokens som kan genereras i svaret. Ange ett lägre värde för kortare svar och ett högre värde för längre svar.', // Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.
com_endpoint_google_custom_name_placeholder: 'Ange ett anpassat namn för PaLM2', // Set a custom name for PaLM2 com_endpoint_google_custom_name_placeholder: 'Ange ett anpassat namn för Google', // Set a custom name for Google
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Ange anpassade instruktioner eller kontext. Ignoreras om tom.', // Set custom instructions or context. Ignored if empty. 'Ange anpassade instruktioner eller kontext. Ignoreras om tom.', // Set custom instructions or context. Ignored if empty.
com_endpoint_custom_name: 'Anpassat namn', // Custom Name com_endpoint_custom_name: 'Anpassat namn', // Custom Name

View file

@ -136,7 +136,7 @@ export default {
'Top-k, modelin çıkış için token seçme şeklini değiştirir. 1 top-k, seçilen tokenın modelin kelime dağarcığındaki tüm tokenlar arasında en olası olduğu anlamına gelir (ayrıca aç gözlü kod çözme denir), 3 top-k ise bir sonraki tokenın 3 en olası token arasından seçildiği anlamına gelir (sıcaklık kullanılarak).', 'Top-k, modelin çıkış için token seçme şeklini değiştirir. 1 top-k, seçilen tokenın modelin kelime dağarcığındaki tüm tokenlar arasında en olası olduğu anlamına gelir (ayrıca aç gözlü kod çözme denir), 3 top-k ise bir sonraki tokenın 3 en olası token arasından seçildiği anlamına gelir (sıcaklık kullanılarak).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Yanıtta üretilebilecek maksimum token sayısı. Daha kısa yanıtlar için daha düşük bir değer belirtin ve daha uzun yanıtlar için daha yüksek bir değer belirtin.', 'Yanıtta üretilebilecek maksimum token sayısı. Daha kısa yanıtlar için daha düşük bir değer belirtin ve daha uzun yanıtlar için daha yüksek bir değer belirtin.',
com_endpoint_google_custom_name_placeholder: 'PaLM2 için özel bir ad belirleyin', com_endpoint_google_custom_name_placeholder: 'Google için özel bir ad belirleyin',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Özel talimatları veya bağlamı ayarlayın. Boşsa göz ardı edilir.', 'Özel talimatları veya bağlamı ayarlayın. Boşsa göz ardı edilir.',
com_endpoint_custom_name: 'Özel Ad', com_endpoint_custom_name: 'Özel Ad',
@ -181,9 +181,12 @@ export default {
com_endpoint_preset_delete_confirm: 'Bu ön ayarı silmek istediğinizden emin misiniz?', com_endpoint_preset_delete_confirm: 'Bu ön ayarı silmek istediğinizden emin misiniz?',
com_endpoint_preset_clear_all_confirm: 'Tüm ön ayarlarınızı silmek istediğinizden emin misiniz?', com_endpoint_preset_clear_all_confirm: 'Tüm ön ayarlarınızı silmek istediğinizden emin misiniz?',
com_endpoint_preset_import: 'Ön Ayar İçe Aktarıldı!', com_endpoint_preset_import: 'Ön Ayar İçe Aktarıldı!',
com_endpoint_preset_import_error: 'Ön ayarınız içe aktarılırken bir hata oluştu. Lütfen tekrar deneyin.', com_endpoint_preset_import_error:
com_endpoint_preset_save_error: 'Ön ayarınız kaydedilirken bir hata oluştu. Lütfen tekrar deneyin.', 'Ön ayarınız içe aktarılırken bir hata oluştu. Lütfen tekrar deneyin.',
com_endpoint_preset_delete_error: 'Ön ayarınız silinirken bir hata oluştu. Lütfen tekrar deneyin.', com_endpoint_preset_save_error:
'Ön ayarınız kaydedilirken bir hata oluştu. Lütfen tekrar deneyin.',
com_endpoint_preset_delete_error:
'Ön ayarınız silinirken bir hata oluştu. Lütfen tekrar deneyin.',
com_endpoint_preset_default_removed: 'artık varsayılan ön ayar değildir.', com_endpoint_preset_default_removed: 'artık varsayılan ön ayar değildir.',
com_endpoint_preset_default_item: 'Varsayılan:', com_endpoint_preset_default_item: 'Varsayılan:',
com_endpoint_preset_default_none: 'Varsayılan ön ayar etkin değil.', com_endpoint_preset_default_none: 'Varsayılan ön ayar etkin değil.',

View file

@ -134,7 +134,7 @@ export default {
'Top-k thay đổi cách mô hình chọn mã thông báo để xuất. Top-k là 1 có nghĩa là mã thông báo được chọn là phổ biến nhất trong tất cả các mã thông báo trong bảng từ vựng của mô hình (còn được gọi là giải mã tham lam), trong khi top-k là 3 có nghĩa là mã thông báo tiếp theo được chọn từ giữa 3 mã thông báo phổ biến nhất (sử dụng nhiệt độ).', 'Top-k thay đổi cách mô hình chọn mã thông báo để xuất. Top-k là 1 có nghĩa là mã thông báo được chọn là phổ biến nhất trong tất cả các mã thông báo trong bảng từ vựng của mô hình (còn được gọi là giải mã tham lam), trong khi top-k là 3 có nghĩa là mã thông báo tiếp theo được chọn từ giữa 3 mã thông báo phổ biến nhất (sử dụng nhiệt độ).',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'Số mã thông báo tối đa có thể được tạo ra trong phản hồi. Chỉ định một giá trị thấp hơn cho các phản hồi ngắn hơn và một giá trị cao hơn cho các phản hồi dài hơn.', 'Số mã thông báo tối đa có thể được tạo ra trong phản hồi. Chỉ định một giá trị thấp hơn cho các phản hồi ngắn hơn và một giá trị cao hơn cho các phản hồi dài hơn.',
com_endpoint_google_custom_name_placeholder: 'Đặt tên tùy chỉnh cho PaLM2', com_endpoint_google_custom_name_placeholder: 'Đặt tên tùy chỉnh cho Google',
com_endpoint_prompt_prefix_placeholder: com_endpoint_prompt_prefix_placeholder:
'Đặt hướng dẫn hoặc ngữ cảnh tùy chỉnh. Bỏ qua nếu trống.', 'Đặt hướng dẫn hoặc ngữ cảnh tùy chỉnh. Bỏ qua nếu trống.',
com_endpoint_custom_name: 'Tên tùy chỉnh', com_endpoint_custom_name: 'Tên tùy chỉnh',

View file

@ -126,7 +126,7 @@ export default {
'Top-k 会改变模型选择输出词的方式。top-k为1意味着所选词是模型词汇中概率最大的也称为贪心解码而top-k为3意味着下一个词是从3个概率最大的词中选出的使用随机性。', 'Top-k 会改变模型选择输出词的方式。top-k为1意味着所选词是模型词汇中概率最大的也称为贪心解码而top-k为3意味着下一个词是从3个概率最大的词中选出的使用随机性。',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
' 响应生成中可以使用的最大词元数。指定较低的值会得到更短的响应,而指定较高的值则会得到更长的响应。', ' 响应生成中可以使用的最大词元数。指定较低的值会得到更短的响应,而指定较高的值则会得到更长的响应。',
com_endpoint_google_custom_name_placeholder: '为PaLM2设置一个名称', com_endpoint_google_custom_name_placeholder: '为Google设置一个名称',
com_endpoint_prompt_prefix_placeholder: '自定义提示词和上下文,默认为空', com_endpoint_prompt_prefix_placeholder: '自定义提示词和上下文,默认为空',
com_endpoint_custom_name: '自定义名称', com_endpoint_custom_name: '自定义名称',
com_endpoint_prompt_prefix: '对话前缀', com_endpoint_prompt_prefix: '对话前缀',

View file

@ -127,7 +127,7 @@ export default {
'Top-k 調整模型如何選取輸出的 token。當 Top-k 設為 1 時,模型會選取在其詞彙庫中機率最高的 token 進行輸出(這也被稱為貪婪解碼)。相對地,當 Top-k 設為 3 時,模型會從機率最高的三個 token 中選取下一個輸出 token這會涉及到所謂的「溫度」調整', 'Top-k 調整模型如何選取輸出的 token。當 Top-k 設為 1 時,模型會選取在其詞彙庫中機率最高的 token 進行輸出(這也被稱為貪婪解碼)。相對地,當 Top-k 設為 3 時,模型會從機率最高的三個 token 中選取下一個輸出 token這會涉及到所謂的「溫度」調整',
com_endpoint_google_maxoutputtokens: com_endpoint_google_maxoutputtokens:
'設定回應中可生成的最大 token 數。若希望回應簡短,請設定較低的數值;若需較長的回應,則設定較高的數值。', '設定回應中可生成的最大 token 數。若希望回應簡短,請設定較低的數值;若需較長的回應,則設定較高的數值。',
com_endpoint_google_custom_name_placeholder: '為 PaLM2 設定自定義名稱', com_endpoint_google_custom_name_placeholder: '為 Google 設定自定義名稱',
com_endpoint_prompt_prefix_placeholder: '設定自定義提示或前後文。如果為空則忽略。', com_endpoint_prompt_prefix_placeholder: '設定自定義提示或前後文。如果為空則忽略。',
com_endpoint_custom_name: '自定義名稱', com_endpoint_custom_name: '自定義名稱',
com_endpoint_prompt_prefix: '提示起始字串', com_endpoint_prompt_prefix: '提示起始字串',

View file

@ -36,7 +36,7 @@ You will need to fill these values:
| BINGAI_TOKEN | `user_provided` | | BINGAI_TOKEN | `user_provided` |
| CHATGPT_TOKEN | `user_provided` | | CHATGPT_TOKEN | `user_provided` |
| ANTHROPIC_API_KEY | `user_provided` | | ANTHROPIC_API_KEY | `user_provided` |
| PALM_KEY | `user_provided` | | GOOGLE_KEY | `user_provided` |
| CREDS_KEY | * see bellow | | CREDS_KEY | * see bellow |
| CREDS_IV | * see bellow | | CREDS_IV | * see bellow |
| JWT_SECRET | * see bellow | | JWT_SECRET | * see bellow |

View file

@ -53,7 +53,7 @@ Also:
| JWT_REFRESH_SECRET | secret | | JWT_REFRESH_SECRET | secret |
| JWT_SECRET | secret | | JWT_SECRET | secret |
| OPENAI_API_KEY | user_provided | | OPENAI_API_KEY | user_provided |
| PALM_KEY | user_provided | | GOOGLE_KEY | user_provided |
| PORT | 3080 | | PORT | 3080 |
| SESSION_EXPIRY | (1000 * 60 * 60 * 24) * 7 | | SESSION_EXPIRY | (1000 * 60 * 60 * 24) * 7 |

View file

@ -6,7 +6,7 @@ The plugins endpoint opens the door to prompting LLMs in new ways other than tra
The first step is using chain-of-thought prompting & ["agency"](https://zapier.com/blog/ai-agent/) for using plugins/tools in a fashion mimicing the official ChatGPT Plugins feature. The first step is using chain-of-thought prompting & ["agency"](https://zapier.com/blog/ai-agent/) for using plugins/tools in a fashion mimicing the official ChatGPT Plugins feature.
More than this, you can use this endpoint for changing your conversation settings mid-conversation. Unlike the official ChatGPT site and all other endpoints, you can switch models, presets, and settings mid-convo, even when you have no plugins selected. This is useful if you first want a creative response from GPT-4, and then a deterministic, lower cost response from GPT-3. Soon, you will be able to use PaLM2 and HuggingFace models, all in this endpoint in the same modular manner. More than this, you can use this endpoint for changing your conversation settings mid-conversation. Unlike the official ChatGPT site and all other endpoints, you can switch models, presets, and settings mid-convo, even when you have no plugins selected. This is useful if you first want a creative response from GPT-4, and then a deterministic, lower cost response from GPT-3. Soon, you will be able to use Google, HuggingFace, local models, all in this or a similar endpoint in the same modular manner.
### Roadmap: ### Roadmap:
- More plugins and advanced plugin usage (ongoing) - More plugins and advanced plugin usage (ongoing)

View file

@ -36,7 +36,7 @@
- 🌎 Multilingual UI: - 🌎 Multilingual UI:
- English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro, Русский - English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro, Русский
- 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands - 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands
- 🤖 AI model selection: OpenAI API, Azure, BingAI, ChatGPT Browser, PaLM2, Anthropic (Claude), Plugins - 🤖 AI model selection: OpenAI API, Azure, BingAI, ChatGPT, Google Vertex AI, Anthropic (Claude), Plugins
- 💾 Create, Save, & Share Custom Presets - 💾 Create, Save, & Share Custom Presets
- 🔄 Edit, Resubmit, and Continue messages with conversation branching - 🔄 Edit, Resubmit, and Continue messages with conversation branching
- 📤 Export conversations as screenshots, markdown, text, json. - 📤 Export conversations as screenshots, markdown, text, json.

View file

@ -46,27 +46,35 @@ To get your Bing Access Token, you have a few options:
- Go to [https://console.anthropic.com/account/keys](https://console.anthropic.com/account/keys) and get your api key - Go to [https://console.anthropic.com/account/keys](https://console.anthropic.com/account/keys) and get your api key
- add it to `ANTHROPIC_API_KEY=` in the `.env` file - add it to `ANTHROPIC_API_KEY=` in the `.env` file
## Google's PaLM 2 ## Google LLMs
To setup PaLM 2 (via Google Cloud Vertex AI API), you need to: To setup Google LLMs (via Google Cloud Vertex AI), first, signup for Google Cloud: https://cloud.google.com/
### Enable the Vertex AI API on Google Cloud: You can usually get $300 starting credit, which makes this option free for 90 days.
- Go to [https://console.cloud.google.com/vertex-ai](https://console.cloud.google.com/vertex-ai)
### Once signed up, Enable the Vertex AI API on Google Cloud:
- Go to [Vertex AI page on Google Cloud console](https://console.cloud.google.com/vertex-ai)
- Click on "Enable API" if prompted - Click on "Enable API" if prompted
### Create a Service Account: ### Create a Service Account with Vertex AI role:
- Go to [https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1) - **[Click here to create a Service Account](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1)**
- Select or create a project - **Select or create a project**
- Enter a service account name and description - ### Enter a service account ID (required), name and description are optional
- Click on "Create and Continue" to give at least the "Vertex AI User" role - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/0c5cd177-029b-44fa-a398-a794aeb09de6)
- Click on "Done" - ### Click on "Create and Continue" to give at least the "Vertex AI User" role
### Create a JSON key, rename as 'auth.json' and save it in /api/data/: - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/22d3a080-e71e-446e-8485-bcc5bf558dbb)
- Go back to [https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts) - **Click on "Continue/Done"**
- Select your service account ### Create a JSON key to Save in Project Directory:
- Click on "Keys" - **Go back to [the Service Accounts page](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts)**
- Click on "Add Key" and then "Create new key" - **Select your service account**
- Choose JSON as the key type and click on "Create" - ### Click on "Keys"
- Download the key file and rename it as 'auth.json' - ![image](https://github.com/danny-avila/LibreChat/assets/110412045/735a7bbe-25a6-4b4c-9bb5-e0d8aa91be3d)
- Save it in `/api/data/` - ### Click on "Add Key" and then "Create new key"
- ![image](https://github.com/danny-avila/LibreChat/assets/110412045/cfbb20d3-94a8-4cd1-ac39-f9cd8c2fceaa)
- **Choose JSON as the key type and click on "Create"**
- **Download the key file and rename it as 'auth.json'**
- **Save it within the project directory, in `/api/data/`**
- ![image](https://github.com/danny-avila/LibreChat/assets/110412045/f5b8bcb5-1b20-4751-81a1-d3757a4b3f2f)
## Azure OpenAI ## Azure OpenAI

View file

@ -249,7 +249,7 @@ OPENROUTER_API_KEY=
Follow these instruction to setup: [Google PaLM 2](./apis_and_tokens.md#googles-palm-2) Follow these instruction to setup: [Google PaLM 2](./apis_and_tokens.md#googles-palm-2)
```bash ```bash
PALM_KEY=user_provided GOOGLE_KEY=user_provided
GOOGLE_REVERSE_PROXY= GOOGLE_REVERSE_PROXY=
``` ```

View file

@ -29,10 +29,44 @@ export const alternateName = {
[EModelEndpoint.bingAI]: 'Bing', [EModelEndpoint.bingAI]: 'Bing',
[EModelEndpoint.chatGPTBrowser]: 'ChatGPT', [EModelEndpoint.chatGPTBrowser]: 'ChatGPT',
[EModelEndpoint.gptPlugins]: 'Plugins', [EModelEndpoint.gptPlugins]: 'Plugins',
[EModelEndpoint.google]: 'PaLM', [EModelEndpoint.google]: 'Google',
[EModelEndpoint.anthropic]: 'Anthropic', [EModelEndpoint.anthropic]: 'Anthropic',
}; };
export const endpointSettings = {
[EModelEndpoint.google]: {
model: {
default: 'chat-bison',
},
maxOutputTokens: {
min: 1,
max: 2048,
step: 1,
default: 1024,
},
temperature: {
min: 0,
max: 1,
step: 0.01,
default: 0.2,
},
topP: {
min: 0,
max: 1,
step: 0.01,
default: 0.8,
},
topK: {
min: 1,
max: 40,
step: 0.01,
default: 40,
},
},
};
const google = endpointSettings[EModelEndpoint.google];
export const EndpointURLs: { [key in EModelEndpoint]: string } = { export const EndpointURLs: { [key in EModelEndpoint]: string } = {
[EModelEndpoint.azureOpenAI]: '/api/ask/azureOpenAI', [EModelEndpoint.azureOpenAI]: '/api/ask/azureOpenAI',
[EModelEndpoint.openAI]: '/api/ask/openAI', [EModelEndpoint.openAI]: '/api/ask/openAI',
@ -275,22 +309,24 @@ export const googleSchema = tConversationSchema
}) })
.transform((obj) => ({ .transform((obj) => ({
...obj, ...obj,
model: obj.model ?? 'chat-bison', model: obj.model ?? google.model.default,
modelLabel: obj.modelLabel ?? null, modelLabel: obj.modelLabel ?? null,
promptPrefix: obj.promptPrefix ?? null, promptPrefix: obj.promptPrefix ?? null,
temperature: obj.temperature ?? 0.2, examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }],
maxOutputTokens: obj.maxOutputTokens ?? 1024, temperature: obj.temperature ?? google.temperature.default,
topP: obj.topP ?? 0.95, maxOutputTokens: obj.maxOutputTokens ?? google.maxOutputTokens.default,
topK: obj.topK ?? 40, topP: obj.topP ?? google.topP.default,
topK: obj.topK ?? google.topK.default,
})) }))
.catch(() => ({ .catch(() => ({
model: 'chat-bison', model: google.model.default,
modelLabel: null, modelLabel: null,
promptPrefix: null, promptPrefix: null,
temperature: 0.2, examples: [{ input: { content: '' }, output: { content: '' } }],
maxOutputTokens: 1024, temperature: google.temperature.default,
topP: 0.95, maxOutputTokens: google.maxOutputTokens.default,
topK: 40, topP: google.topP.default,
topK: google.topK.default,
})); }));
export const bingAISchema = tConversationSchema export const bingAISchema = tConversationSchema
@ -539,7 +575,13 @@ export const getResponseSender = (endpointOption: TEndpointOption): string => {
} }
if (endpoint === EModelEndpoint.google) { if (endpoint === EModelEndpoint.google) {
return modelLabel ?? 'PaLM2'; if (modelLabel) {
return modelLabel;
} else if (model && model.includes('code')) {
return 'Codey';
}
return 'PaLM2';
} }
return ''; return '';
@ -590,19 +632,19 @@ export const compactGoogleSchema = tConversationSchema
}) })
.transform((obj) => { .transform((obj) => {
const newObj: Partial<TConversation> = { ...obj }; const newObj: Partial<TConversation> = { ...obj };
if (newObj.model === 'chat-bison') { if (newObj.model === google.model.default) {
delete newObj.model; delete newObj.model;
} }
if (newObj.temperature === 0.2) { if (newObj.temperature === google.temperature.default) {
delete newObj.temperature; delete newObj.temperature;
} }
if (newObj.maxOutputTokens === 1024) { if (newObj.maxOutputTokens === google.maxOutputTokens.default) {
delete newObj.maxOutputTokens; delete newObj.maxOutputTokens;
} }
if (newObj.topP === 0.95) { if (newObj.topP === google.topP.default) {
delete newObj.topP; delete newObj.topP;
} }
if (newObj.topK === 40) { if (newObj.topK === google.topK.default) {
delete newObj.topK; delete newObj.topK;
} }