Translate: Update API key management

d91c6c28cb054c0de36c26187c81389c689c2062

Cohee <18619528+Cohee1207@users.noreply.github.com>

4 files changed, +35 -63Showing whitespace changes
public/scripts/extensions/translate/index.html+8 -2
@@ -28,8 +28,14 @@
2828 <option value="free">Free</option>
2929 <option value="pro">Pro</option>
3030 </select>
3131 <div id="translate_key_button" class="menu_button fa-solid fa-keymenu_button_icon margin0 manage-api-keys"></div>
3232 <div id="translate_url_button"i class="menu_button fa-solid fa-link margin0key"></divi>
33+ <span>Key</span>
34+ </div>
35+ <div id="translate_url_button" class="menu_button menu_button_icon margin0 manage-api-keys">
36+ <i class="fa-solid fa-link"></i>
37+ <span>URL</span>
38+ </div>
3339 </div>
3440 <label data-i18n="ext_translate_target_lang" for="translation_target_language">Target Language</label>
3541 <select id="translation_target_language" name="target_language"></select>
public/scripts/extensions/translate/index.js+11 -58
@@ -10,9 +10,9 @@ import {
1010 updateMessageBlock,
1111} from '../../../script.js';
1212import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
1313import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';
1414import { updateReasoningUI } from '../../reasoning.js';
1515import { findSecret, secret_state, writeSecret } from '../../secrets.js';
1616import { SlashCommand } from '../../slash-commands/SlashCommand.js';
1717import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
1818import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
@@ -152,9 +152,9 @@ const LOCAL_URL = ['libre', 'oneringtranslator', 'deeplx', 'lingva'];
152152function showKeysButton() {
153153 const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider);
154154 const providerOptionalUrl = LOCAL_URL.includes(extension_settings.translate.provider);
155155 $('#translate_key_button').toggle(providerRequiresKey).data('key', extension_settings.translate.provider);
156156 $('#translate_key_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider]));
157157 $('#translate_url_button').toggle(providerOptionalUrl).data('key', extension_settings.translate.provider + '_url');
158158 $('#translate_url_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider + '_url']));
159159 $('#deepl_api_endpoint').toggle(extension_settings.translate.provider === 'deepl');
160160}
@@ -749,63 +749,16 @@ jQuery(async () => {
749749 saveSettingsDebounced();
750750 });
751751 $(document).on('click', '.mes_translate', onMessageTranslateClick);
752- $('#translate_key_button').on('click', async () => {
753- const optionText = $('#translation_provider option:selected').text();
754- const key = await callGenericPopup(`<h3>${optionText} API Key</h3>`, POPUP_TYPE.INPUT, '', {
755- customButtons: [{
756- text: 'Remove Key',
757- appendAtEnd: true,
758- result: POPUP_RESULT.NEGATIVE,
759- action: async () => {
760- await writeSecret(extension_settings.translate.provider, '');
761- toastr.success('API Key removed');
762- $('#translate_key_button').toggleClass('success', !!secret_state[extension_settings.translate.provider]);
763- },
764- }],
765- });
766752
767- if (!key) {
753+ [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach((eventType) => {
768- return;
754+ eventSource.on(eventType, (/** @type {string} */ key) => {
755+ if (key === extension_settings.translate.provider) {
756+ $('#translate_key_button').toggleClass('success', !!secret_state[extension_settings.translate.provider]);
769757 }
770-
758+ if (key === `${extension_settings.translate.provider}_url`) {
771- await writeSecret(extension_settings.translate.provider, String(key));
759+ $('#translate_url_button').toggleClass('success', !!secret_state[`${extension_settings.translate.provider}_url`]);
772- toastr.success('API Key saved');
773- $('#translate_key_button').addClass('success');
774- });
775- $('#translate_url_button').on('click', async () => {
776- const optionText = $('#translation_provider option:selected').text();
777- const exampleURLs = {
778- 'libre': 'http://127.0.0.1:5000/translate',
779- 'lingva': 'https://lingva.ml/api/v1',
780- 'oneringtranslator': 'http://127.0.0.1:4990/translate',
781- 'deeplx': 'http://127.0.0.1:1188/translate',
782- };
783- const popupText = `<h3>${optionText} API URL</h3><i>Example: <tt>${String(exampleURLs[extension_settings.translate.provider])}</tt></i>`;
784-
785- const secretKey = extension_settings.translate.provider + '_url';
786- const savedUrl = secret_state[secretKey] ? await findSecret(secretKey) : '';
787-
788- const url = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedUrl, {
789- customButtons: [{
790- text: 'Remove URL',
791- appendAtEnd: true,
792- result: POPUP_RESULT.NEGATIVE,
793- action: async () => {
794- await writeSecret(secretKey, '');
795- toastr.success('API URL removed');
796- $('#translate_url_button').toggleClass('success', !!secret_state[secretKey]);
797- },
798- }],
799- });
800-
801- if (!url) {
802- return;
803760 }
804-
761+ });
805- await writeSecret(secretKey, String(url));
806-
807- toastr.success('API URL saved');
808- $('#translate_url_button').addClass('success');
809762 });
810763
811764 loadSettings();
public/scripts/secrets.js+12 -0
@@ -23,10 +23,16 @@ export const SECRET_KEYS = {
2323 OPENAI: 'api_key_openai',
2424 NOVEL: 'api_key_novel',
2525 CLAUDE: 'api_key_claude',
26+ DEEPL: 'deepl',
27+ LIBRE: 'libre',
28+ LIBRE_URL: 'libre_url',
29+ LINGVA_URL: 'lingva_url',
2630 OPENROUTER: 'api_key_openrouter',
2731 SCALE: 'api_key_scale',
2832 AI21: 'api_key_ai21',
2933 SCALE_COOKIE: 'scale_cookie',
34+ ONERING_URL: 'oneringtranslator_url',
35+ DEEPLX_URL: 'deeplx_url',
3036 MAKERSUITE: 'api_key_makersuite',
3137 VERTEXAI: 'api_key_vertexai',
3238 SERPAPI: 'api_key_serpapi',
@@ -104,6 +110,12 @@ const FRIENDLY_NAMES = {
104110 [SECRET_KEYS.FALAI]: 'FAL.AI',
105111 [SECRET_KEYS.AZURE_TTS]: 'Azure TTS',
106112 [SECRET_KEYS.AIMLAPI]: 'AI/ML API',
113+ [SECRET_KEYS.DEEPL]: 'DeepL',
114+ [SECRET_KEYS.LIBRE]: 'LibreTranslate',
115+ [SECRET_KEYS.LIBRE_URL]: 'LibreTranslate Endpoint (e.g. http://127.0.0.1:5000/translate)',
116+ [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)',
117+ [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)',
118+ [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)',
107119};
108120
109121const INPUT_MAP = {
src/endpoints/secrets.js+4 -3
@@ -162,11 +162,12 @@ export class SecretManager {
162162 /**
163163 * Masks a secret value with asterisks in the middle
164164 * @param {string} value The secret value to mask
165+ * @param {string} key The secret key
165166 * @returns {string} A masked version of the value for peeking
166167 */
167168 getMaskedValue(value, key) {
168169 // No masking if exposure is allowed
169170 if (allowKeysExposure || EXPORTABLE_KEYS.includes(key)) {
170171 return value;
171172 }
172173 const threshold = 10;
@@ -340,7 +341,7 @@ export class SecretManager {
340341 if (value && Array.isArray(value) && value.length > 0) {
341342 state[key] = value.map(secret => ({
342343 id: secret.id,
343344 value: this.getMaskedValue(secret.value, key),
344345 label: secret.label,
345346 active: secret.active,
346347 }));