diff --git a/api/.env.example b/api/.env.example index df7d59bc7d..a66dbff088 100644 --- a/api/.env.example +++ b/api/.env.example @@ -89,6 +89,33 @@ CHATGPT_MODELS=text-davinci-002-render-sha,text-davinci-002-render-paid,gpt-4 # By default, the server will use the node-chatgpt-api recommended proxy (a third party server). # CHATGPT_REVERSE_PROXY= +########################## +# PaLM (Google) Endpoint: +########################## + +# PaLM 2 Client (via Google Cloud Vertex AI API) +# Steps: + # Enable the Vertex AI API on Google Cloud: + # https://console.cloud.google.com/vertex-ai + # Create a Service Account: + # https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1 + # Make sure to click '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/. +# Alternatively + # Uncomment below PALM_KEY and set as "user_provided" to allow the user to provide a Service Account key JSON from the UI. + # They will follow the steps above except for renaming the file. +# Leave blank or omit to disable this endpoint + +# PALM_KEY="user_provided" + +# In case you need a reverse proxy for this endpoint: +# GOOGLE_REVERSE_PROXY= + +########################## +# Proxy: To be Used by all endpoints +########################## +PROXY= + ########################## # Search: ########################## diff --git a/api/app/google/GoogleClient.js b/api/app/google/GoogleClient.js new file mode 100644 index 0000000000..d6f0eff3ad --- /dev/null +++ b/api/app/google/GoogleClient.js @@ -0,0 +1,390 @@ +const crypto = require('crypto'); +const TextStream = require('../stream'); +const { google } = require('googleapis'); +const { Agent, ProxyAgent } = require('undici'); +const { getMessages, saveMessage, saveConvo } = require('../../models'); +const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('@dqbd/tiktoken'); + +const tokenizersCache = {}; + +class GoogleAgent { + constructor(credentials, options = {}) { + this.client_email = credentials.client_email; + this.project_id = credentials.project_id; + this.private_key = credentials.private_key; + this.setOptions(options); + this.currentDateString = new Date().toLocaleDateString('en-us', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + } + + constructUrl() { + return `https://us-central1-aiplatform.googleapis.com/v1/projects/${this.project_id}/locations/us-central1/publishers/google/models/${this.modelOptions.model}:predict`; + } + + setOptions(options) { + if (this.options && !this.options.replaceOptions) { + // nested options aren't spread properly, so we need to do this manually + this.options.modelOptions = { + ...this.options.modelOptions, + ...options.modelOptions + }; + delete options.modelOptions; + // now we can merge options + this.options = { + ...this.options, + ...options + }; + } else { + this.options = options; + } + + this.options.examples = this.options.examples.filter( + obj => obj.input.content !== '' && obj.output.content !== '' + ); + + const modelOptions = this.options.modelOptions || {}; + this.modelOptions = { + ...modelOptions, + // set some good defaults (check for undefined in some cases because they may be 0) + model: modelOptions.model || 'chat-bison', + temperature: typeof modelOptions.temperature === 'undefined' ? 0.2 : modelOptions.temperature, // 0 - 1, 0.2 is recommended + topP: typeof modelOptions.topP === 'undefined' ? 0.95 : modelOptions.topP, // 0 - 1, default: 0.95 + topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK // 1-40, default: 40 + // stop: modelOptions.stop // no stop method for now + }; + + this.isChatModel = this.modelOptions.model.startsWith('chat-'); + const { isChatModel } = this; + this.isTextModel = this.modelOptions.model.startsWith('text-'); + const { isTextModel } = this; + + this.maxContextTokens = this.options.maxContextTokens || (isTextModel ? 8000 : 4096); + // The max prompt tokens is determined by the max context tokens minus the max response tokens. + // Earlier messages will be dropped until the prompt is within the limit. + this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1024; + this.maxPromptTokens = this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens; + + if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) { + throw new Error( + `maxPromptTokens + maxOutputTokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${ + this.maxPromptTokens + this.maxResponseTokens + }) must be less than or equal to maxContextTokens (${this.maxContextTokens})` + ); + } + + this.userLabel = this.options.userLabel || 'User'; + this.modelLabel = this.options.modelLabel || 'Assistant'; + + if (isChatModel) { + // Use these faux tokens to help the AI understand the context since we are building the chat log ourselves. + // Trying to use "<|im_start|>" causes the AI to still generate "<" or "<|" at the end sometimes for some reason, + // without tripping the stop sequences, so I'm using "||>" instead. + this.startToken = '||>'; + this.endToken = ''; + this.gptEncoder = this.constructor.getTokenizer('cl100k_base'); + } else if (isTextModel) { + this.startToken = '<|im_start|>'; + this.endToken = '<|im_end|>'; + this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, { + '<|im_start|>': 100264, + '<|im_end|>': 100265 + }); + } else { + // Previously I was trying to use "<|endoftext|>" but there seems to be some bug with OpenAI's token counting + // system that causes only the first "<|endoftext|>" to be counted as 1 token, and the rest are not treated + // as a single token. So we're using this instead. + this.startToken = '||>'; + this.endToken = ''; + try { + this.gptEncoder = this.constructor.getTokenizer(this.modelOptions.model, true); + } catch { + this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true); + } + } + + if (!this.modelOptions.stop) { + const stopTokens = [this.startToken]; + if (this.endToken && this.endToken !== this.startToken) { + stopTokens.push(this.endToken); + } + stopTokens.push(`\n${this.userLabel}:`); + stopTokens.push('<|diff_marker|>'); + // I chose not to do one for `modelLabel` because I've never seen it happen + this.modelOptions.stop = stopTokens; + } + + if (this.options.reverseProxyUrl) { + this.completionsUrl = this.options.reverseProxyUrl; + } else { + this.completionsUrl = this.constructUrl(); + } + + return this; + } + + static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) { + if (tokenizersCache[encoding]) { + return tokenizersCache[encoding]; + } + let tokenizer; + if (isModelName) { + tokenizer = encodingForModel(encoding, extendSpecialTokens); + } else { + tokenizer = getEncoding(encoding, extendSpecialTokens); + } + tokenizersCache[encoding] = tokenizer; + return tokenizer; + } + + async getClient() { + const scopes = ['https://www.googleapis.com/auth/cloud-platform']; + const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes); + + jwtClient.authorize((err) => { + if (err) { + console.log(err); + throw err; + } + }); + + return jwtClient; + } + + buildPayload(input, { messages = [] }) { + let payload = { + instances: [ + { + messages: [...messages, { author: this.userLabel, content: input }] + } + ], + parameters: this.options.modelOptions + }; + + if (this.options.promptPrefix) { + payload.instances[0].context = this.options.promptPrefix; + } + + if (this.options.examples.length > 0) { + payload.instances[0].examples = this.options.examples; + } + + if (this.isTextModel) { + payload.instances = [ + { + prompt: input + } + ]; + } + + if (this.options.debug) { + console.debug('buildPayload'); + console.dir(payload, { depth: null }); + } + + return payload; + } + + async getCompletion(input, messages = [], abortController = null) { + if (!abortController) { + abortController = new AbortController(); + } + const { debug } = this.options; + const url = this.completionsUrl; + if (debug) { + console.debug(); + console.debug(url); + console.debug(this.modelOptions); + console.debug(); + } + const opts = { + method: 'POST', + agent: new Agent({ + bodyTimeout: 0, + headersTimeout: 0 + }), + signal: abortController.signal + }; + + if (this.options.proxy) { + opts.agent = new ProxyAgent(this.options.proxy); + } + + const client = await this.getClient(); + const payload = this.buildPayload(input, { messages }); + const res = await client.request({ url, method: 'POST', data: payload }); + console.dir(res.data, { depth: null }); + return res.data; + } + + async loadHistory(conversationId, parentMessageId = null) { + if (this.options.debug) { + console.debug('Loading history for conversation', conversationId, parentMessageId); + } + + if (!parentMessageId) { + return []; + } + + const messages = (await getMessages({ conversationId })) || []; + + if (messages.length === 0) { + this.currentMessages = []; + return []; + } + + const orderedMessages = this.constructor.getMessagesForConversation(messages, parentMessageId); + return orderedMessages.map((message) => { + return { + author: message.isCreatedByUser ? this.userLabel : this.modelLabel, + content: message.content + }; + }); + } + + async saveMessageToDatabase(message, user = null) { + await saveMessage({ ...message, unfinished: false }); + await saveConvo(user, { + conversationId: message.conversationId, + endpoint: 'google', + ...this.modelOptions + }); + } + + async sendMessage(message, opts = {}) { + if (opts && typeof opts === 'object') { + this.setOptions(opts); + } + console.log('sendMessage', message, opts); + + const user = opts.user || null; + const conversationId = opts.conversationId || crypto.randomUUID(); + const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000'; + const userMessageId = crypto.randomUUID(); + const responseMessageId = crypto.randomUUID(); + const messages = await this.loadHistory(conversationId, this.options?.parentMessageId); + + const userMessage = { + messageId: userMessageId, + parentMessageId, + conversationId, + sender: 'User', + text: message, + isCreatedByUser: true + }; + + if (typeof opts?.getIds === 'function') { + opts.getIds({ + userMessage, + conversationId, + responseMessageId + }); + } + + console.log('userMessage', userMessage); + + await this.saveMessageToDatabase(userMessage, user); + let reply = ''; + let blocked = false; + try { + const result = await this.getCompletion(message, messages, opts.abortController); + 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) { + console.debug('result'); + console.debug(result); + } + } catch (err) { + console.error(err); + } + + if (this.options.debug) { + console.debug('options'); + console.debug(this.options); + } + + if (!blocked) { + const textStream = new TextStream(reply, { delay: 0.5 }); + await textStream.processTextStream(opts.onProgress); + } + + const responseMessage = { + messageId: responseMessageId, + conversationId, + parentMessageId: userMessage.messageId, + sender: 'PaLM2', + text: reply, + error: blocked, + isCreatedByUser: false + }; + + await this.saveMessageToDatabase(responseMessage, user); + return responseMessage; + } + + getTokenCount(text) { + return this.gptEncoder.encode(text, 'all').length; + } + + /** + * Algorithm adapted from "6. Counting tokens for chat API calls" of + * https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb + * + * An additional 2 tokens need to be added for metadata after all messages have been counted. + * + * @param {*} message + */ + getTokenCountForMessage(message) { + // Map each property of the message to the number of tokens it contains + const propertyTokenCounts = Object.entries(message).map(([key, value]) => { + // Count the number of tokens in the property value + const numTokens = this.getTokenCount(value); + + // Subtract 1 token if the property key is 'name' + const adjustment = key === 'name' ? 1 : 0; + return numTokens - adjustment; + }); + + // Sum the number of tokens in all properties and add 4 for metadata + return propertyTokenCounts.reduce((a, b) => a + b, 4); + } + + /** + * Iterate through messages, building an array based on the parentMessageId. + * Each message has an id and a parentMessageId. The parentMessageId is the id of the message that this message is a reply to. + * @param messages + * @param parentMessageId + * @returns {*[]} An array containing the messages in the order they should be displayed, starting with the root message. + */ + static getMessagesForConversation(messages, parentMessageId) { + const orderedMessages = []; + let currentMessageId = parentMessageId; + while (currentMessageId) { + // eslint-disable-next-line no-loop-func + const message = messages.find(m => m.messageId === currentMessageId); + if (!message) { + break; + } + orderedMessages.unshift(message); + currentMessageId = message.parentMessageId; + } + + if (orderedMessages.length === 0) { + return []; + } + + return orderedMessages.map(msg => ({ + isCreatedByUser: msg.isCreatedByUser, + content: msg.text + })); + } +} + +module.exports = GoogleAgent; diff --git a/api/app/stream.js b/api/app/stream.js new file mode 100644 index 0000000000..17a3a61b9d --- /dev/null +++ b/api/app/stream.js @@ -0,0 +1,62 @@ +const { Readable } = require('stream'); + +class TextStream extends Readable { + constructor(text, options = {}) { + super(options); + this.text = text; + this.currentIndex = 0; + this.delay = options.delay || 20; // Time in milliseconds + } + + _read() { + const minChunkSize = 2; + const maxChunkSize = 4; + const { delay } = this; + + if (this.currentIndex < this.text.length) { + setTimeout(() => { + const remainingChars = this.text.length - this.currentIndex; + const chunkSize = Math.min( + this.randomInt(minChunkSize, maxChunkSize + 1), + remainingChars + ); + + const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize); + this.push(chunk); + this.currentIndex += chunkSize; + }, delay); + } else { + this.push(null); // signal end of data + } + } + + randomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; + } + + async processTextStream(onProgressCallback) { + const streamPromise = new Promise((resolve, reject) => { + this.on('data', (chunk) => { + onProgressCallback(chunk.toString()); + }); + + this.on('end', () => { + console.log('Stream ended'); + resolve(); + }); + + this.on('error', (err) => { + reject(err); + }); + }); + + try { + await streamPromise; + } catch (err) { + console.error('Error processing text stream:', err); + // Handle the error appropriately, e.g., return an error message or throw an error + } + } +} + +module.exports = TextStream; diff --git a/api/models/schema/conversationPreset.js b/api/models/schema/conversationPreset.js index f64d6658cd..5438f750e3 100644 --- a/api/models/schema/conversationPreset.js +++ b/api/models/schema/conversationPreset.js @@ -17,6 +17,12 @@ module.exports = { default: null, required: false }, + // for google only + modelLabel: { + type: String, + default: null, + required: false + }, promptPrefix: { type: String, default: null, @@ -32,6 +38,22 @@ module.exports = { default: 1, required: false }, + // for google only + topP: { + type: Number, + default: 0.95, + required: false + }, + topK: { + type: Number, + default: 40, + required: false + }, + maxOutputTokens: { + type: Number, + default: 1024, + required: false + }, presence_penalty: { type: Number, default: 0, diff --git a/api/models/schema/convoSchema.js b/api/models/schema/convoSchema.js index d73a0bde0d..02f5c93a16 100644 --- a/api/models/schema/convoSchema.js +++ b/api/models/schema/convoSchema.js @@ -20,6 +20,8 @@ const convoSchema = mongoose.Schema( default: null }, messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }], + // google only + examples: [{ type: mongoose.Schema.Types.Mixed }], ...conversationPreset, // for bingAI only jailbreakConversationId: { diff --git a/api/models/schema/presetSchema.js b/api/models/schema/presetSchema.js index 35a2e1c579..dc50bd317d 100644 --- a/api/models/schema/presetSchema.js +++ b/api/models/schema/presetSchema.js @@ -17,6 +17,8 @@ const presetSchema = mongoose.Schema( type: String, default: null }, + // google only + examples: [{ type: mongoose.Schema.Types.Mixed }], ...conversationPreset }, { timestamps: true } diff --git a/api/package-lock.json b/api/package-lock.json index f72c0ae337..8b775f472f 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -22,6 +22,7 @@ "dotenv": "^16.0.3", "eslint": "^8.36.0", "express": "^4.18.2", + "googleapis": "^118.0.0", "handlebars": "^4.7.7", "html": "^1.0.0", "joi": "^14.3.1", @@ -1998,6 +1999,14 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2081,6 +2090,14 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3017,6 +3034,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -3109,6 +3131,11 @@ "node": ">=6" } }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "node_modules/fast-uri": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", @@ -3445,6 +3472,32 @@ "node": ">=8" } }, + "node_modules/gaxios": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", + "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -3529,6 +3582,94 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.8.0.tgz", + "integrity": "sha512-0iJn7IDqObDG5Tu9Tn2WemmJ31ksEa96IyK0J0OZCpTh6CrC6FrattwKX87h3qKVuprCJpdOGKc1Xi8V0kMh8Q==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.2.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis": { + "version": "118.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-118.0.0.tgz", + "integrity": "sha512-Ny6zJOGn5P/YDT6GQbJU6K0lSzEu4Yuxnsn45ZgBIeSQ1RM0FolEjUToLXquZd89DU9wUfqA5XYHPEctk1TFWg==", + "dependencies": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.4.tgz", + "integrity": "sha512-m4ErxGE8unR1z0VajT6AYk3s6a9gIMM6EkDZfkPnES8joeOlEtFEJeF8IyZkb0tjPXkktUfYrE4b3Li1DNyOwA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3539,6 +3680,38 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -4025,6 +4198,14 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4486,6 +4667,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/nodemailer": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", @@ -5903,6 +6092,11 @@ "punycode": "^2.1.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", @@ -7901,6 +8095,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7960,6 +8159,11 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, + "bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -8644,6 +8848,11 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -8728,6 +8937,11 @@ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==" }, + "fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "fast-uri": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", @@ -8983,6 +9197,26 @@ } } }, + "gaxios": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", + "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + } + }, + "gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, "get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -9039,6 +9273,80 @@ } } }, + "google-auth-library": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.8.0.tgz", + "integrity": "sha512-0iJn7IDqObDG5Tu9Tn2WemmJ31ksEa96IyK0J0OZCpTh6CrC6FrattwKX87h3qKVuprCJpdOGKc1Xi8V0kMh8Q==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.2.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "requires": { + "node-forge": "^1.3.1" + } + }, + "googleapis": { + "version": "118.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-118.0.0.tgz", + "integrity": "sha512-Ny6zJOGn5P/YDT6GQbJU6K0lSzEu4Yuxnsn45ZgBIeSQ1RM0FolEjUToLXquZd89DU9wUfqA5XYHPEctk1TFWg==", + "requires": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + } + }, + "googleapis-common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.4.tgz", + "integrity": "sha512-m4ErxGE8unR1z0VajT6AYk3s6a9gIMM6EkDZfkPnES8joeOlEtFEJeF8IyZkb0tjPXkktUfYrE4b3Li1DNyOwA==", + "requires": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + } + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -9049,6 +9357,37 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "requires": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, "handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -9387,6 +9726,14 @@ "argparse": "^2.0.1" } }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -9747,6 +10094,11 @@ } } }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, "nodemailer": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", @@ -10802,6 +11154,11 @@ "punycode": "^2.1.0" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", diff --git a/api/package.json b/api/package.json index 5123f7ef4e..6743f197bb 100644 --- a/api/package.json +++ b/api/package.json @@ -32,6 +32,7 @@ "dotenv": "^16.0.3", "eslint": "^8.36.0", "express": "^4.18.2", + "googleapis": "^118.0.0", "handlebars": "^4.7.7", "html": "^1.0.0", "joi": "^14.3.1", diff --git a/api/server/routes/ask/askGoogle.js b/api/server/routes/ask/askGoogle.js new file mode 100644 index 0000000000..195814dff8 --- /dev/null +++ b/api/server/routes/ask/askGoogle.js @@ -0,0 +1,156 @@ +const express = require('express'); +const router = express.Router(); +const { titleConvo } = require('../../../app/'); +const GoogleClient = require('../../../app/google/GoogleClient'); +const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); +const { handleError, sendMessage, createOnProgress } = require('./handlers'); +const requireJwtAuth = require('../../../middleware/requireJwtAuth'); + +router.post('/', requireJwtAuth, async (req, res) => { + const { endpoint, text, parentMessageId, conversationId } = 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 + const endpointOption = { + examples: req.body?.examples ?? [{ input: { content: '' }, output: { content: '' } }], + promptPrefix: req.body?.promptPrefix ?? null, + token: req.body?.token ?? 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']; + if (availableModels.find(model => model === endpointOption.modelOptions.model) === undefined) { + return handleError(res, { text: `Illegal request: model` }); + } + + // 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 }) => { + res.writeHead(200, { + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'Access-Control-Allow-Origin': '*', + 'X-Accel-Buffering': 'no' + }); + let userMessage; + let userMessageId; + let responseMessageId; + let lastSavedTimestamp = 0; + + try { + const getIds = (data) => { + userMessage = data.userMessage; + userMessageId = userMessage.messageId; + responseMessageId = data.responseMessageId; + if (!conversationId) { + conversationId = data.conversationId; + } + }; + + const { onProgress: progressCallback } = createOnProgress({ + onProgress: ({ text: partialText }) => { + const currentTimestamp = Date.now(); + if (currentTimestamp - lastSavedTimestamp > 500) { + lastSavedTimestamp = currentTimestamp; + saveMessage({ + messageId: responseMessageId, + sender: 'PaLM2', + conversationId, + parentMessageId: userMessageId, + text: partialText, + unfinished: true, + cancelled: false, + error: false + }); + } + } + }); + + const abortController = new AbortController(); + + let key; + if (endpointOption.token) { + key = JSON.parse(endpointOption.token); + delete endpointOption.token; + console.log('Using service account key provided by User for PaLM models'); + } + + try { + if (!key) { + 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, { + getIds, + user: req.user.id, + parentMessageId, + conversationId, + onProgress: progressCallback.call(null, { res, text, parentMessageId: userMessageId }), + abortController + }); + + await saveMessage(response); + sendMessage(res, { + title: await getConvoTitle(req.user.id, conversationId), + final: true, + conversation: await getConvo(req.user.id, conversationId), + requestMessage: userMessage, + responseMessage: response + }); + res.end(); + + if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + const title = await titleConvo({ text, response }); + await saveConvo(req.user.id, { + conversationId: conversationId, + title + }); + } + } 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); + handleError(res, errorMessage); + } +}; + +module.exports = router; diff --git a/api/server/routes/ask/index.js b/api/server/routes/ask/index.js index 78288591f0..d339206898 100644 --- a/api/server/routes/ask/index.js +++ b/api/server/routes/ask/index.js @@ -2,11 +2,13 @@ const express = require('express'); const router = express.Router(); // const askAzureOpenAI = require('./askAzureOpenAI';) const askOpenAI = require('./askOpenAI'); +const askGoogle = require('./askGoogle'); const askBingAI = require('./askBingAI'); const askChatGPTBrowser = require('./askChatGPTBrowser'); // router.use('/azureOpenAI', askAzureOpenAI); router.use('/openAI', askOpenAI); +router.use('/google', askGoogle); router.use('/bingAI', askBingAI); router.use('/chatGPTBrowser', askChatGPTBrowser); diff --git a/api/server/routes/endpoints.js b/api/server/routes/endpoints.js index 31b9957268..ef05db86ff 100644 --- a/api/server/routes/endpoints.js +++ b/api/server/routes/endpoints.js @@ -15,9 +15,33 @@ const getChatGPTBrowserModels = () => { return models; }; -router.get('/', function (req, res) { +let i = 0; +router.get('/', async function (req, res) { + let key, palmUser; + try { + key = require('../../data/auth.json'); + } catch (e) { + if (i === 0) { + console.log("No 'auth.json' file (service account key) found in /api/data/ for PaLM models"); + i++; + } + } + + if (process.env.PALM_KEY === 'user_provided') { + palmUser = true; + if (i <= 1) { + console.log('User will provide key for PaLM models'); + i++; + } + } + + const google = + key || palmUser ? { userProvide: palmUser, availableModels: ['chat-bison', 'text-bison'] } : false; const azureOpenAI = !!process.env.AZURE_OPENAI_KEY; - const openAI = process.env.OPENAI_KEY || process.env.AZURE_OPENAI_API_KEY ? { availableModels: getOpenAIModels() } : false; + const openAI = + process.env.OPENAI_KEY || process.env.AZURE_OPENAI_API_KEY + ? { availableModels: getOpenAIModels() } + : false; const bingAI = process.env.BINGAI_TOKEN ? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' } : false; @@ -28,7 +52,7 @@ router.get('/', function (req, res) { } : false; - res.send(JSON.stringify({ azureOpenAI, openAI, bingAI, chatGPTBrowser })); + res.send(JSON.stringify({ azureOpenAI, openAI, google, bingAI, chatGPTBrowser })); }); module.exports = { router, getOpenAIModels, getChatGPTBrowserModels }; diff --git a/client/public/assets/palm.png b/client/public/assets/palm.png new file mode 100644 index 0000000000..3488a71457 Binary files /dev/null and b/client/public/assets/palm.png differ diff --git a/client/src/components/Endpoints/BingAI/Settings.jsx b/client/src/components/Endpoints/BingAI/Settings.jsx index 07c28dccd7..701ff6a74a 100644 --- a/client/src/components/Endpoints/BingAI/Settings.jsx +++ b/client/src/components/Endpoints/BingAI/Settings.jsx @@ -40,7 +40,7 @@ function Settings(props) { return ( - <> +
@@ -141,7 +141,7 @@ function Settings(props) { )}
- +
); } diff --git a/client/src/components/Endpoints/EditPresetDialog.jsx b/client/src/components/Endpoints/EditPresetDialog.jsx index c9d618dbd2..0e2ff90d6f 100644 --- a/client/src/components/Endpoints/EditPresetDialog.jsx +++ b/client/src/components/Endpoints/EditPresetDialog.jsx @@ -1,4 +1,6 @@ import React, { useEffect, useState } from 'react'; +import Examples from './Google/Examples.jsx'; +import MessagesSquared from '~/components/svg/MessagesSquared.jsx'; import { useSetRecoilState, useRecoilValue } from 'recoil'; import filenamify from 'filenamify'; import axios from 'axios'; @@ -7,6 +9,7 @@ import DialogTemplate from '../ui/DialogTemplate'; import { Dialog, DialogClose, DialogButton } from '../ui/Dialog.tsx'; import { Input } from '../ui/Input.tsx'; import { Label } from '../ui/Label.tsx'; +import { Button } from '../ui/Button.tsx'; import Dropdown from '../ui/Dropdown'; import { cn } from '~/utils/'; import cleanupPreset from '~/utils/cleanupPreset'; @@ -18,11 +21,14 @@ import store from '~/store'; const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => { // const [title, setTitle] = useState('My Preset'); const [preset, setPreset] = useState(_preset); + const [showExamples, setShowExamples] = useState(false); const setPresets = useSetRecoilState(store.presets); const availableEndpoints = useRecoilValue(store.availableEndpoints); const endpointsConfig = useRecoilValue(store.endpointsConfig); + const triggerExamples = () => setShowExamples(prev => !prev); + const setOption = param => newValue => { let update = {}; update[param] = newValue; @@ -37,6 +43,69 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => { ); }; + const setExample = (i, type, newValue = null) => { + let update = {}; + let current = preset?.examples.slice() || []; + let currentExample = { ...current[i] } || {}; + currentExample[type] = { content: newValue }; + current[i] = currentExample; + update.examples = current; + setPreset(prevState => + cleanupPreset({ + preset: { + ...prevState, + ...update + }, + endpointsConfig + }) + ); + }; + + const addExample = () => { + let update = {}; + let current = preset?.examples.slice() || []; + current.push({ input: { content: '' }, output: { content: '' } }); + update.examples = current; + setPreset(prevState => + cleanupPreset({ + preset: { + ...prevState, + ...update + }, + endpointsConfig + }) + ); + }; + + const removeExample = () => { + let update = {}; + let current = preset?.examples.slice() || []; + if (current.length <= 1) { + update.examples = [{ input: { content: '' }, output: { content: '' } }]; + setPreset(prevState => + cleanupPreset({ + preset: { + ...prevState, + ...update + }, + endpointsConfig + }) + ); + return; + } + current.pop(); + update.examples = current; + setPreset(prevState => + cleanupPreset({ + preset: { + ...prevState, + ...update + }, + endpointsConfig + }) + ); + }; + const defaultTextProps = 'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 focus:dark:bg-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0'; @@ -111,14 +180,35 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => { )} containerClassName="flex w-full resize-none" /> + {preset?.endpoint === 'google' && ( + + )}
- + {((preset?.endpoint === 'google' && !showExamples) || preset?.endpoint !== 'google') && ( + + )} + {preset?.endpoint === 'google' && showExamples && ( + + )}
} diff --git a/client/src/components/Endpoints/EndpointOptionsDialog.jsx b/client/src/components/Endpoints/EndpointOptionsDialog.jsx index 17b9528f64..9191b5758d 100644 --- a/client/src/components/Endpoints/EndpointOptionsDialog.jsx +++ b/client/src/components/Endpoints/EndpointOptionsDialog.jsx @@ -12,12 +12,16 @@ import store from '~/store'; // A preset dialog to show readonly preset values. const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) => { - // const [title, setTitle] = useState('My Preset'); const [preset, setPreset] = useState(_preset); + const [endpointName, setEndpointName] = useState(preset?.endpoint); const [saveAsDialogShow, setSaveAsDialogShow] = useState(false); const endpointsConfig = useRecoilValue(store.endpointsConfig); + if (endpointName === 'google') { + setEndpointName('PaLM'); + } + const setOption = param => newValue => { let update = {}; update[param] = newValue; @@ -50,7 +54,7 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) = onOpenChange={onOpenChange} > diff --git a/client/src/components/Endpoints/EndpointOptionsPopover.jsx b/client/src/components/Endpoints/EndpointOptionsPopover.jsx index 2a1948c57f..af97c2f879 100644 --- a/client/src/components/Endpoints/EndpointOptionsPopover.jsx +++ b/client/src/components/Endpoints/EndpointOptionsPopover.jsx @@ -4,7 +4,13 @@ import CrossIcon from '../svg/CrossIcon'; // import SaveIcon from '../svg/SaveIcon'; import { Save } from 'lucide-react'; -function EndpointOptionsPopover({ content, visible, saveAsPreset, switchToSimpleMode }) { +function EndpointOptionsPopover({ + content, + visible, + saveAsPreset, + switchToSimpleMode, + additionalButton = null +}) { const cardStyle = 'shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white'; @@ -12,29 +18,39 @@ function EndpointOptionsPopover({ content, visible, saveAsPreset, switchToSimple <>
-
+
{/* Advanced settings for OpenAI endpoint */} + {additionalButton && ( + + )} + +
+ + ); +} + +export default Examples; diff --git a/client/src/components/Endpoints/Google/OptionHover.jsx b/client/src/components/Endpoints/Google/OptionHover.jsx new file mode 100644 index 0000000000..caadaf71cb --- /dev/null +++ b/client/src/components/Endpoints/Google/OptionHover.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { HoverCardPortal, HoverCardContent } from '~/components/ui/HoverCard.tsx'; + +const types = { + temp: 'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.', + topp: 'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.', + topk: "Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).", + 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." +}; + +function OptionHover({ type, side }) { + // const options = {}; + // if (type === 'pres') { + // options.sideOffset = 45; + // } + + return ( + + +
+

{types[type]}

+
+
+
+ ); +} + +export default OptionHover; diff --git a/client/src/components/Endpoints/Google/Settings.jsx b/client/src/components/Endpoints/Google/Settings.jsx new file mode 100644 index 0000000000..bb8186dc05 --- /dev/null +++ b/client/src/components/Endpoints/Google/Settings.jsx @@ -0,0 +1,271 @@ +import { useRecoilValue } from 'recoil'; +import TextareaAutosize from 'react-textarea-autosize'; +import SelectDropDown from '../../ui/SelectDropDown'; +import { Input } from '~/components/ui/Input.tsx'; +import { Label } from '~/components/ui/Label.tsx'; +import { Slider } from '~/components/ui/Slider.tsx'; +import { InputNumber } from '~/components/ui/InputNumber.tsx'; +import OptionHover from './OptionHover'; +import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx'; +import { cn } from '~/utils/'; +const defaultTextProps = + 'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 focus:dark:bg-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0'; + +const optionText = + 'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors'; + +import store from '~/store'; + +function Settings(props) { + const { readonly, model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens, setOption, edit = false } = props; + const maxHeight = edit ? 'max-h-[233px]' : 'max-h-[350px]'; + const endpointsConfig = useRecoilValue(store.endpointsConfig); + + const setModel = setOption('model'); + const setModelLabel = setOption('modelLabel'); + const setPromptPrefix = setOption('promptPrefix'); + const setTemperature = setOption('temperature'); + const setTopP = setOption('topP'); + const setTopK = setOption('topK'); + const setMaxOutputTokens = setOption('maxOutputTokens'); + + const models = endpointsConfig?.['google']?.['availableModels'] || []; + + return ( +
+
+
+
+ +
+
+ + setModelLabel(e.target.value || null)} + placeholder="Set a custom name for PaLM2" + className={cn( + defaultTextProps, + 'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0' + )} + /> +
+
+ + setPromptPrefix(e.target.value || null)} + placeholder="Set custom instructions or context. Ignored if empty." + className={cn( + defaultTextProps, + 'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 ' + )} + /> +
+
+
+ + +
+ + setTemperature(value)} + max={1} + min={0} + step={0.01} + controls={false} + className={cn( + defaultTextProps, + cn( + optionText, + 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200' + ) + )} + /> +
+ setTemperature(value[0])} + doubleClickHandler={() => setTemperature(1)} + max={1} + min={0} + step={0.01} + className="flex h-4 w-full" + /> +
+ +
+ + +
+ + setTopP(value)} + max={1} + min={0} + step={0.01} + controls={false} + className={cn( + defaultTextProps, + cn( + optionText, + 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200' + ) + )} + /> +
+ setTopP(value[0])} + doubleClickHandler={() => setTopP(1)} + max={1} + min={0} + step={0.01} + className="flex h-4 w-full" + /> +
+ +
+ + + +
+ + setTopK(value)} + max={40} + min={1} + step={0.01} + controls={false} + className={cn( + defaultTextProps, + cn( + optionText, + 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200' + ) + )} + /> +
+ setTopK(value[0])} + doubleClickHandler={() => setTopK(0)} + max={40} + min={1} + step={0.01} + className="flex h-4 w-full" + /> +
+ +
+ + + +
+ + setMaxOutputTokens(value)} + max={1024} + min={1} + step={1} + controls={false} + className={cn( + defaultTextProps, + cn( + optionText, + 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200' + ) + )} + /> +
+ setMaxOutputTokens(value[0])} + doubleClickHandler={() => setMaxOutputTokens(0)} + max={1024} + min={1} + step={1} + className="flex h-4 w-full" + /> +
+ +
+
+
+
+ ); +} + +export default Settings; diff --git a/client/src/components/Endpoints/OpenAI/Settings.jsx b/client/src/components/Endpoints/OpenAI/Settings.jsx index 2b95ea1c70..c5b463e656 100644 --- a/client/src/components/Endpoints/OpenAI/Settings.jsx +++ b/client/src/components/Endpoints/OpenAI/Settings.jsx @@ -32,7 +32,7 @@ function Settings(props) { const models = endpointsConfig?.['openAI']?.['availableModels'] || []; return ( - <> +
@@ -264,7 +264,7 @@ function Settings(props) {
- +
); } diff --git a/client/src/components/Endpoints/Settings.jsx b/client/src/components/Endpoints/Settings.jsx index 109d5d56c3..f7eaf41e0c 100644 --- a/client/src/components/Endpoints/Settings.jsx +++ b/client/src/components/Endpoints/Settings.jsx @@ -2,13 +2,15 @@ import React from 'react'; import OpenAISettings from './OpenAI/Settings.jsx'; import BingAISettings from './BingAI/Settings.jsx'; +import GoogleSettings from './Google/Settings.jsx'; // A preset dialog to show readonly preset values. const Settings = ({ preset, ...props }) => { const renderSettings = () => { const { endpoint } = preset || {}; + // console.log('preset', preset); - if (endpoint === 'openAI') + if (endpoint === 'openAI') { return ( { {...props} /> ); - else if (endpoint === 'bingAI') + } else if (endpoint === 'bingAI') { return ( { {...props} /> ); - else return
Not implemented
; + } else if (endpoint === 'google') { + return ( + + ); + } else { + return
Not implemented
; + } }; return renderSettings(); diff --git a/client/src/components/Input/GoogleOptions/index.jsx b/client/src/components/Input/GoogleOptions/index.jsx new file mode 100644 index 0000000000..d5c2c11683 --- /dev/null +++ b/client/src/components/Input/GoogleOptions/index.jsx @@ -0,0 +1,170 @@ +import { useState } from 'react'; +import { Settings2 } from 'lucide-react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import MessagesSquared from '~/components/svg/MessagesSquared.jsx'; +import SelectDropDown from '../../ui/SelectDropDown'; +import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover'; +import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog'; +import { Button } from '../../ui/Button.tsx'; +import Settings from '../../Endpoints/Google/Settings.jsx'; +import Examples from '../../Endpoints/Google/Examples.jsx'; +import { cn } from '~/utils/'; + +import store from '~/store'; + +function GoogleOptions() { + const [advancedMode, setAdvancedMode] = useState(false); + const [showExamples, setShowExamples] = useState(false); + const [saveAsDialogShow, setSaveAsDialogShow] = useState(false); + + const [conversation, setConversation] = useRecoilState(store.conversation) || {}; + const { endpoint, conversationId } = conversation; + const { model, modelLabel, promptPrefix, examples, temperature, topP, topK, maxOutputTokens } = + conversation; + + const endpointsConfig = useRecoilValue(store.endpointsConfig); + + if (endpoint !== 'google') return null; + if (conversationId !== 'new') return null; + + const models = endpointsConfig?.['google']?.['availableModels'] || []; + + const triggerAdvancedMode = () => setAdvancedMode(prev => !prev); + const triggerExamples = () => setShowExamples(prev => !prev); + + const switchToSimpleMode = () => { + setAdvancedMode(false); + }; + + const saveAsPreset = () => { + setSaveAsDialogShow(true); + }; + + const setOption = param => newValue => { + let update = {}; + update[param] = newValue; + setConversation(prevState => ({ + ...prevState, + ...update + })); + }; + + const setExample = (i, type, newValue = null) => { + let update = {}; + let current = conversation?.examples.slice() || []; + let currentExample = { ...current[i] } || {}; + currentExample[type] = { content: newValue }; + current[i] = currentExample; + update.examples = current; + setConversation(prevState => ({ + ...prevState, + ...update + })); + }; + + const addExample = () => { + let update = {}; + let current = conversation?.examples.slice() || []; + current.push({ input: { content: '' }, output: { content: '' } }); + update.examples = current; + setConversation(prevState => ({ + ...prevState, + ...update + })); + }; + + const removeExample = () => { + let update = {}; + let current = conversation?.examples.slice() || []; + if (current.length <= 1) { + update.examples = [{ input: { content: '' }, output: { content: '' } }]; + setConversation(prevState => ({ + ...prevState, + ...update + })); + return; + } + current.pop(); + update.examples = current; + setConversation(prevState => ({ + ...prevState, + ...update + })); + }; + + const cardStyle = + 'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white'; + + return ( + <> +
+ + +
+ + {showExamples ? ( + + ) : ( + + )} +
+ } + visible={advancedMode} + saveAsPreset={saveAsPreset} + switchToSimpleMode={switchToSimpleMode} + additionalButton={{ + label: (showExamples ? 'Hide' : 'Show') + ' Examples', + handler: triggerExamples, + icon: + }} + /> + + + ); +} + +export default GoogleOptions; diff --git a/client/src/components/Input/NewConversationMenu/EndpointItem.jsx b/client/src/components/Input/NewConversationMenu/EndpointItem.jsx index 690e120e82..1b041f02e8 100644 --- a/client/src/components/Input/NewConversationMenu/EndpointItem.jsx +++ b/client/src/components/Input/NewConversationMenu/EndpointItem.jsx @@ -7,6 +7,14 @@ import SetTokenDialog from '../SetTokenDialog'; import store from '../../../store'; +const alternateName = { + openAI: 'OpenAI', + azureOpenAI: 'Azure OpenAI', + bingAI: 'Bing', + chatGPTBrowser: 'ChatGPT', + google: 'PaLM', +} + export default function ModelItem({ endpoint, value, onSelect }) { const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false); const endpointsConfig = useRecoilValue(store.endpointsConfig); @@ -28,7 +36,7 @@ export default function ModelItem({ endpoint, value, onSelect }) { className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800" > {icon} - {endpoint} + {alternateName[endpoint] || endpoint} {!!['azureOpenAI', 'openAI'].find(e => e === endpoint) && $}
{isuserProvide ? ( diff --git a/client/src/components/Input/NewConversationMenu/FileUpload.jsx b/client/src/components/Input/NewConversationMenu/FileUpload.jsx index 15b8e1a743..e3f005c205 100644 --- a/client/src/components/Input/NewConversationMenu/FileUpload.jsx +++ b/client/src/components/Input/NewConversationMenu/FileUpload.jsx @@ -1,13 +1,10 @@ -import React from 'react'; +import { useState } from 'react'; import { FileUp } from 'lucide-react'; -import cleanupPreset from '~/utils/cleanupPreset.js'; -import { useRecoilValue } from 'recoil'; +import { cn } from '~/utils/'; -import store from '~/store'; - -const FileUpload = ({ onFileSelected }) => { - // const setPresets = useSetRecoilState(store.presets); - const endpointsConfig = useRecoilValue(store.endpointsConfig); +const FileUpload = ({ onFileSelected, successText = null, invalidText = null, validator = null, text = null, id = '1' }) => { + const [statusColor, setStatusColor] = useState('text-gray-600'); + const [status, setStatus] = useState(null); const handleFileChange = event => { const file = event.target.files[0]; @@ -16,20 +13,34 @@ const FileUpload = ({ onFileSelected }) => { const reader = new FileReader(); reader.onload = e => { const jsonData = JSON.parse(e.target.result); - onFileSelected({ ...cleanupPreset({ preset: jsonData, endpointsConfig }), presetId: null }); + if (validator && !validator(jsonData)) { + setStatus('invalid'); + setStatusColor('text-red-600'); + return; + } + + if (validator) { + setStatus('success'); + setStatusColor('text-green-500 dark:text-green-500'); + } + + onFileSelected(jsonData); }; reader.readAsText(file); }; return (
diff --git a/client/src/components/Messages/HoverButtons.jsx b/client/src/components/Messages/HoverButtons.jsx index 93468598e9..5754aa89f4 100644 --- a/client/src/components/Messages/HoverButtons.jsx +++ b/client/src/components/Messages/HoverButtons.jsx @@ -16,7 +16,7 @@ export default function HoverButtons({ const branchingSupported = // azureOpenAI, openAI, chatGPTBrowser support branching, so edit enabled - !!['azureOpenAI', 'openAI', 'chatGPTBrowser'].find(e => e === endpoint) || + !!['azureOpenAI', 'openAI', 'chatGPTBrowser', 'google'].find(e => e === endpoint) || // Sydney in bingAI supports branching, so edit enabled (endpoint === 'bingAI' && jailbreak); diff --git a/client/src/components/Messages/MessageHeader.jsx b/client/src/components/Messages/MessageHeader.jsx index 1d60797c22..65fea04ad8 100644 --- a/client/src/components/Messages/MessageHeader.jsx +++ b/client/src/components/Messages/MessageHeader.jsx @@ -31,6 +31,11 @@ const MessageHeader = ({ isSearchView = false }) => { const { chatGptLabel, model } = conversation; if (model) _title += `: ${model}`; if (chatGptLabel) _title += ` as ${chatGptLabel}`; + } else if (endpoint === 'google') { + _title = 'PaLM'; + const { modelLabel, model } = conversation; + if (model) _title += `: ${model}`; + if (modelLabel) _title += ` as ${modelLabel}`; } else if (endpoint === 'bingAI') { const { jailbreak, toneStyle } = conversation; if (toneStyle) _title += `: ${toneStyle}`; diff --git a/client/src/components/svg/MessagesSquared.jsx b/client/src/components/svg/MessagesSquared.jsx new file mode 100644 index 0000000000..5203e322c9 --- /dev/null +++ b/client/src/components/svg/MessagesSquared.jsx @@ -0,0 +1,21 @@ +import { cn } from '~/utils/'; + +export default function MessagesSquared({ className }) { + return ( + + + + + ); +} diff --git a/client/src/components/ui/DialogTemplate.jsx b/client/src/components/ui/DialogTemplate.jsx index 2e46bbc49f..f09cf87bea 100644 --- a/client/src/components/ui/DialogTemplate.jsx +++ b/client/src/components/ui/DialogTemplate.jsx @@ -29,26 +29,6 @@ export default function DialogTemplate({ {title} {description} - {/*
-
//input template - -
-
- - setPromptPrefix(e.target.value)} - placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" - className="col-span-3 flex h-20 w-full resize-none rounded-md border border-gray-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-none dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0" - /> -
-
*/} {main ? main : null}
{leftButtons ? leftButtons : null}
diff --git a/client/src/data-provider/createPayload.ts b/client/src/data-provider/createPayload.ts index 5c60e8f197..9e401ed622 100644 --- a/client/src/data-provider/createPayload.ts +++ b/client/src/data-provider/createPayload.ts @@ -8,6 +8,7 @@ export default function createPayload(submission: TSubmission) { const endpointUrlMap = { azureOpenAI: '/api/ask/azureOpenAI', openAI: '/api/ask/openAI', + google: '/api/ask/google', bingAI: '/api/ask/bingAI', chatGPTBrowser: '/api/ask/chatGPTBrowser' }; diff --git a/client/src/data-provider/types.ts b/client/src/data-provider/types.ts index f06de9a9d6..bfa1db58db 100644 --- a/client/src/data-provider/types.ts +++ b/client/src/data-provider/types.ts @@ -11,6 +11,11 @@ export type TMessage = { updatedAt: string, }; +export type TExample = { + input: string, + output: string, +}; + export type TSubmission = { clientId?: string; context?: string; @@ -43,7 +48,8 @@ export enum EModelEndpoint { openAI = 'openAI', bingAI = 'bingAI', chatGPT = 'chatGPT', - chatGPTBrowser = 'chatGPTBrowser' + chatGPTBrowser = 'chatGPTBrowser', + google = 'google', } export type TConversation = { @@ -55,11 +61,19 @@ export type TConversation = { messages?: TMessage[]; createdAt: string; updatedAt: string; + // google only + modelLabel?: string; + examples?: TExample[]; // for azureOpenAI, openAI only chatGptLabel?: string; + userLabel?: string; model?: string; promptPrefix?: string; temperature?: number; + topP?: number; + topK?: number; + // bing and google + context?: string; top_p?: number; presence_penalty?: number; // for bingAI only diff --git a/client/src/store/endpoints.js b/client/src/store/endpoints.js index 2406e461cd..2b33134084 100644 --- a/client/src/store/endpoints.js +++ b/client/src/store/endpoints.js @@ -6,7 +6,8 @@ const endpointsConfig = atom({ azureOpenAI: null, openAI: null, bingAI: null, - chatGPTBrowser: null + chatGPTBrowser: null, + google: null, } }); @@ -24,7 +25,7 @@ const endpointsFilter = selector({ const availableEndpoints = selector({ key: 'availableEndpoints', get: ({ get }) => { - const endpoints = ['azureOpenAI', 'openAI', 'bingAI', 'chatGPTBrowser']; + const endpoints = ['azureOpenAI', 'openAI', 'chatGPTBrowser', 'bingAI', 'google']; const f = get(endpointsFilter); return endpoints.filter(endpoint => f[endpoint]); } diff --git a/client/src/utils/cleanupPreset.js b/client/src/utils/cleanupPreset.js index ab962a01d4..9cdc069a89 100644 --- a/client/src/utils/cleanupPreset.js +++ b/client/src/utils/cleanupPreset.js @@ -15,6 +15,20 @@ const cleanupPreset = ({ preset: _preset, endpointsConfig = {} }) => { frequency_penalty: _preset?.frequency_penalty ?? 0, title: _preset?.title ?? 'New Preset' }; + } else if (endpoint === 'google') { + preset = { + endpoint, + presetId: _preset?.presetId ?? null, + model: _preset?.model ?? endpointsConfig[endpoint]?.availableModels?.[0] ?? 'chat-bison', + modelLabel: _preset?.modelLabel ?? null, + examples: _preset?.examples ?? [{ input: { content: '' }, output: { content: '' } }], + promptPrefix: _preset?.promptPrefix ?? null, + temperature: _preset?.temperature ?? 0.2, + maxOutputTokens: _preset?.maxOutputTokens ?? 1024, + topP: _preset?.topP ?? 0.95, + topK: _preset?.topK ?? 40, + title: _preset?.title ?? 'New Preset' + }; } else if (endpoint === 'bingAI') { preset = { endpoint, diff --git a/client/src/utils/getDefaultConversation.js b/client/src/utils/getDefaultConversation.js index 6776fefe58..a941fae3b7 100644 --- a/client/src/utils/getDefaultConversation.js +++ b/client/src/utils/getDefaultConversation.js @@ -22,6 +22,23 @@ const buildDefaultConversation = ({ presence_penalty: lastConversationSetup?.presence_penalty ?? 0, frequency_penalty: lastConversationSetup?.frequency_penalty ?? 0 }; + } else if (endpoint === 'google') { + conversation = { + ...conversation, + endpoint, + model: + lastConversationSetup?.model ?? + lastSelectedModel[endpoint] ?? + endpointsConfig[endpoint]?.availableModels?.[0] ?? + 'chat-bison', + modelLabel: lastConversationSetup?.modelLabel ?? null, + promptPrefix: lastConversationSetup?.promptPrefix ?? null, + examples: lastConversationSetup?.examples ?? [{ input: { content: '' }, output: { content: '' }}], + temperature: lastConversationSetup?.temperature ?? 0.2, + maxOutputTokens: lastConversationSetup?.maxOutputTokens ?? 1024, + topP: lastConversationSetup?.topP ?? 0.95, + topK: lastConversationSetup?.topK ?? 40, + }; } else if (endpoint === 'bingAI') { conversation = { ...conversation, @@ -108,7 +125,7 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsConfi // if anything happens, reset to default model - const endpoint = ['openAI', 'azureOpenAI', 'bingAI', 'chatGPTBrowser'].find(e => endpointsConfig?.[e]); + const endpoint = ['openAI', 'azureOpenAI', 'bingAI', 'chatGPTBrowser', 'google'].find(e => endpointsConfig?.[e]); if (endpoint) { conversation = buildDefaultConversation({ conversation, endpoint, endpointsConfig }); return conversation; diff --git a/client/src/utils/getIcon.jsx b/client/src/utils/getIcon.jsx index 9e3970ef39..bafedab435 100644 --- a/client/src/utils/getIcon.jsx +++ b/client/src/utils/getIcon.jsx @@ -42,6 +42,10 @@ const getIcon = props => { ? `rgba(16, 163, 127, ${button ? 0.75 : 1})` : `rgba(16, 163, 127, ${button ? 0.75 : 1})`); name = chatGptLabel || 'ChatGPT'; + } else if (endpoint === 'google') { + const { modelLabel } = props; + icon = ; + name = modelLabel || 'PaLM2'; } else if (endpoint === 'bingAI') { const { jailbreak } = props; diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index 7df1830f61..a65de4f014 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -40,6 +40,21 @@ const useMessageHandler = () => { frequency_penalty: currentConversation?.frequency_penalty ?? 0 }; responseSender = endpointOption.chatGptLabel ?? 'ChatGPT'; + } else if (endpoint === 'google') { + endpointOption = { + endpoint, + model: + currentConversation?.model ?? endpointsConfig[endpoint]?.availableModels?.[0] ?? 'chat-bison', + chatGptLabel: currentConversation?.chatGptLabel ?? null, + promptPrefix: currentConversation?.promptPrefix ?? null, + examples: currentConversation?.examples ?? [{ input: { content: '' }, output: { content: '' }}], + temperature: currentConversation?.temperature ?? 0.2, + maxOutputTokens: currentConversation?.maxOutputTokens ?? 1024, + topP: currentConversation?.topP ?? 0.95, + topK: currentConversation?.topK ?? 40, + token: endpointsConfig[endpoint]?.userProvide ? getToken() : null + }; + responseSender = endpointOption.chatGptLabel ?? 'ChatGPT'; } else if (endpoint === 'bingAI') { endpointOption = { endpoint, @@ -125,7 +140,7 @@ const useMessageHandler = () => { initialResponse }; - console.log('User Input:', text); + console.log('User Input:', text, submission); if (isRegenerate) { setMessages([...currentMessages, initialResponse]); diff --git a/package-lock.json b/package-lock.json index 9308577938..e3ac0fb693 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "dotenv": "^16.0.3", "eslint": "^8.36.0", "express": "^4.18.2", + "googleapis": "^118.0.0", "handlebars": "^4.7.7", "html": "^1.0.0", "joi": "^14.3.1", @@ -5522,6 +5523,14 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, "node_modules/asn1.js": { "version": "5.4.1", "license": "MIT", @@ -5868,6 +5877,14 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "license": "MIT", @@ -8173,6 +8190,11 @@ "node": ">=6" } }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "node_modules/fast-uri": { "version": "2.2.0", "license": "MIT" @@ -8661,6 +8683,32 @@ "node": ">=8" } }, + "node_modules/gaxios": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", + "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -8800,6 +8848,110 @@ "node": ">=8" } }, + "node_modules/google-auth-library": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.8.0.tgz", + "integrity": "sha512-0iJn7IDqObDG5Tu9Tn2WemmJ31ksEa96IyK0J0OZCpTh6CrC6FrattwKX87h3qKVuprCJpdOGKc1Xi8V0kMh8Q==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.2.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis": { + "version": "118.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-118.0.0.tgz", + "integrity": "sha512-Ny6zJOGn5P/YDT6GQbJU6K0lSzEu4Yuxnsn45ZgBIeSQ1RM0FolEjUToLXquZd89DU9wUfqA5XYHPEctk1TFWg==", + "dependencies": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.4.tgz", + "integrity": "sha512-m4ErxGE8unR1z0VajT6AYk3s6a9gIMM6EkDZfkPnES8joeOlEtFEJeF8IyZkb0tjPXkktUfYrE4b3Li1DNyOwA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "dev": true, @@ -8819,6 +8971,38 @@ "version": "1.0.4", "license": "MIT" }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/hamt_plus": { "version": "1.0.2", "license": "MIT" @@ -10517,6 +10701,14 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "license": "MIT" @@ -12011,6 +12203,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-html-parser": { "version": "5.4.2", "dev": true, @@ -15978,6 +16178,11 @@ "querystring": "0.2.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "license": "MIT" @@ -19967,6 +20172,11 @@ "get-intrinsic": "^1.1.3" } }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asn1.js": { "version": "5.4.1", "requires": { @@ -20203,6 +20413,11 @@ "version": "5.2.2", "dev": true }, + "bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" + }, "binary-extensions": { "version": "2.2.0" }, @@ -20457,6 +20672,7 @@ "dotenv": "^16.0.3", "eslint": "^8.36.0", "express": "^4.18.2", + "googleapis": "^118.0.0", "handlebars": "^4.7.7", "html": "^1.0.0", "joi": "^14.3.1", @@ -21802,6 +22018,11 @@ "fast-redact": { "version": "3.1.2" }, + "fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "fast-uri": { "version": "2.2.0" }, @@ -22107,6 +22328,26 @@ } } }, + "gaxios": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", + "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + } + }, + "gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, "gensync": { "version": "1.0.0-beta.2", "dev": true @@ -22187,6 +22428,93 @@ } } }, + "google-auth-library": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.8.0.tgz", + "integrity": "sha512-0iJn7IDqObDG5Tu9Tn2WemmJ31ksEa96IyK0J0OZCpTh6CrC6FrattwKX87h3qKVuprCJpdOGKc1Xi8V0kMh8Q==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.2.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "requires": { + "node-forge": "^1.3.1" + } + }, + "googleapis": { + "version": "118.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-118.0.0.tgz", + "integrity": "sha512-Ny6zJOGn5P/YDT6GQbJU6K0lSzEu4Yuxnsn45ZgBIeSQ1RM0FolEjUToLXquZd89DU9wUfqA5XYHPEctk1TFWg==", + "requires": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + } + }, + "googleapis-common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.4.tgz", + "integrity": "sha512-m4ErxGE8unR1z0VajT6AYk3s6a9gIMM6EkDZfkPnES8joeOlEtFEJeF8IyZkb0tjPXkktUfYrE4b3Li1DNyOwA==", + "requires": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + } + } + }, "gopd": { "version": "1.0.1", "dev": true, @@ -22200,6 +22528,37 @@ "grapheme-splitter": { "version": "1.0.4" }, + "gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "requires": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, "hamt_plus": { "version": "1.0.2" }, @@ -23189,6 +23548,14 @@ "version": "2.5.2", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.1" }, @@ -24058,6 +24425,11 @@ } } }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, "node-html-parser": { "version": "5.4.2", "dev": true, @@ -26332,6 +26704,11 @@ } } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "use-callback-ref": { "version": "1.3.0", "requires": {