Merge branch 'SillyTavern:release' into enhancement-make-buttons-scrollable

dd0caa68069b7b4c9831c49b34546be4aa400a9d

joenunezb <joenunezb@gmail.com>

Signed
14 files changed, +145 -15Showing whitespace changes
package-lock.json+9 -2
@@ -1,12 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.12.6",3 "version": "1.12.7",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.12.6",9 "version": "1.12.7",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
@@ -24,6 +24,7 @@
24 "csrf-csrf": "^2.2.3",24 "csrf-csrf": "^2.2.3",
25 "express": "^4.21.0",25 "express": "^4.21.0",
26 "form-data": "^4.0.0",26 "form-data": "^4.0.0",
27 "google-translate-api-browser": "^3.0.1",
27 "google-translate-api-x": "^10.7.1",28 "google-translate-api-x": "^10.7.1",
28 "helmet": "^7.1.0",29 "helmet": "^7.1.0",
29 "html-entities": "^2.5.2",30 "html-entities": "^2.5.2",
@@ -3693,6 +3694,12 @@
3693 "url": "https://github.com/sponsors/sindresorhus"3694 "url": "https://github.com/sponsors/sindresorhus"
3694 }3695 }
3695 },3696 },
3697 "node_modules/google-translate-api-browser": {
3698 "version": "3.0.1",
3699 "resolved": "https://registry.npmjs.org/google-translate-api-browser/-/google-translate-api-browser-3.0.1.tgz",
3700 "integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A==",
3701 "license": "MIT"
3702 },
3696 "node_modules/google-translate-api-x": {3703 "node_modules/google-translate-api-x": {
3697 "version": "10.7.1",3704 "version": "10.7.1",
3698 "resolved": "https://registry.npmjs.org/google-translate-api-x/-/google-translate-api-x-10.7.1.tgz",3705 "resolved": "https://registry.npmjs.org/google-translate-api-x/-/google-translate-api-x-10.7.1.tgz",
package.json+2 -1
@@ -14,6 +14,7 @@
14 "csrf-csrf": "^2.2.3",14 "csrf-csrf": "^2.2.3",
15 "express": "^4.21.0",15 "express": "^4.21.0",
16 "form-data": "^4.0.0",16 "form-data": "^4.0.0",
17 "google-translate-api-browser": "^3.0.1",
17 "google-translate-api-x": "^10.7.1",18 "google-translate-api-x": "^10.7.1",
18 "helmet": "^7.1.0",19 "helmet": "^7.1.0",
19 "html-entities": "^2.5.2",20 "html-entities": "^2.5.2",
@@ -66,7 +67,7 @@
66 "type": "git",67 "type": "git",
67 "url": "https://github.com/SillyTavern/SillyTavern.git"68 "url": "https://github.com/SillyTavern/SillyTavern.git"
68 },69 },
69 "version": "1.12.6",70 "version": "1.12.7",
70 "scripts": {71 "scripts": {
71 "start": "node server.js",72 "start": "node server.js",
72 "start:no-csrf": "node server.js --disableCsrf",73 "start:no-csrf": "node server.js --disableCsrf",
public/index.html+3 -2
@@ -1397,8 +1397,7 @@
1397 </div>1397 </div>
1398 </div>1398 </div>
13991399
1400 <!-- Enable for llama.cpp when the PR is merged: https://github.com/ggerganov/llama.cpp/pull/6839 -->1400 <div data-tg-type="ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p">
1401 <div data-tg-type="ooba, koboldcpp, tabby" id="dryBlock" class="wide100p">
1402 <h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable." data-i18n="[title]DRY_Repetition_Penalty_desc">1401 <h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable." data-i18n="[title]DRY_Repetition_Penalty_desc">
1403 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>1402 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
1404 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">1403 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -2797,6 +2796,8 @@
2797 <h4 data-i18n="Claude Model">Claude Model</h4>2796 <h4 data-i18n="Claude Model">Claude Model</h4>
2798 <select id="model_claude_select">2797 <select id="model_claude_select">
2799 <optgroup label="Versions">2798 <optgroup label="Versions">
2799 <option value="claude-3-5-sonnet-latest">claude-3-5-sonnet-latest</option>
2800 <option value="claude-3-5-sonnet-20241022">claude-3-5-sonnet-20241022</option>
2800 <option value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>2801 <option value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
2801 <option value="claude-3-opus-20240229">claude-3-opus-20240229</option>2802 <option value="claude-3-opus-20240229">claude-3-opus-20240229</option>
2802 <option value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>2803 <option value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
public/script.js+15 -4
@@ -3136,7 +3136,8 @@ class StreamingProcessor {
31363136
3137 //console.log("Generated text size:", text.length, text)3137 //console.log("Generated text size:", text.length, text)
31383138
3139 if (power_user.auto_swipe) {3139 const isAborted = this.abortController.signal.aborted;
3140 if (power_user.auto_swipe && !isAborted) {
3140 function containsBlacklistedWords(str, blacklist, threshold) {3141 function containsBlacklistedWords(str, blacklist, threshold) {
3141 const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');3142 const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
3142 const matches = str.match(regex) || [];3143 const matches = str.match(regex) || [];
@@ -3840,7 +3841,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3840 if (addUserAlignment) {3841 if (addUserAlignment) {
3841 const alignmentMessage = {3842 const alignmentMessage = {
3842 name: name1,3843 name: name1,
3843 mes: power_user.instruct.user_alignment_message,3844 mes: substituteParams(power_user.instruct.user_alignment_message),
3844 is_user: true,3845 is_user: true,
3845 };3846 };
3846 userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, force_output_sequence.FIRST);3847 userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, force_output_sequence.FIRST);
@@ -4583,7 +4584,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4583 playMessageSound();4584 playMessageSound();
4584 }4585 }
45854586
4586 if (power_user.auto_swipe) {4587 const isAborted = abortController && abortController.signal.aborted;
4588 if (power_user.auto_swipe && !isAborted) {
4587 console.debug('checking for autoswipeblacklist on non-streaming message');4589 console.debug('checking for autoswipeblacklist on non-streaming message');
4588 function containsBlacklistedWords(getMessage, blacklist, threshold) {4590 function containsBlacklistedWords(getMessage, blacklist, threshold) {
4589 console.debug('checking blacklisted words');4591 console.debug('checking blacklisted words');
@@ -4768,6 +4770,11 @@ export function shouldAutoContinue(messageChunk, isImpersonate) {
4768 return false;4770 return false;
4769 }4771 }
47704772
4773 if (abortController && abortController.signal.aborted) {
4774 console.debug('Auto-continue is not triggered because the generation was stopped.');
4775 return false;
4776 }
4777
4771 if (power_user.auto_continue.target_length <= 0) {4778 if (power_user.auto_continue.target_length <= 0) {
4772 console.log('Auto-continue target length is 0, not triggering auto-continue');4779 console.log('Auto-continue target length is 0, not triggering auto-continue');
4773 return false;4780 return false;
@@ -7178,7 +7185,7 @@ export function select_rm_info(type, charId, previousCharId = null) {
7178 importFlashTimeout = setTimeout(function () {7185 importFlashTimeout = setTimeout(function () {
7179 if (type === 'char_import' || type === 'char_create') {7186 if (type === 'char_import' || type === 'char_create') {
7180 // Find the page at which the character is located7187 // Find the page at which the character is located
7181 const avatarFileName = `${charId}.png`;7188 const avatarFileName = charId;
7182 const charData = getEntitiesList({ doFilter: true });7189 const charData = getEntitiesList({ doFilter: true });
7183 const charIndex = charData.findIndex((x) => x?.item?.avatar?.startsWith(avatarFileName));7190 const charIndex = charData.findIndex((x) => x?.item?.avatar?.startsWith(avatarFileName));
71847191
@@ -8218,6 +8225,10 @@ window['SillyTavern'].getContext = function () {
8218 substituteParams,8225 substituteParams,
8219 substituteParamsExtended,8226 substituteParamsExtended,
8220 SlashCommandParser,8227 SlashCommandParser,
8228 SlashCommand,
8229 SlashCommandArgument,
8230 SlashCommandNamedArgument,
8231 ARGUMENT_TYPE,
8221 executeSlashCommandsWithOptions,8232 executeSlashCommandsWithOptions,
8222 /** @deprecated Use SlashCommandParser.addCommandObject() instead */8233 /** @deprecated Use SlashCommandParser.addCommandObject() instead */
8223 registerSlashCommand: registerSlashCommand,8234 registerSlashCommand: registerSlashCommand,
public/scripts/chats.js+1 -0
@@ -1458,6 +1458,7 @@ jQuery(function () {
1458 wrapper.classList.add('height100p', 'wide100p', 'flex-container');1458 wrapper.classList.add('height100p', 'wide100p', 'flex-container');
1459 wrapper.classList.add('flexFlowColumn', 'justifyCenter', 'alignitemscenter');1459 wrapper.classList.add('flexFlowColumn', 'justifyCenter', 'alignitemscenter');
1460 const textarea = document.createElement('textarea');1460 const textarea = document.createElement('textarea');
1461 textarea.dataset.for = broId;
1461 textarea.value = String(contentEditable ? bro[0].innerText : bro.val());1462 textarea.value = String(contentEditable ? bro[0].innerText : bro.val());
1462 textarea.classList.add('height100p', 'wide100p', 'maximized_textarea');1463 textarea.classList.add('height100p', 'wide100p', 'maximized_textarea');
1463 bro.hasClass('monospace') && textarea.classList.add('monospace');1464 bro.hasClass('monospace') && textarea.classList.add('monospace');
public/scripts/extensions/caption/settings.html+2 -0
@@ -43,6 +43,8 @@
43 <option data-type="openai" value="gpt-4o">gpt-4o</option>43 <option data-type="openai" value="gpt-4o">gpt-4o</option>
44 <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>44 <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
45 <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>45 <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
46 <option data-type="anthropic" value="claude-3-5-sonnet-latest">claude-3-5-sonnet-latest</option>
47 <option data-type="anthropic" value="claude-3-5-sonnet-20241022">claude-3-5-sonnet-20241022</option>
46 <option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>48 <option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
47 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>49 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
48 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>50 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
public/scripts/i18n.js+2 -1
@@ -9,6 +9,8 @@ var langs;
9// eslint-disable-next-line prefer-const9// eslint-disable-next-line prefer-const
10var localeData;10var localeData;
1111
12export const getCurrentLocale = () => localeFile;
13
12/**14/**
13 * An observer that will check if any new i18n elements are added to the document15 * An observer that will check if any new i18n elements are added to the document
14 * @type {MutationObserver}16 * @type {MutationObserver}
@@ -215,7 +217,6 @@ function addLanguagesToDropdown() {
215}217}
216218
217export async function initLocales() {219export async function initLocales() {
218 moment.locale(localeFile);
219 langs = await fetch('/locales/lang.json').then(response => response.json());220 langs = await fetch('/locales/lang.json').then(response => response.json());
220 localeData = await getLocaleData(localeFile);221 localeData = await getLocaleData(localeFile);
221 applyLocale();222 applyLocale();
public/scripts/secrets.js+1 -0
@@ -34,6 +34,7 @@ export const SECRET_KEYS = {
34 STABILITY: 'api_key_stability',34 STABILITY: 'api_key_stability',
35 BLOCKENTROPY: 'api_key_blockentropy',35 BLOCKENTROPY: 'api_key_blockentropy',
36 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',36 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
37 TAVILY: 'api_key_tavily',
37};38};
3839
39const INPUT_MAP = {40const INPUT_MAP = {
public/scripts/slash-commands.js+9 -1
@@ -1396,6 +1396,13 @@ export function initDefaultSlashCommands() {
1396 returns: 'popup text',1396 returns: 'popup text',
1397 namedArgumentList: [1397 namedArgumentList: [
1398 SlashCommandNamedArgument.fromProps({1398 SlashCommandNamedArgument.fromProps({
1399 name: 'scroll',
1400 description: 'allows vertical scrolling of the content',
1401 typeList: [ARGUMENT_TYPE.BOOLEAN],
1402 enumList: commonEnumProviders.boolean('trueFalse')(),
1403 defaultValue: 'true',
1404 }),
1405 SlashCommandNamedArgument.fromProps({
1399 name: 'large',1406 name: 'large',
1400 description: 'show large popup',1407 description: 'show large popup',
1401 typeList: [ARGUMENT_TYPE.BOOLEAN],1408 typeList: [ARGUMENT_TYPE.BOOLEAN],
@@ -2125,7 +2132,7 @@ async function buttonsCallback(args, text) {
2125 popupContainer.style.flexDirection = 'column';2132 popupContainer.style.flexDirection = 'column';
2126 popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup2133 popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup
21272134
2128 popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: 'Cancel' });2135 popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: 'Cancel', allowVerticalScrolling: true });
2129 popup.show()2136 popup.show()
2130 .then((result => resolve(typeof result === 'number' ? resultToButtonMap.get(result) ?? '' : '')))2137 .then((result => resolve(typeof result === 'number' ? resultToButtonMap.get(result) ?? '' : '')))
2131 .catch(() => resolve(''));2138 .catch(() => resolve(''));
@@ -2142,6 +2149,7 @@ async function popupCallback(args, value) {
21422149
2143 /** @type {import('./popup.js').PopupOptions} */2150 /** @type {import('./popup.js').PopupOptions} */
2144 const popupOptions = {2151 const popupOptions = {
2152 allowVerticalScrolling: !isFalseBoolean(args?.scroll),
2145 large: isTrueBoolean(args?.large),2153 large: isTrueBoolean(args?.large),
2146 wide: isTrueBoolean(args?.wide),2154 wide: isTrueBoolean(args?.wide),
2147 wider: isTrueBoolean(args?.wider),2155 wider: isTrueBoolean(args?.wider),
public/scripts/textgen-settings.js+12 -0
@@ -1307,11 +1307,23 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
1307 : [];1307 : [];
1308 const tokenBans = toIntArray(banned_tokens);1308 const tokenBans = toIntArray(banned_tokens);
1309 logitBiasArray.push(...tokenBans.map(x => [Number(x), false]));1309 logitBiasArray.push(...tokenBans.map(x => [Number(x), false]));
1310 const sequenceBreakers = (() => {
1311 try {
1312 return JSON.parse(params.dry_sequence_breakers);
1313 } catch {
1314 if (typeof params.dry_sequence_breakers === 'string') {
1315 return params.dry_sequence_breakers.split(',');
1316 }
1317
1318 return undefined;
1319 }
1320 })();
1310 const llamaCppParams = {1321 const llamaCppParams = {
1311 'logit_bias': logitBiasArray,1322 'logit_bias': logitBiasArray,
1312 // Conflicts with ooba's grammar_string1323 // Conflicts with ooba's grammar_string
1313 'grammar': settings.grammar_string,1324 'grammar': settings.grammar_string,
1314 'cache_prompt': true,1325 'cache_prompt': true,
1326 'dry_sequence_breakers': sequenceBreakers,
1315 };1327 };
1316 params = Object.assign(params, llamaCppParams);1328 params = Object.assign(params, llamaCppParams);
1317 }1329 }
public/scripts/utils.js+2 -1
@@ -7,6 +7,7 @@ import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
7import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';7import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
8import { getTagsList } from './tags.js';8import { getTagsList } from './tags.js';
9import { groups, selected_group } from './group-chats.js';9import { groups, selected_group } from './group-chats.js';
10import { getCurrentLocale } from './i18n.js';
1011
11/**12/**
12 * Pagination status string template.13 * Pagination status string template.
@@ -831,7 +832,7 @@ export function timestampToMoment(timestamp) {
831 }832 }
832833
833 const iso8601 = parseTimestamp(timestamp);834 const iso8601 = parseTimestamp(timestamp);
834 const objMoment = iso8601 ? moment(iso8601) : moment.invalid();835 const objMoment = iso8601 ? moment(iso8601).locale(getCurrentLocale()) : moment.invalid();
835836
836 dateCache.set(timestamp, objMoment);837 dateCache.set(timestamp, objMoment);
837 return objMoment;838 return objMoment;
src/endpoints/search.js+48 -0
@@ -206,6 +206,54 @@ router.post('/searxng', jsonParser, async (request, response) => {
206 }206 }
207});207});
208208
209router.post('/tavily', jsonParser, async (request, response) => {
210 try {
211 const apiKey = readSecret(request.user.directories, SECRET_KEYS.TAVILY);
212
213 if (!apiKey) {
214 console.log('No Tavily key found');
215 return response.sendStatus(400);
216 }
217
218 const { query } = request.body;
219
220 const body = {
221 query: query,
222 api_key: apiKey,
223 search_depth: 'basic',
224 topic: 'general',
225 include_answer: true,
226 include_raw_content: false,
227 include_images: false,
228 include_image_descriptions: false,
229 include_domains: [],
230 max_results: 10,
231 };
232
233 const result = await fetch('https://api.tavily.com/search', {
234 method: 'POST',
235 headers: {
236 'Content-Type': 'application/json',
237 },
238 body: JSON.stringify(body),
239 });
240
241 console.log('Tavily query', query);
242
243 if (!result.ok) {
244 const text = await result.text();
245 console.log('Tavily request failed', result.statusText, text);
246 return response.status(500).send(text);
247 }
248
249 const data = await result.json();
250 return response.json(data);
251 } catch (error) {
252 console.log(error);
253 return response.sendStatus(500);
254 }
255});
256
209router.post('/visit', jsonParser, async (request, response) => {257router.post('/visit', jsonParser, async (request, response) => {
210 try {258 try {
211 const url = request.body.url;259 const url = request.body.url;
src/endpoints/secrets.js+1 -0
@@ -47,6 +47,7 @@ export const SECRET_KEYS = {
47 STABILITY: 'api_key_stability',47 STABILITY: 'api_key_stability',
48 BLOCKENTROPY: 'api_key_blockentropy',48 BLOCKENTROPY: 'api_key_blockentropy',
49 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',49 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
50 TAVILY: 'api_key_tavily',
50};51};
5152
52// These are the keys that are safe to expose, even if allowKeysExposure is false53// These are the keys that are safe to expose, even if allowKeysExposure is false
src/endpoints/translate.js+38 -3
@@ -1,9 +1,10 @@
1import https from 'node:https';1import https from 'node:https';
2import { createRequire } from 'node:module';
23
3import fetch from 'node-fetch';4import fetch from 'node-fetch';
4import express from 'express';5import express from 'express';
5import bingTranslateApi from 'bing-translate-api';6import bingTranslateApi from 'bing-translate-api';
6import googleTranslateApi from 'google-translate-api-x';7import iconv from 'iconv-lite';
78
8import { readSecret, SECRET_KEYS } from './secrets.js';9import { readSecret, SECRET_KEYS } from './secrets.js';
9import { getConfigValue, uuidv4 } from '../util.js';10import { getConfigValue, uuidv4 } from '../util.js';
@@ -14,6 +15,30 @@ const ONERING_URL_DEFAULT = 'http://127.0.0.1:4990/translate';
1415
15export const router = express.Router();16export const router = express.Router();
1617
18/**
19 * Get the Google Translate API client.
20 * @returns {import('google-translate-api-browser')} Google Translate API client
21 */
22function getGoogleTranslateClient() {
23 const require = createRequire(import.meta.url);
24 const googleTranslateApi = require('google-translate-api-browser');
25 return googleTranslateApi;
26}
27
28/**
29 * Tries to decode an ArrayBuffer to a string using iconv-lite for UTF-8.
30 * @param {ArrayBuffer} buffer ArrayBuffer
31 * @returns {string} Decoded string
32 */
33function decodeBuffer(buffer) {
34 try {
35 return iconv.decode(Buffer.from(buffer), 'utf-8');
36 } catch (error) {
37 console.log('Failed to decode buffer:', error);
38 return Buffer.from(buffer).toString('utf-8');
39 }
40}
41
17router.post('/libre', jsonParser, async (request, response) => {42router.post('/libre', jsonParser, async (request, response) => {
18 const key = readSecret(request.user.directories, SECRET_KEYS.LIBRE);43 const key = readSecret(request.user.directories, SECRET_KEYS.LIBRE);
19 const url = readSecret(request.user.directories, SECRET_KEYS.LIBRE_URL);44 const url = readSecret(request.user.directories, SECRET_KEYS.LIBRE_URL);
@@ -81,8 +106,18 @@ router.post('/google', jsonParser, async (request, response) => {
81106
82 console.log('Input text: ' + text);107 console.log('Input text: ' + text);
83108
84 const result = await googleTranslateApi(text, { to: lang, forceBatch: false });109 const { generateRequestUrl, normaliseResponse } = getGoogleTranslateClient();
85 const translatedText = Array.isArray(result) ? result.map(x => x.text).join('') : result.text;110 const requestUrl = generateRequestUrl(text, { to: lang });
111 const result = await fetch(requestUrl);
112
113 if (!result.ok) {
114 console.log('Google Translate error: ', result.statusText);
115 return response.sendStatus(500);
116 }
117
118 const buffer = await result.arrayBuffer();
119 const translateResponse = normaliseResponse(JSON.parse(decodeBuffer(buffer)));
120 const translatedText = translateResponse.text;
86121
87 response.setHeader('Content-Type', 'text/plain; charset=utf-8');122 response.setHeader('Content-Type', 'text/plain; charset=utf-8');
88 console.log('Translated text: ' + translatedText);123 console.log('Translated text: ' + translatedText);