Refactor API key management in extensions
| @@ -768,7 +768,7 @@ jQuery(async () => { | |||
| 768 | return; | 768 | return; |
| 769 | } | 769 | } |
| 770 | 770 | ||
| 771 | await writeSecret(extension_settings.translate.provider, key); | 771 | await writeSecret(extension_settings.translate.provider, String(key)); |
| 772 | toastr.success('API Key saved'); | 772 | toastr.success('API Key saved'); |
| 773 | $('#translate_key_button').addClass('success'); | 773 | $('#translate_key_button').addClass('success'); |
| 774 | }); | 774 | }); |
| @@ -802,7 +802,7 @@ jQuery(async () => { | |||
| 802 | return; | 802 | return; |
| 803 | } | 803 | } |
| 804 | 804 | ||
| 805 | await writeSecret(secretKey, url); | 805 | await writeSecret(secretKey, String(url)); |
| 806 | 806 | ||
| 807 | toastr.success('API URL saved'); | 807 | toastr.success('API URL saved'); |
| 808 | $('#translate_url_button').addClass('success'); | 808 | $('#translate_url_button').addClass('success'); |
| @@ -1,6 +1,5 @@ | |||
| 1 | import { getRequestHeaders } from '../../../script.js'; | 1 | import { event_types, eventSource, getRequestHeaders } from '../../../script.js'; |
| 2 | import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js'; | 2 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 3 | import { SECRET_KEYS, findSecret, secret_state, writeSecret } from '../../secrets.js'; | ||
| 4 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; | 3 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; |
| 5 | export { AzureTtsProvider }; | 4 | export { AzureTtsProvider }; |
| 6 | 5 | ||
| @@ -26,7 +25,7 @@ class AzureTtsProvider { | |||
| 26 | <h4 for="azure_tts_key" class="flex1 margin0"> | 25 | <h4 for="azure_tts_key" class="flex1 margin0"> |
| 27 | <a href="https://portal.azure.com/" target="_blank">Azure TTS Key</a> | 26 | <a href="https://portal.azure.com/" target="_blank">Azure TTS Key</a> |
| 28 | </h4> | 27 | </h4> |
| 29 | <div id="azure_tts_key" class="menu_button menu_button_icon"> | 28 | <div id="azure_tts_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_azure_tts"> |
| 30 | <i class="fa-solid fa-key"></i> | 29 | <i class="fa-solid fa-key"></i> |
| 31 | <span>Click to set</span> | 30 | <span>Click to set</span> |
| 32 | </div> | 31 | </div> |
| @@ -39,6 +38,20 @@ class AzureTtsProvider { | |||
| 39 | return html; | 38 | return html; |
| 40 | } | 39 | } |
| 41 | 40 | ||
| 41 | constructor() { | ||
| 42 | this.handler = async function (/** @type {string} */ key) { | ||
| 43 | if (key !== SECRET_KEYS.AZURE_TTS) return; | ||
| 44 | $('#azure_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.AZURE_TTS]); | ||
| 45 | await this.onRefreshClick(); | ||
| 46 | }.bind(this); | ||
| 47 | } | ||
| 48 | |||
| 49 | dispose() { | ||
| 50 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { | ||
| 51 | eventSource.removeListener(event, this.handler); | ||
| 52 | }); | ||
| 53 | } | ||
| 54 | |||
| 42 | onSettingsChange() { | 55 | onSettingsChange() { |
| 43 | // Update dynamically | 56 | // Update dynamically |
| 44 | this.settings.region = String($('#azure_tts_region').val()); | 57 | this.settings.region = String($('#azure_tts_region').val()); |
| @@ -65,34 +78,9 @@ class AzureTtsProvider { | |||
| 65 | } | 78 | } |
| 66 | 79 | ||
| 67 | $('#azure_tts_region').val(this.settings.region).on('input', () => this.onSettingsChange()); | 80 | $('#azure_tts_region').val(this.settings.region).on('input', () => this.onSettingsChange()); |
| 68 | $('#azure_tts_key').toggleClass('success', secret_state[SECRET_KEYS.AZURE_TTS]); | ||
| 69 | $('#azure_tts_key').on('click', async () => { | ||
| 70 | const popupText = 'Azure TTS API Key'; | ||
| 71 | const savedKey = secret_state[SECRET_KEYS.AZURE_TTS] ? await findSecret(SECRET_KEYS.AZURE_TTS) : ''; | ||
| 72 | |||
| 73 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey, { | ||
| 74 | customButtons: [{ | ||
| 75 | text: 'Remove Key', | ||
| 76 | appendAtEnd: true, | ||
| 77 | result: POPUP_RESULT.NEGATIVE, | ||
| 78 | action: async () => { | ||
| 79 | await writeSecret(SECRET_KEYS.AZURE_TTS, ''); | ||
| 80 | $('#azure_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.AZURE_TTS]); | 81 | $('#azure_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.AZURE_TTS]); |
| 81 | toastr.success('API Key removed'); | 82 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { |
| 82 | await this.onRefreshClick(); | 83 | eventSource.on(event, this.handler); |
| 83 | }, | ||
| 84 | }], | ||
| 85 | }); | ||
| 86 | |||
| 87 | if (!key) { | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | await writeSecret(SECRET_KEYS.AZURE_TTS, String(key)); | ||
| 92 | |||
| 93 | toastr.success('API Key saved'); | ||
| 94 | $('#azure_tts_key').addClass('success'); | ||
| 95 | await this.onRefreshClick(); | ||
| 96 | }); | 84 | }); |
| 97 | 85 | ||
| 98 | try { | 86 | try { |
| @@ -1,6 +1,5 @@ | |||
| 1 | import { getRequestHeaders } from '../../../script.js'; | 1 | import { event_types, eventSource, getRequestHeaders } from '../../../script.js'; |
| 2 | import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; | 2 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 3 | import { findSecret, SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; | ||
| 4 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; | 3 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; |
| 5 | 4 | ||
| 6 | export { OpenAICompatibleTtsProvider }; | 5 | export { OpenAICompatibleTtsProvider }; |
| @@ -27,7 +26,7 @@ class OpenAICompatibleTtsProvider { | |||
| 27 | <div class="flex1"> | 26 | <div class="flex1"> |
| 28 | <input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="500" value="${this.defaultSettings.provider_endpoint}"/> | 27 | <input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="500" value="${this.defaultSettings.provider_endpoint}"/> |
| 29 | </div> | 28 | </div> |
| 30 | <div id="openai_compatible_tts_key" class="menu_button menu_button_icon"> | 29 | <div id="openai_compatible_tts_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_custom_openai_tts"> |
| 31 | <i class="fa-solid fa-key"></i> | 30 | <i class="fa-solid fa-key"></i> |
| 32 | <span>API Key</span> | 31 | <span>API Key</span> |
| 33 | </div> | 32 | </div> |
| @@ -41,6 +40,20 @@ class OpenAICompatibleTtsProvider { | |||
| 41 | return html; | 40 | return html; |
| 42 | } | 41 | } |
| 43 | 42 | ||
| 43 | constructor() { | ||
| 44 | this.handler = async function (/** @type {string} */ key) { | ||
| 45 | if (key !== SECRET_KEYS.CUSTOM_OPENAI_TTS) return; | ||
| 46 | $('#openai_compatible_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]); | ||
| 47 | await this.onRefreshClick(); | ||
| 48 | }.bind(this); | ||
| 49 | } | ||
| 50 | |||
| 51 | dispose() { | ||
| 52 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { | ||
| 53 | eventSource.removeListener(event, this.handler); | ||
| 54 | }); | ||
| 55 | } | ||
| 56 | |||
| 44 | async loadSettings(settings) { | 57 | async loadSettings(settings) { |
| 45 | // Populate Provider UI given input settings | 58 | // Populate Provider UI given input settings |
| 46 | if (Object.keys(settings).length == 0) { | 59 | if (Object.keys(settings).length == 0) { |
| @@ -74,34 +87,9 @@ class OpenAICompatibleTtsProvider { | |||
| 74 | 87 | ||
| 75 | $('#openai_compatible_tts_speed_output').text(this.settings.speed); | 88 | $('#openai_compatible_tts_speed_output').text(this.settings.speed); |
| 76 | 89 | ||
| 77 | $('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]); | ||
| 78 | $('#openai_compatible_tts_key').on('click', async () => { | ||
| 79 | const popupText = 'OpenAI-compatible TTS API Key'; | ||
| 80 | const savedKey = secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS] ? await findSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS) : ''; | ||
| 81 | |||
| 82 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey, { | ||
| 83 | customButtons: [{ | ||
| 84 | text: 'Remove Key', | ||
| 85 | appendAtEnd: true, | ||
| 86 | result: POPUP_RESULT.NEGATIVE, | ||
| 87 | action: async () => { | ||
| 88 | await writeSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS, ''); | ||
| 89 | $('#openai_compatible_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]); | 90 | $('#openai_compatible_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]); |
| 90 | toastr.success('API Key removed'); | 91 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { |
| 91 | await this.onRefreshClick(); | 92 | eventSource.on(event, this.handler); |
| 92 | }, | ||
| 93 | }], | ||
| 94 | }); | ||
| 95 | |||
| 96 | if (!key) { | ||
| 97 | return; | ||
| 98 | } | ||
| 99 | |||
| 100 | await writeSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS, String(key)); | ||
| 101 | |||
| 102 | toastr.success('API Key saved'); | ||
| 103 | $('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]); | ||
| 104 | await this.onRefreshClick(); | ||
| 105 | }); | 93 | }); |
| 106 | 94 | ||
| 107 | await this.checkReady(); | 95 | await this.checkReady(); |
| @@ -22,7 +22,7 @@ import { | |||
| 22 | openThirdPartyExtensionMenu, | 22 | openThirdPartyExtensionMenu, |
| 23 | } from '../../extensions.js'; | 23 | } from '../../extensions.js'; |
| 24 | import { collapseNewlines, registerDebugFunction } from '../../power-user.js'; | 24 | import { collapseNewlines, registerDebugFunction } from '../../power-user.js'; |
| 25 | import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; | 25 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 26 | import { getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment } from '../../chats.js'; | 26 | import { getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment } from '../../chats.js'; |
| 27 | import { debounce, getStringHash as calculateHash, waitUntilCondition, onlyUnique, splitRecursive, trimToStartSentence, trimToEndSentence, escapeHtml } from '../../utils.js'; | 27 | import { debounce, getStringHash as calculateHash, waitUntilCondition, onlyUnique, splitRecursive, trimToStartSentence, trimToEndSentence, escapeHtml } from '../../utils.js'; |
| 28 | import { debounce_timeout } from '../../constants.js'; | 28 | import { debounce_timeout } from '../../constants.js'; |
| @@ -33,7 +33,6 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | |||
| 33 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 33 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 34 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; | 34 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 35 | import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; | 35 | import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js'; |
| 36 | import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; | ||
| 37 | import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; | 36 | import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js'; |
| 38 | import { WebLlmVectorProvider } from './webllm.js'; | 37 | import { WebLlmVectorProvider } from './webllm.js'; |
| 39 | import { removeReasoningFromString } from '../../reasoning.js'; | 38 | import { removeReasoningFromString } from '../../reasoning.js'; |
| @@ -1494,32 +1493,6 @@ jQuery(async () => { | |||
| 1494 | Object.assign(extension_settings.vectors, settings); | 1493 | Object.assign(extension_settings.vectors, settings); |
| 1495 | saveSettingsDebounced(); | 1494 | saveSettingsDebounced(); |
| 1496 | }); | 1495 | }); |
| 1497 | $('#api_key_nomicai').on('click', async () => { | ||
| 1498 | const popupText = 'NomicAI API Key:'; | ||
| 1499 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', { | ||
| 1500 | customButtons: [{ | ||
| 1501 | text: 'Remove Key', | ||
| 1502 | appendAtEnd: true, | ||
| 1503 | result: POPUP_RESULT.NEGATIVE, | ||
| 1504 | action: async () => { | ||
| 1505 | await writeSecret(SECRET_KEYS.NOMICAI, ''); | ||
| 1506 | toastr.success('API Key removed'); | ||
| 1507 | $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]); | ||
| 1508 | saveSettingsDebounced(); | ||
| 1509 | }, | ||
| 1510 | }], | ||
| 1511 | }); | ||
| 1512 | |||
| 1513 | if (!key) { | ||
| 1514 | return; | ||
| 1515 | } | ||
| 1516 | |||
| 1517 | await writeSecret(SECRET_KEYS.NOMICAI, String(key)); | ||
| 1518 | $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]); | ||
| 1519 | |||
| 1520 | toastr.success('API Key saved'); | ||
| 1521 | saveSettingsDebounced(); | ||
| 1522 | }); | ||
| 1523 | $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => { | 1496 | $('#vectors_togetherai_model').val(settings.togetherai_model).on('change', () => { |
| 1524 | settings.togetherai_model = String($('#vectors_togetherai_model').val()); | 1497 | settings.togetherai_model = String($('#vectors_togetherai_model').val()); |
| 1525 | Object.assign(extension_settings.vectors, settings); | 1498 | Object.assign(extension_settings.vectors, settings); |
| @@ -1777,6 +1750,12 @@ jQuery(async () => { | |||
| 1777 | }); | 1750 | }); |
| 1778 | 1751 | ||
| 1779 | $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]); | 1752 | $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]); |
| 1753 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { | ||
| 1754 | eventSource.on(event, (/** @type {string} */ key)=> { | ||
| 1755 | if (key !== SECRET_KEYS.NOMICAI) return; | ||
| 1756 | $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]); | ||
| 1757 | }); | ||
| 1758 | }); | ||
| 1780 | 1759 | ||
| 1781 | toggleSettings(); | 1760 | toggleSettings(); |
| 1782 | eventSource.on(event_types.MESSAGE_DELETED, onChatEvent); | 1761 | eventSource.on(event_types.MESSAGE_DELETED, onChatEvent); |
| @@ -145,7 +145,7 @@ | |||
| 145 | <label for="api_key_nomicai" class="flex1"> | 145 | <label for="api_key_nomicai" class="flex1"> |
| 146 | <span data-i18n="NomicAI API Key">NomicAI API Key</span> | 146 | <span data-i18n="NomicAI API Key">NomicAI API Key</span> |
| 147 | </label> | 147 | </label> |
| 148 | <div id="api_key_nomicai" class="menu_button menu_button_icon"> | 148 | <div id="api_key_nomicai" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_nomicai"> |
| 149 | <i class="fa-solid fa-key"></i> | 149 | <i class="fa-solid fa-key"></i> |
| 150 | <span data-i18n="Click to set">Click to set</span> | 150 | <span data-i18n="Click to set">Click to set</span> |
| 151 | </div> | 151 | </div> |