| import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js'; |
| import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js'; |
| import { t } from '../i18n.js'; |
| import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js'; |
| import { SECRET_KEYS, secret_state } from '../secrets.js'; |
| import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js'; |
| import { getTokenCountAsync } from '../tokenizers.js'; |
| import { createThumbnail, isValidUrl } from '../utils.js'; |
|
|
| |
| |
| |
| |
| |
| |
| export async function getMultimodalCaption(base64Img, prompt) { |
| const useReverseProxy = |
| (['openai', 'anthropic', 'google', 'mistral', 'vertexai', 'xai'].includes(extension_settings.caption.multimodal_api)) |
| && extension_settings.caption.allow_reverse_proxy |
| && oai_settings.reverse_proxy |
| && isValidUrl(oai_settings.reverse_proxy); |
|
|
| throwIfInvalidModel(useReverseProxy); |
|
|
| |
| |
| const isOllama = extension_settings.caption.multimodal_api === 'ollama'; |
| const isLlamaCpp = extension_settings.caption.multimodal_api === 'llamacpp'; |
| const isCustom = extension_settings.caption.multimodal_api === 'custom'; |
| const isOoba = extension_settings.caption.multimodal_api === 'ooba'; |
| const isKoboldCpp = extension_settings.caption.multimodal_api === 'koboldcpp'; |
| const isVllm = extension_settings.caption.multimodal_api === 'vllm'; |
| const base64Bytes = base64Img.length * 0.75; |
| const compressionLimit = 2 * 1024 * 1024; |
| const safeMimeTypes = ['image/jpeg', 'image/png', 'image/webp']; |
| const mimeType = base64Img?.split(';')?.[0]?.split(':')?.[1] || 'image/jpeg'; |
| const isImage = mimeType.startsWith('image/'); |
| const thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api); |
| if ((isImage && thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) { |
| const maxSide = 2048; |
| base64Img = await createThumbnail(base64Img, maxSide, maxSide); |
| } else if (isImage && !safeMimeTypes.includes(mimeType)) { |
| base64Img = await createThumbnail(base64Img, null, null); |
| } |
| if (isOllama && base64Img.startsWith('data:image/')) { |
| base64Img = base64Img.split(',')[1]; |
| } |
|
|
| const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : ''; |
| const proxyPassword = useReverseProxy ? oai_settings.proxy_password : ''; |
|
|
| const requestBody = { |
| image: base64Img, |
| prompt: prompt, |
| reverse_proxy: proxyUrl, |
| proxy_password: proxyPassword, |
| api: extension_settings.caption.multimodal_api || 'openai', |
| model: extension_settings.caption.multimodal_model || 'gpt-4-turbo', |
| }; |
|
|
| |
| if (extension_settings.caption.multimodal_api === 'vertexai') { |
| requestBody.vertexai_auth_mode = oai_settings.vertexai_auth_mode; |
| requestBody.vertexai_region = oai_settings.vertexai_region; |
| requestBody.vertexai_express_project_id = oai_settings.vertexai_express_project_id; |
| } |
|
|
| if (isOllama) { |
| if (extension_settings.caption.multimodal_model === 'ollama_current') { |
| requestBody.model = textgenerationwebui_settings.ollama_model; |
| } |
|
|
| if (extension_settings.caption.multimodal_model === 'ollama_custom') { |
| requestBody.model = extension_settings.caption.ollama_custom_model; |
| } |
|
|
| requestBody.server_url = extension_settings.caption.alt_endpoint_enabled |
| ? extension_settings.caption.alt_endpoint_url |
| : textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]; |
| } |
|
|
| if (isVllm) { |
| if (extension_settings.caption.multimodal_model === 'vllm_current') { |
| requestBody.model = textgenerationwebui_settings.vllm_model; |
| } |
|
|
| requestBody.server_url = extension_settings.caption.alt_endpoint_enabled |
| ? extension_settings.caption.alt_endpoint_url |
| : textgenerationwebui_settings.server_urls[textgen_types.VLLM]; |
| } |
|
|
| if (isLlamaCpp) { |
| requestBody.server_url = extension_settings.caption.alt_endpoint_enabled |
| ? extension_settings.caption.alt_endpoint_url |
| : textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]; |
| } |
|
|
| if (isOoba) { |
| requestBody.server_url = extension_settings.caption.alt_endpoint_enabled |
| ? extension_settings.caption.alt_endpoint_url |
| : textgenerationwebui_settings.server_urls[textgen_types.OOBA]; |
| } |
|
|
| if (isKoboldCpp) { |
| requestBody.server_url = extension_settings.caption.alt_endpoint_enabled |
| ? extension_settings.caption.alt_endpoint_url |
| : textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]; |
| } |
|
|
| if (isCustom) { |
| requestBody.server_url = oai_settings.custom_url; |
| requestBody.model = oai_settings.custom_model || 'gpt-4-turbo'; |
| requestBody.custom_include_headers = oai_settings.custom_include_headers; |
| requestBody.custom_include_body = oai_settings.custom_include_body; |
| requestBody.custom_exclude_body = oai_settings.custom_exclude_body; |
| } |
|
|
| if (extension_settings.caption.multimodal_api === 'zai') { |
| requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON; |
| } |
|
|
| function getEndpointUrl() { |
| switch (extension_settings.caption.multimodal_api) { |
| case 'google': |
| case 'vertexai': |
| return '/api/google/caption-image'; |
| case 'anthropic': |
| return '/api/anthropic/caption-image'; |
| case 'ollama': |
| return '/api/backends/text-completions/ollama/caption-image'; |
| default: |
| return '/api/openai/caption-image'; |
| } |
| } |
|
|
| const apiResult = await fetch(getEndpointUrl(), { |
| method: 'POST', |
| headers: getRequestHeaders(), |
| body: JSON.stringify(requestBody), |
| }); |
|
|
| if (!apiResult.ok) { |
| throw new Error('Failed to caption image via Multimodal API.'); |
| } |
|
|
| const { caption } = await apiResult.json(); |
| return String(caption).trim(); |
| } |
|
|
| function throwIfInvalidModel(useReverseProxy) { |
| const altEndpointEnabled = extension_settings.caption.alt_endpoint_enabled; |
| const altEndpointUrl = extension_settings.caption.alt_endpoint_url; |
| const multimodalModel = extension_settings.caption.multimodal_model; |
| const multimodalApi = extension_settings.caption.multimodal_api; |
|
|
| if (altEndpointEnabled && ['llamacpp', 'ooba', 'koboldcpp', 'vllm', 'ollama'].includes(multimodalApi) && !altEndpointUrl) { |
| throw new Error('Secondary endpoint URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'openai' && !secret_state[SECRET_KEYS.OPENAI] && !useReverseProxy) { |
| throw new Error('OpenAI API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'openrouter' && !secret_state[SECRET_KEYS.OPENROUTER]) { |
| throw new Error('OpenRouter API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'anthropic' && !secret_state[SECRET_KEYS.CLAUDE] && !useReverseProxy) { |
| throw new Error('Anthropic (Claude) API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'groq' && !secret_state[SECRET_KEYS.GROQ]) { |
| throw new Error('Groq API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'google' && !secret_state[SECRET_KEYS.MAKERSUITE] && !useReverseProxy) { |
| throw new Error('Google AI Studio API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'vertexai' && !useReverseProxy) { |
| |
| const authMode = oai_settings.vertexai_auth_mode || 'express'; |
|
|
| if (authMode === 'express') { |
| |
| if (!secret_state[SECRET_KEYS.VERTEXAI]) { |
| throw new Error('Google Vertex AI API key is not set for Express mode.'); |
| } |
| } else if (authMode === 'full') { |
| |
| if (!secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) { |
| throw new Error('Service Account JSON is required for Vertex AI Full mode. Please validate and save your Service Account JSON.'); |
| } |
| if (!oai_settings.vertexai_region) { |
| throw new Error('Region is required for Vertex AI Full mode.'); |
| } |
| } |
| } |
|
|
| if (multimodalApi === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) { |
| throw new Error('Mistral AI API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'cohere' && !secret_state[SECRET_KEYS.COHERE]) { |
| throw new Error('Cohere API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'xai' && !secret_state[SECRET_KEYS.XAI] && !useReverseProxy) { |
| throw new Error('xAI API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] && !altEndpointEnabled) { |
| throw new Error('Ollama server URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'ollama' && multimodalModel === 'ollama_current' && !textgenerationwebui_settings.ollama_model) { |
| throw new Error('Ollama model is not set.'); |
| } |
|
|
| if (multimodalApi === 'ollama' && multimodalModel === 'ollama_custom' && !extension_settings.caption.ollama_custom_model) { |
| throw new Error('Ollama custom model tag is not set.'); |
| } |
|
|
| if (multimodalApi === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP] && !altEndpointEnabled) { |
| throw new Error('LlamaCPP server URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'ooba' && !textgenerationwebui_settings.server_urls[textgen_types.OOBA] && !altEndpointEnabled) { |
| throw new Error('Text Generation WebUI server URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] && !altEndpointEnabled) { |
| throw new Error('KoboldCpp server URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] && !altEndpointEnabled) { |
| throw new Error('vLLM server URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'vllm' && multimodalModel === 'vllm_current' && !textgenerationwebui_settings.vllm_model) { |
| throw new Error('vLLM model is not set.'); |
| } |
|
|
| if (multimodalApi === 'custom' && !oai_settings.custom_url) { |
| throw new Error('Custom API URL is not set.'); |
| } |
|
|
| if (multimodalApi === 'aimlapi' && !secret_state[SECRET_KEYS.AIMLAPI]) { |
| throw new Error('AI/ML API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'moonshot' && !secret_state[SECRET_KEYS.MOONSHOT]) { |
| throw new Error('Moonshot AI API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT]) { |
| throw new Error('NanoGPT API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB]) { |
| throw new Error('Electron Hub API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'chutes' && !secret_state[SECRET_KEYS.CHUTES]) { |
| throw new Error('Chutes API key is not set.'); |
| } |
|
|
| if (multimodalApi === 'zai' && !secret_state[SECRET_KEYS.ZAI]) { |
| throw new Error('Z.AI API key is not set.'); |
| } |
| } |
|
|
| |
| |
| |
| |
| export function isWebLlmSupported() { |
| if (!('gpu' in navigator)) { |
| const warningKey = 'webllm_browser_warning_shown'; |
| if (!sessionStorage.getItem(warningKey)) { |
| toastr.error('Your browser does not support the WebGPU API. Please use a different browser.', 'WebLLM', { |
| preventDuplicates: true, |
| timeOut: 0, |
| extendedTimeOut: 0, |
| }); |
| sessionStorage.setItem(warningKey, '1'); |
| } |
| return false; |
| } |
|
|
| if (!('llm' in SillyTavern)) { |
| const warningKey = 'webllm_extension_warning_shown'; |
| if (!sessionStorage.getItem(warningKey)) { |
| toastr.error('WebLLM extension is not installed. Click here to install it.', 'WebLLM', { |
| timeOut: 0, |
| extendedTimeOut: 0, |
| preventDuplicates: true, |
| onclick: () => openThirdPartyExtensionMenu('https://github.com/SillyTavern/Extension-WebLLM'), |
| }); |
| sessionStorage.setItem(warningKey, '1'); |
| } |
| return false; |
| } |
|
|
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function generateWebLlmChatPrompt(messages, params = {}) { |
| if (!isWebLlmSupported()) { |
| throw new Error('WebLLM extension is not installed.'); |
| } |
|
|
| console.debug('WebLLM chat completion request:', messages, params); |
| const engine = SillyTavern.llm; |
| const response = await engine.generateChatPrompt(messages, params); |
| console.debug('WebLLM chat completion response:', response); |
| return response; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function countWebLlmTokens(text) { |
| if (!isWebLlmSupported()) { |
| throw new Error('WebLLM extension is not installed.'); |
| } |
|
|
| try { |
| const engine = SillyTavern.llm; |
| const response = await engine.countTokens(text); |
| return response; |
| } catch (error) { |
| |
| return await getTokenCountAsync(text); |
| } |
| } |
|
|
| |
| |
| |
| |
| export async function getWebLlmContextSize() { |
| if (!isWebLlmSupported()) { |
| throw new Error('WebLLM extension is not installed.'); |
| } |
|
|
| const engine = SillyTavern.llm; |
| await engine.loadModel(); |
| const model = await engine.getCurrentModelInfo(); |
| return model?.context_size; |
| } |
|
|
| |
| |
| |
| export class ConnectionManagerRequestService { |
| static defaultSendRequestParams = { |
| stream: false, |
| signal: null, |
| extractData: true, |
| includePreset: true, |
| includeInstruct: true, |
| instructSettings: {}, |
| }; |
|
|
| static getAllowedTypes() { |
| return { |
| openai: t`Chat Completion`, |
| textgenerationwebui: t`Text Completion`, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams, overridePayload = {}) { |
| const { stream, signal, extractData, includePreset, includeInstruct, instructSettings } = { ...this.defaultSendRequestParams, ...custom }; |
|
|
| const context = SillyTavern.getContext(); |
| if (context.extensionSettings.disabledExtensions.includes('connection-manager')) { |
| throw new Error('Connection Manager is not available'); |
| } |
|
|
| const profile = this.getProfile(profileId); |
| const selectedApiMap = this.validateProfile(profile); |
|
|
| try { |
| switch (selectedApiMap.selected) { |
| case 'openai': { |
| if (!selectedApiMap.source) { |
| throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`); |
| } |
|
|
| const proxyPreset = proxies.find((p) => p.name === profile.proxy); |
|
|
| const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }]; |
| return await context.ChatCompletionService.processRequest({ |
| stream, |
| messages, |
| max_tokens: maxTokens, |
| model: profile.model, |
| chat_completion_source: selectedApiMap.source, |
| custom_url: profile['api-url'], |
| vertexai_region: profile['api-url'], |
| zai_endpoint: profile['api-url'], |
| reverse_proxy: proxyPreset?.url, |
| proxy_password: proxyPreset?.password, |
| custom_prompt_post_processing: profile['prompt-post-processing'], |
| ...overridePayload, |
| }, { |
| presetName: includePreset ? profile.preset : undefined, |
| }, extractData, signal); |
| } |
| case 'textgenerationwebui': { |
| if (!selectedApiMap.type) { |
| throw new Error(`API type ${selectedApiMap.selected} does not support text completions`); |
| } |
|
|
| return await context.TextCompletionService.processRequest({ |
| stream, |
| prompt, |
| max_tokens: maxTokens, |
| model: profile.model, |
| api_type: selectedApiMap.type, |
| api_server: profile['api-url'], |
| ...overridePayload, |
| }, { |
| instructName: includeInstruct ? profile.instruct : undefined, |
| presetName: includePreset ? profile.preset : undefined, |
| instructSettings: includeInstruct ? instructSettings : undefined, |
| }, extractData, signal); |
| } |
| default: { |
| throw new Error(`Unknown API type ${selectedApiMap.selected}`); |
| } |
| } |
| } catch (error) { |
| throw new Error('API request failed', { cause: error }); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static constructPrompt(prompt, profileId, instructSettings = null) { |
| const context = SillyTavern.getContext(); |
| const profile = this.getProfile(profileId); |
| const selectedApiMap = this.validateProfile(profile); |
| const instructName = profile.instruct; |
|
|
| switch (selectedApiMap.selected) { |
| case 'openai': { |
| if (!selectedApiMap.source) { |
| throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`); |
| } |
| return prompt; |
| } |
| case 'textgenerationwebui': { |
| if (!selectedApiMap.type) { |
| throw new Error(`API type ${selectedApiMap.selected} does not support text completions`); |
| } |
| return context.TextCompletionService.constructPrompt(prompt, instructName, instructSettings); |
| } |
| default: { |
| throw new Error(`Unknown API type ${selectedApiMap.selected}`); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| static getSupportedProfiles() { |
| const context = SillyTavern.getContext(); |
| if (context.extensionSettings.disabledExtensions.includes('connection-manager')) { |
| throw new Error('Connection Manager is not available'); |
| } |
|
|
| const profiles = context.extensionSettings.connectionManager.profiles; |
| return profiles.filter((p) => this.isProfileSupported(p)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static getProfile(profileId) { |
| const profile = SillyTavern.getContext().extensionSettings.connectionManager.profiles.find((p) => p.id === profileId); |
| if (!profile) throw new Error(`Profile not found (ID: ${profileId})`); |
| return profile; |
| } |
|
|
| |
| |
| |
| |
| static isProfileSupported(profile) { |
| if (!profile || !profile.api) { |
| return false; |
| } |
|
|
| const apiMap = CONNECT_API_MAP[profile.api]; |
| if (!Object.hasOwn(this.getAllowedTypes(), apiMap.selected)) { |
| return false; |
| } |
|
|
| |
| switch (apiMap.selected) { |
| case 'openai': |
| return !!apiMap.source; |
| case 'textgenerationwebui': |
| return !!apiMap.type; |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| static validateProfile(profile) { |
| if (!profile) { |
| throw new Error('Could not find profile.'); |
| } |
| if (!profile.api) { |
| throw new Error('Select a connection profile that has an API'); |
| } |
|
|
| const context = SillyTavern.getContext(); |
| const selectedApiMap = context.CONNECT_API_MAP[profile.api]; |
| if (!selectedApiMap) { |
| throw new Error(`Unknown API type ${profile.api}`); |
| } |
| if (!Object.hasOwn(this.getAllowedTypes(), selectedApiMap.selected)) { |
| throw new Error(`API type ${selectedApiMap.selected} is not supported. Supported types: ${Object.values(this.getAllowedTypes()).join(', ')}`); |
| } |
|
|
| return selectedApiMap; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static handleDropdown( |
| selector, |
| initialSelectedProfileId, |
| onChange = () => { }, |
| onCreate = () => { }, |
| unUpdate = () => { }, |
| onDelete = () => { }, |
| ) { |
| const context = SillyTavern.getContext(); |
| if (context.extensionSettings.disabledExtensions.includes('connection-manager')) { |
| throw new Error('Connection Manager is not available'); |
| } |
|
|
| |
| |
| |
| const dropdown = $(selector); |
|
|
| if (!dropdown || !dropdown.length) { |
| throw new Error(`Could not find dropdown with selector ${selector}`); |
| } |
|
|
| dropdown.empty(); |
|
|
| |
| const defaultOption = document.createElement('option'); |
| defaultOption.value = ''; |
| defaultOption.textContent = 'Select a Connection Profile'; |
| defaultOption.dataset.i18n = 'Select a Connection Profile'; |
| dropdown.append(defaultOption); |
|
|
| const profiles = context.extensionSettings.connectionManager.profiles; |
|
|
| |
| const groups = {}; |
| for (const [apiType, groupLabel] of Object.entries(this.getAllowedTypes())) { |
| const optgroup = document.createElement('optgroup'); |
| optgroup.label = groupLabel; |
| groups[apiType] = optgroup; |
| } |
|
|
| const sortedProfilesByGroup = {}; |
| for (const apiType of Object.keys(this.getAllowedTypes())) { |
| sortedProfilesByGroup[apiType] = []; |
| } |
|
|
| for (const profile of profiles) { |
| if (this.isProfileSupported(profile)) { |
| const apiMap = CONNECT_API_MAP[profile.api]; |
| if (sortedProfilesByGroup[apiMap.selected]) { |
| sortedProfilesByGroup[apiMap.selected].push(profile); |
| } |
| } |
| } |
|
|
| |
| for (const [apiType, groupProfiles] of Object.entries(sortedProfilesByGroup)) { |
| if (groupProfiles.length === 0) continue; |
|
|
| groupProfiles.sort((a, b) => a.name.localeCompare(b.name)); |
|
|
| const group = groups[apiType]; |
| for (const profile of groupProfiles) { |
| const option = document.createElement('option'); |
| option.value = profile.id; |
| option.textContent = profile.name; |
| group.appendChild(option); |
| } |
| } |
|
|
| for (const group of Object.values(groups)) { |
| if (group.children.length > 0) { |
| dropdown.append(group); |
| } |
| } |
|
|
| const selectedProfile = profiles.find((p) => p.id === initialSelectedProfileId); |
| if (selectedProfile) { |
| dropdown.val(selectedProfile.id); |
| } |
|
|
| context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_CREATED, async (profile) => { |
| const isSupported = this.isProfileSupported(profile); |
| if (!isSupported) { |
| return; |
| } |
|
|
| const group = groups[CONNECT_API_MAP[profile.api].selected]; |
| const option = document.createElement('option'); |
| option.value = profile.id; |
| option.textContent = profile.name; |
| group.appendChild(option); |
|
|
| await onCreate(profile); |
| }); |
|
|
| context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_UPDATED, async (oldProfile, newProfile) => { |
| const currentSelected = dropdown.val(); |
| const isSelectedProfile = currentSelected === oldProfile.id; |
| await unUpdate(oldProfile, newProfile); |
|
|
| if (!this.isProfileSupported(newProfile)) { |
| if (isSelectedProfile) { |
| dropdown.val(''); |
| dropdown.trigger('change'); |
| } |
| return; |
| } |
|
|
| const group = groups[CONNECT_API_MAP[newProfile.api].selected]; |
| const oldOption = group.querySelector(`option[value="${oldProfile.id}"]`); |
| if (oldOption) { |
| oldOption.remove(); |
| } |
|
|
| const option = document.createElement('option'); |
| option.value = newProfile.id; |
| option.textContent = newProfile.name; |
| group.appendChild(option); |
|
|
| if (isSelectedProfile) { |
| |
| dropdown.val(newProfile.id); |
| dropdown.trigger('change'); |
| } |
| }); |
|
|
| context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_DELETED, async (profile) => { |
| const currentSelected = dropdown.val(); |
| const isSelectedProfile = currentSelected === profile.id; |
| if (!this.isProfileSupported(profile)) { |
| return; |
| } |
|
|
| const group = groups[CONNECT_API_MAP[profile.api].selected]; |
| const optionToRemove = group.querySelector(`option[value="${profile.id}"]`); |
| if (optionToRemove) { |
| optionToRemove.remove(); |
| } |
|
|
| if (isSelectedProfile) { |
| dropdown.val(''); |
| dropdown.trigger('change'); |
| } |
|
|
| await onDelete(profile); |
| }); |
|
|
| dropdown.on('change', async () => { |
| const profileId = dropdown.val(); |
| const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId); |
| await onChange(profile); |
| }); |
| } |
| } |
|
|