Merge branch 'SillyTavern:release' into enhancement-make-buttons-scrollable
Signed| @@ -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", |
| @@ -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", |
| @@ -1397,8 +1397,7 @@ | |||
| 1397 | </div> | 1397 | </div> |
| 1398 | </div> | 1398 | </div> |
| 1399 | 1399 | ||
| 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> |
| @@ -3136,7 +3136,8 @@ class StreamingProcessor { | |||
| 3136 | 3136 | ||
| 3137 | //console.log("Generated text size:", text.length, text) | 3137 | //console.log("Generated text size:", text.length, text) |
| 3138 | 3138 | ||
| 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 | } |
| 4585 | 4586 | ||
| 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 | } |
| 4770 | 4772 | ||
| 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 located | 7187 | // 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)); |
| 7184 | 7191 | ||
| @@ -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, |
| @@ -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'); |
| @@ -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> |
| @@ -9,6 +9,8 @@ var langs; | |||
| 9 | // eslint-disable-next-line prefer-const | 9 | // eslint-disable-next-line prefer-const |
| 10 | var localeData; | 10 | var localeData; |
| 11 | 11 | ||
| 12 | export const getCurrentLocale = () => localeFile; | ||
| 13 | |||
| 12 | /** | 14 | /** |
| 13 | * An observer that will check if any new i18n elements are added to the document | 15 | * 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 | } |
| 216 | 218 | ||
| 217 | export async function initLocales() { | 219 | export 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(); |
| @@ -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 | }; |
| 38 | 39 | ||
| 39 | const INPUT_MAP = { | 40 | const INPUT_MAP = { |
| @@ -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 popup | 2133 | popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup |
| 2127 | 2134 | ||
| 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) { | |||
| 2142 | 2149 | ||
| 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), |
| @@ -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_string | 1323 | // 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 | } |
| @@ -7,6 +7,7 @@ import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; | |||
| 7 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | 7 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| 8 | import { getTagsList } from './tags.js'; | 8 | import { getTagsList } from './tags.js'; |
| 9 | import { groups, selected_group } from './group-chats.js'; | 9 | import { groups, selected_group } from './group-chats.js'; |
| 10 | import { getCurrentLocale } from './i18n.js'; | ||
| 10 | 11 | ||
| 11 | /** | 12 | /** |
| 12 | * Pagination status string template. | 13 | * Pagination status string template. |
| @@ -831,7 +832,7 @@ export function timestampToMoment(timestamp) { | |||
| 831 | } | 832 | } |
| 832 | 833 | ||
| 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(); |
| 835 | 836 | ||
| 836 | dateCache.set(timestamp, objMoment); | 837 | dateCache.set(timestamp, objMoment); |
| 837 | return objMoment; | 838 | return objMoment; |
| @@ -206,6 +206,54 @@ router.post('/searxng', jsonParser, async (request, response) => { | |||
| 206 | } | 206 | } |
| 207 | }); | 207 | }); |
| 208 | 208 | ||
| 209 | router.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 | |||
| 209 | router.post('/visit', jsonParser, async (request, response) => { | 257 | router.post('/visit', jsonParser, async (request, response) => { |
| 210 | try { | 258 | try { |
| 211 | const url = request.body.url; | 259 | const url = request.body.url; |
| @@ -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 | }; |
| 51 | 52 | ||
| 52 | // These are the keys that are safe to expose, even if allowKeysExposure is false | 53 | // These are the keys that are safe to expose, even if allowKeysExposure is false |
| @@ -1,9 +1,10 @@ | |||
| 1 | import https from 'node:https'; | 1 | import https from 'node:https'; |
| 2 | import { createRequire } from 'node:module'; | ||
| 2 | 3 | ||
| 3 | import fetch from 'node-fetch'; | 4 | import fetch from 'node-fetch'; |
| 4 | import express from 'express'; | 5 | import express from 'express'; |
| 5 | import bingTranslateApi from 'bing-translate-api'; | 6 | import bingTranslateApi from 'bing-translate-api'; |
| 6 | import googleTranslateApi from 'google-translate-api-x'; | 7 | import iconv from 'iconv-lite'; |
| 7 | 8 | ||
| 8 | import { readSecret, SECRET_KEYS } from './secrets.js'; | 9 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| 9 | import { getConfigValue, uuidv4 } from '../util.js'; | 10 | import { getConfigValue, uuidv4 } from '../util.js'; |
| @@ -14,6 +15,30 @@ const ONERING_URL_DEFAULT = 'http://127.0.0.1:4990/translate'; | |||
| 14 | 15 | ||
| 15 | export const router = express.Router(); | 16 | export const router = express.Router(); |
| 16 | 17 | ||
| 18 | /** | ||
| 19 | * Get the Google Translate API client. | ||
| 20 | * @returns {import('google-translate-api-browser')} Google Translate API client | ||
| 21 | */ | ||
| 22 | function 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 | */ | ||
| 33 | function 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 | |||
| 17 | router.post('/libre', jsonParser, async (request, response) => { | 42 | router.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) => { | |||
| 81 | 106 | ||
| 82 | console.log('Input text: ' + text); | 107 | console.log('Input text: ' + text); |
| 83 | 108 | ||
| 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; | ||
| 86 | 121 | ||
| 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); |