diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/AzureServiceVersion.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/AzureServiceVersion.kt new file mode 100644 index 000000000..92184803a --- /dev/null +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/AzureServiceVersion.kt @@ -0,0 +1,8 @@ +@file:Suppress("unused") + +package cn.yiiguxing.plugin.translate.trans.openai + +enum class AzureServiceVersion(val value: String) { + V2023_05_15("2023-05-15"), + V2023_12_01_PREVIEW("2023-12-01-preview"); +} \ No newline at end of file diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAI.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAI.kt deleted file mode 100644 index 0bcaa69c9..000000000 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAI.kt +++ /dev/null @@ -1,26 +0,0 @@ -package cn.yiiguxing.plugin.translate.trans.openai - -import cn.yiiguxing.plugin.translate.trans.openai.chat.ChatCompletion -import cn.yiiguxing.plugin.translate.trans.openai.chat.ChatCompletionRequest -import com.intellij.openapi.components.service -import com.intellij.util.concurrency.annotations.RequiresBackgroundThread -import com.intellij.util.io.RequestBuilder - -object OpenAI { - - const val DEFAULT_API_ENDPOINT = "https://api.openai.com" - const val API_PATH = "/v1/chat/completions" - private val apiEndpoint: String - get() = (service().apiEndpoint ?: DEFAULT_API_ENDPOINT).trimEnd('/') + API_PATH - - private fun RequestBuilder.auth() { - val apiKey = OpenAICredential.apiKey - tuner { it.setRequestProperty("Authorization", "Bearer $apiKey") } - } - - @RequiresBackgroundThread - fun chatCompletion(request: ChatCompletionRequest): ChatCompletion { - return OpenAIHttp.post(apiEndpoint, request) { auth() } - } - -} diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAICredential.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAICredential.kt deleted file mode 100644 index 6937f6f40..000000000 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAICredential.kt +++ /dev/null @@ -1,28 +0,0 @@ -package cn.yiiguxing.plugin.translate.trans.openai - -import cn.yiiguxing.plugin.translate.TranslationPlugin -import cn.yiiguxing.plugin.translate.util.credential.SimpleStringCredentialManager -import cn.yiiguxing.plugin.translate.util.credential.StringCredentialManager -import com.intellij.credentialStore.generateServiceName -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service - -@Service -internal class OpenAICredential private constructor() : - StringCredentialManager by SimpleStringCredentialManager(SERVICE_NAME) { - - companion object { - private val SERVICE_NAME = - generateServiceName("OpenAI Credentials", "${TranslationPlugin.PLUGIN_ID}.OPENAI_API_KEY") - - private val service: OpenAICredential get() = service() - - val isApiKeySet: Boolean get() = service.isCredentialSet - - var apiKey: String? - get() = service.credential - set(value) { - service.credential = value - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAICredentials.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAICredentials.kt new file mode 100644 index 000000000..ba0daf20a --- /dev/null +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAICredentials.kt @@ -0,0 +1,29 @@ +package cn.yiiguxing.plugin.translate.trans.openai + +import cn.yiiguxing.plugin.translate.TranslationPlugin +import cn.yiiguxing.plugin.translate.util.credential.SimpleStringCredentialManager +import cn.yiiguxing.plugin.translate.util.credential.StringCredentialManager +import com.intellij.credentialStore.generateServiceName +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service + +@Service +internal class OpenAICredentials private constructor() { + + val openAi = SimpleStringCredentialManager(OPEN_AI_SERVICE_NAME) + val azure = SimpleStringCredentialManager(AZURE_SERVICE_NAME) + + companion object { + private val OPEN_AI_SERVICE_NAME = + generateServiceName("OpenAI Credentials", "${TranslationPlugin.PLUGIN_ID}.OPENAI_API_KEY") + private val AZURE_SERVICE_NAME = + generateServiceName("OpenAI Credentials", "${TranslationPlugin.PLUGIN_ID}.AZURE_OPENAI_API_KEY") + + private val service: OpenAICredentials get() = service() + + fun manager(provider: ServiceProvider): StringCredentialManager = when (provider) { + ServiceProvider.OpenAI -> service.openAi + ServiceProvider.Azure -> service.azure + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIModel.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIModel.kt index 699841321..8509ecc26 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIModel.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIModel.kt @@ -3,7 +3,7 @@ package cn.yiiguxing.plugin.translate.trans.openai /** - * See: [OpenAI Models](https://platform.openai.com/docs/models) + * See: [OpenAIService Models](https://platform.openai.com/docs/models) */ enum class OpenAIModel(val value: String, val modelName: String) { GPT_3_5_TURBO("gpt-3.5-turbo", "GPT-3.5-Turbo"), diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIService.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIService.kt new file mode 100644 index 000000000..326690a77 --- /dev/null +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAIService.kt @@ -0,0 +1,74 @@ +package cn.yiiguxing.plugin.translate.trans.openai + +import cn.yiiguxing.plugin.translate.trans.openai.chat.ChatCompletion +import cn.yiiguxing.plugin.translate.trans.openai.chat.ChatMessage +import cn.yiiguxing.plugin.translate.trans.openai.chat.chatCompletionRequest +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import com.intellij.util.io.RequestBuilder + +const val DEFAULT_OPEN_AI_API_ENDPOINT = "https://api.openai.com" +const val OPEN_AI_API_PATH = "/v1/chat/completions" + +private const val AZURE_OPEN_AI_API_PATH = "/openai/deployments/%s/chat/completions" + +interface OpenAIService { + + @RequiresBackgroundThread + fun chatCompletion(messages: List): ChatCompletion + + interface Options { + val model: OpenAIModel + val endpoint: String? + } + + interface AzureOptions : Options { + val apiVersion: AzureServiceVersion + } + + companion object { + fun get(settings: OpenAISettings): OpenAIService { + return when (settings.provider) { + ServiceProvider.OpenAI -> OpenAI(settings.openAi) + ServiceProvider.Azure -> Azure(settings.azure) + } + } + } +} + + +class OpenAI(private val options: OpenAIService.Options) : OpenAIService { + private val apiUrl: String + get() = (options.endpoint ?: DEFAULT_OPEN_AI_API_ENDPOINT).trimEnd('/') + OPEN_AI_API_PATH + + private fun RequestBuilder.auth() { + val apiKey = OpenAICredentials.manager(ServiceProvider.OpenAI).credential + tuner { it.setRequestProperty("Authorization", "Bearer $apiKey") } + } + + override fun chatCompletion(messages: List): ChatCompletion { + val request = chatCompletionRequest { + model = options.model.value + this.messages = messages + } + return OpenAIHttp.post(apiUrl, request) { auth() } + } +} + +class Azure(options: OpenAIService.AzureOptions) : OpenAIService { + + private val apiUrl: String = requireNotNull(options.endpoint) { "Azure OpenAI API endpoint is required" } + + AZURE_OPEN_AI_API_PATH.format(options.model.value) + + "?api-version=${options.apiVersion.value}" + + private fun RequestBuilder.auth() { + val apiKey = OpenAICredentials.manager(ServiceProvider.Azure).credential + tuner { it.setRequestProperty("api-key", apiKey) } + } + + override fun chatCompletion(messages: List): ChatCompletion { + val request = chatCompletionRequest(false) { + this.messages = messages + } + return OpenAIHttp.post(apiUrl, request) { auth() } + } +} diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettings.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettings.kt index 9ff08d250..c68c179eb 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettings.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettings.kt @@ -3,6 +3,8 @@ package cn.yiiguxing.plugin.translate.trans.openai import cn.yiiguxing.plugin.translate.TranslationStorages import com.intellij.openapi.components.* import com.intellij.util.xmlb.annotations.OptionTag +import com.intellij.util.xmlb.annotations.Tag +import com.intellij.util.xmlb.annotations.Transient /** @@ -12,15 +14,40 @@ import com.intellij.util.xmlb.annotations.OptionTag @State(name = "Translation.OpenAISettings", storages = [Storage(TranslationStorages.PREFERENCES_STORAGE_NAME)]) class OpenAISettings : BaseState(), PersistentStateComponent { - @get:OptionTag("MODEL") - var model: OpenAIModel by enum(OpenAIModel.GPT_3_5_TURBO) + @get:OptionTag("PROVIDER") + var provider: ServiceProvider by enum(ServiceProvider.OpenAI) - @get:OptionTag("API_ENDPOINT") - var apiEndpoint: String? by string() + @get:OptionTag("OPEN_AI") + var openAi: OpenAI by property(OpenAI()) + + @get:OptionTag("AZURE") + var azure: Azure by property(Azure()) + + @get:Transient + val model: OpenAIModel + get() = when (provider) { + ServiceProvider.OpenAI -> openAi.model + ServiceProvider.Azure -> azure.model + } override fun getState(): OpenAISettings = this override fun loadState(state: OpenAISettings) { copyFrom(state) } + + @Tag("open-ai") + open class OpenAI : BaseState(), OpenAIService.Options { + @get:OptionTag("MODEL") + override var model: OpenAIModel by enum(OpenAIModel.GPT_3_5_TURBO) + + @get:OptionTag("ENDPOINT") + override var endpoint: String? by string() + } + + @Tag("azure") + class Azure : OpenAI(), OpenAIService.AzureOptions { + @get:OptionTag("API_VERSION") + override var apiVersion: AzureServiceVersion by enum(AzureServiceVersion.V2023_05_15) + } } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettingsDialog.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettingsDialog.kt index a3494f331..0b84e4cbb 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettingsDialog.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAISettingsDialog.kt @@ -40,7 +40,7 @@ class OpenAISettingsDialog : DialogWrapper(false) { private val apiKeyField: JBPasswordField = JBPasswordField() private val apiEndpointField: ExtendableTextField = ExtendableTextField().apply { - emptyText.text = OpenAI.DEFAULT_API_ENDPOINT + emptyText.text = DEFAULT_OPEN_AI_API_ENDPOINT val extension = Extension.create(AllIcons.General.Reset, message("set.as.default.action.name")) { text = null setErrorText(null) @@ -87,8 +87,8 @@ class OpenAISettingsDialog : DialogWrapper(false) { setResizable(false) init() - apiEndpoint = settings.apiEndpoint - apiModelComboBox.selectedItem = settings.model + apiEndpoint = settings.openAi.endpoint + apiModelComboBox.selectedItem = settings.openAi.model } @@ -100,7 +100,7 @@ class OpenAISettingsDialog : DialogWrapper(false) { private fun createConfigurationPanel(): JPanel { val fieldWidth = 320 - val apiPathLabel = JBLabel(OpenAI.API_PATH).apply { + val apiPathLabel = JBLabel(OPEN_AI_API_PATH).apply { border = JBUI.Borders.emptyRight(apiEndpointField.insets.right) isEnabled = false } @@ -139,14 +139,14 @@ class OpenAISettingsDialog : DialogWrapper(false) { return } - OpenAICredential.apiKey = apiKey - isOK = OpenAICredential.isApiKeySet - settings.apiEndpoint = apiEndpoint + OpenAICredentials.manager(ServiceProvider.OpenAI).credential = apiKey + isOK = OpenAICredentials.manager(ServiceProvider.OpenAI).isCredentialSet + settings.openAi.endpoint = apiEndpoint - val oldModel = settings.model + val oldModel = settings.openAi.model val newModel = apiModelComboBox.selected ?: OpenAIModel.GPT_3_5_TURBO if (oldModel != newModel) { - settings.model = newModel + settings.openAi.model = newModel service().removeMemoryCache { key, _ -> key.translator == TranslationEngine.OPEN_AI.id } @@ -159,7 +159,10 @@ class OpenAISettingsDialog : DialogWrapper(false) { // This is a modal dialog, so it needs to be invoked later. SwingUtilities.invokeLater { val dialogRef = DisposableRef.create(disposable, this) - runAsync { OpenAICredential.apiKey to OpenAICredential.isApiKeySet } + runAsync { + OpenAICredentials.manager(ServiceProvider.OpenAI) + .let { it.credential to it.isCredentialSet } + } .expireWith(disposable) .successOnUiThread(dialogRef) { dialog, (apiKey, isApiKeySet) -> dialog.apiKey = apiKey diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAITranslator.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAITranslator.kt index 967bffa40..20dc1a82e 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAITranslator.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/OpenAITranslator.kt @@ -4,7 +4,7 @@ import cn.yiiguxing.plugin.translate.message import cn.yiiguxing.plugin.translate.service.CacheService import cn.yiiguxing.plugin.translate.trans.* import cn.yiiguxing.plugin.translate.trans.openai.chat.ChatRole -import cn.yiiguxing.plugin.translate.trans.openai.chat.chatCompletionRequest +import cn.yiiguxing.plugin.translate.trans.openai.chat.chatMessages import cn.yiiguxing.plugin.translate.trans.openai.exception.OpenAIStatusException import cn.yiiguxing.plugin.translate.ui.settings.TranslationEngine.OPEN_AI import cn.yiiguxing.plugin.translate.util.md5 @@ -25,11 +25,11 @@ object OpenAITranslator : AbstractTranslator(), DocumentationTranslator { OpenAILanguages.languages.toMutableList().apply { add(0, Lang.AUTO) } override val supportedTargetLanguages: List = OpenAILanguages.languages - private val openAIModel: OpenAIModel get() = service().model + private val settings: OpenAISettings get() = service() override fun checkConfiguration(force: Boolean): Boolean { - if (force || !OpenAICredential.isApiKeySet) { + if (force || !OpenAICredentials.manager(settings.provider).isCredentialSet) { return OPEN_AI.showConfigurationDialog() } @@ -58,16 +58,15 @@ object OpenAITranslator : AbstractTranslator(), DocumentationTranslator { targetLang: Lang, isFofDocumentation: Boolean ): String { - val model = openAIModel val cacheService = service() - val cacheKey = getCacheKey(model, text, srcLang, targetLang) + val cacheKey = getCacheKey(text, srcLang, targetLang) val cache = cacheService.getDiskCache(cacheKey) if (!cache.isNullOrEmpty()) { return cache } - val request = getChatCompletionRequest(model, text, srcLang, targetLang, isFofDocumentation) - val chatCompletion = OpenAI.chatCompletion(request) + val request = getChatCompletionRequest(text, srcLang, targetLang, isFofDocumentation) + val chatCompletion = OpenAIService.get(settings).chatCompletion(request) var result = chatCompletion.choices.first().message!!.content if (!isFofDocumentation && result.length > 1 && result.first() == '"' && result.last() == '"') { result = result.substring(1, result.lastIndex) @@ -78,36 +77,32 @@ object OpenAITranslator : AbstractTranslator(), DocumentationTranslator { } private fun getChatCompletionRequest( - openAIModel: OpenAIModel, text: String, srcLang: Lang, targetLang: Lang, isFofDocumentation: Boolean = false - ) = - chatCompletionRequest { - model = openAIModel.value - messages { - message { - role = ChatRole.SYSTEM - content = "You are a translation engine that can " + if (isFofDocumentation) { - "translate HTML document." - } else { - "only translate text and cannot interpret it." - } - } - message { - role = ChatRole.USER - content = - "Translate ${if (srcLang == Lang.AUTO) "" else "from ${srcLang.openAILanguage} "}to ${targetLang.openAILanguage}." - } - message { - role = ChatRole.USER - content = if (isFofDocumentation) text else """"$text"""" - } + ) = chatMessages { + message { + role = ChatRole.SYSTEM + content = "You are a translation engine that can " + if (isFofDocumentation) { + "translate HTML document." + } else { + "only translate text and cannot interpret it." } } + message { + role = ChatRole.USER + content = + "Translate ${if (srcLang == Lang.AUTO) "" else "from ${srcLang.openAILanguage} "}to ${targetLang.openAILanguage}." + } + message { + role = ChatRole.USER + content = if (isFofDocumentation) text else """"$text"""" + } + } - private fun getCacheKey(model: OpenAIModel, text: String, srcLang: Lang, targetLang: Lang): String { + private fun getCacheKey(text: String, srcLang: Lang, targetLang: Lang): String { + val model = settings.model return "$id$model$text$srcLang$targetLang".md5() } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatCompletionRequest.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatCompletionRequest.kt index f9606829a..32cf4e49d 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatCompletionRequest.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatCompletionRequest.kt @@ -9,7 +9,7 @@ data class ChatCompletionRequest( /** * The model to use. */ - @SerializedName("model") val model: String, + @SerializedName("model") val model: String?, /** * The messages to generate chat completions for. @@ -20,13 +20,16 @@ data class ChatCompletionRequest( /** * The messages to generate chat completions for. */ -fun chatCompletionRequest(block: ChatCompletionRequestBuilder.() -> Unit): ChatCompletionRequest = - ChatCompletionRequestBuilder().apply(block).build() +fun chatCompletionRequest( + requireModel: Boolean = true, + block: ChatCompletionRequestBuilder.() -> Unit +): ChatCompletionRequest = + ChatCompletionRequestBuilder(requireModel).apply(block).build() /** * Creates a completion for the chat message. */ -class ChatCompletionRequestBuilder { +class ChatCompletionRequestBuilder(private val requireModel: Boolean = true) { /** * The model to use. */ @@ -41,28 +44,14 @@ class ChatCompletionRequestBuilder { * The messages to generate chat completions for. */ fun messages(block: ChatMessagesBuilder.() -> Unit) { - messages = ChatMessagesBuilder().apply(block).messages + messages = chatMessages(block) } /** * Builder of [ChatCompletionRequest] instances. */ fun build(): ChatCompletionRequest = ChatCompletionRequest( - model = requireNotNull(model) { "model is required" }, + model = if (requireModel) requireNotNull(model) { "model is required" } else model, messages = requireNotNull(messages) { "messages is required" }, ) } - -/** - * Creates a list of [ChatMessage]. - */ -class ChatMessagesBuilder { - internal val messages = mutableListOf() - - /** - * Creates a [ChatMessage] instance. - */ - fun message(block: ChatMessageBuilder.() -> Unit) { - messages += ChatMessageBuilder().apply(block).build() - } -} diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatMessage.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatMessage.kt index c3eebdd2c..37f46965b 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatMessage.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/openai/chat/ChatMessage.kt @@ -22,11 +22,17 @@ data class ChatMessage( ) /** - * The messages to generate chat completions for. + * The message to generate chat completions for. */ fun chatMessage(block: ChatMessageBuilder.() -> Unit): ChatMessage = ChatMessageBuilder().apply(block).build() +/** + * The messages to generate chat completions for. + */ +fun chatMessages(block: ChatMessagesBuilder.() -> Unit): List = + ChatMessagesBuilder().apply(block).messages + /** * Builder of [ChatMessageBuilder] instances. */ @@ -50,4 +56,18 @@ class ChatMessageBuilder { role = requireNotNull(role) { "role is required" }, content = requireNotNull(content) { "content is required" }, ) +} + +/** + * Creates a list of [ChatMessage]. + */ +class ChatMessagesBuilder { + internal val messages = mutableListOf() + + /** + * Creates a [ChatMessage] instance. + */ + fun message(block: ChatMessageBuilder.() -> Unit) { + messages += ChatMessageBuilder().apply(block).build() + } } \ No newline at end of file diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/settings/TranslationEngine.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/settings/TranslationEngine.kt index b312e1dc6..52af925a7 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/settings/TranslationEngine.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/settings/TranslationEngine.kt @@ -13,7 +13,8 @@ import cn.yiiguxing.plugin.translate.trans.deepl.DeeplTranslator import cn.yiiguxing.plugin.translate.trans.google.GoogleSettingsDialog import cn.yiiguxing.plugin.translate.trans.google.GoogleTranslator import cn.yiiguxing.plugin.translate.trans.microsoft.MicrosoftTranslator -import cn.yiiguxing.plugin.translate.trans.openai.OpenAICredential +import cn.yiiguxing.plugin.translate.trans.openai.OpenAICredentials +import cn.yiiguxing.plugin.translate.trans.openai.OpenAISettings import cn.yiiguxing.plugin.translate.trans.openai.OpenAISettingsDialog import cn.yiiguxing.plugin.translate.trans.openai.OpenAITranslator import cn.yiiguxing.plugin.translate.trans.youdao.YoudaoSettingsDialog @@ -21,6 +22,7 @@ import cn.yiiguxing.plugin.translate.trans.youdao.YoudaoTranslator import cn.yiiguxing.plugin.translate.ui.AppKeySettingsDialog import cn.yiiguxing.plugin.translate.ui.AppKeySettingsPanel import cn.yiiguxing.plugin.translate.util.Settings +import com.intellij.openapi.components.service import icons.TranslationIcons import java.util.* import javax.swing.Icon @@ -84,7 +86,7 @@ enum class TranslationEngine( BAIDU -> isConfigured(Settings.baiduTranslateSettings) ALI -> isConfigured(Settings.aliTranslateSettings) DEEPL -> DeeplCredential.isAuthKeySet - OPEN_AI -> OpenAICredential.isApiKeySet + OPEN_AI -> OpenAICredentials.manager(service().provider).isCredentialSet } }