mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-19 16:28:46 +00:00
feat: Add separate model selection for code completion (#1032)
* add separate Ollama model selection for code completion closes issue #733 * add separate selection of code completion provider closes issue #804 * add test for Ollama code completion with separate model selection also added missing clearing of code completion cache to other tests. This fixes ProxyAI test - before the fix it was passed always regardless of real ProxyAI completion operation * fix saving of code completion provider option in IDE settings * fix code style * fix: remove expected failing test --------- Co-authored-by: Carl-Robert Linnupuu <carlrobertoh@gmail.com>
This commit is contained in:
parent
2ead45efde
commit
b5215ec45c
15 changed files with 233 additions and 119 deletions
|
|
@ -1,10 +1,13 @@
|
|||
package ee.carlrobert.codegpt.settings;
|
||||
|
||||
import static ee.carlrobert.codegpt.settings.service.ModelRole.CHAT_ROLE;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings;
|
||||
|
|
@ -39,7 +42,11 @@ public class GeneralSettings implements PersistentStateComponent<GeneralSettings
|
|||
}
|
||||
|
||||
public static ServiceType getSelectedService() {
|
||||
return getCurrentState().getSelectedService();
|
||||
return getCurrentState().getSelectedService(CHAT_ROLE);
|
||||
}
|
||||
|
||||
public static ServiceType getSelectedService(ModelRole role) {
|
||||
return getCurrentState().getSelectedService(role);
|
||||
}
|
||||
|
||||
public static boolean isSelected(ServiceType serviceType) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package ee.carlrobert.codegpt.settings;
|
||||
|
||||
import static ee.carlrobert.codegpt.settings.service.ModelRole.CHAT_ROLE;
|
||||
import static ee.carlrobert.codegpt.settings.service.ModelRole.CODECOMPLETION_ROLE;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole;
|
||||
import ee.carlrobert.codegpt.settings.service.ProviderChangeNotifier;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
|
||||
|
|
@ -9,6 +13,7 @@ public class GeneralSettingsState {
|
|||
private String displayName = "";
|
||||
private String avatarBase64 = "";
|
||||
private ServiceType selectedService = ServiceType.CODEGPT;
|
||||
private ServiceType codeCompletionService = ServiceType.CODEGPT;
|
||||
|
||||
public String getDisplayName() {
|
||||
if (displayName == null || displayName.isEmpty()) {
|
||||
|
|
@ -33,16 +38,52 @@ public class GeneralSettingsState {
|
|||
this.avatarBase64 = avatarBase64;
|
||||
}
|
||||
|
||||
public ServiceType getSelectedService(ModelRole role) {
|
||||
switch (role) {
|
||||
case CHAT_ROLE -> {
|
||||
return selectedService;
|
||||
}
|
||||
case CODECOMPLETION_ROLE -> {
|
||||
return codeCompletionService;
|
||||
}
|
||||
default -> {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceType getSelectedService() {
|
||||
return selectedService;
|
||||
return getSelectedService(CHAT_ROLE);
|
||||
}
|
||||
|
||||
public ServiceType getSelectedCodeCompletionService() {
|
||||
return getSelectedService(CODECOMPLETION_ROLE);
|
||||
}
|
||||
|
||||
public void setSelectedService(ModelRole role, ServiceType selectedService) {
|
||||
switch (role) {
|
||||
case CHAT_ROLE -> {
|
||||
this.selectedService = selectedService;
|
||||
|
||||
ApplicationManager.getApplication()
|
||||
.getMessageBus()
|
||||
.syncPublisher(ProviderChangeNotifier.getTOPIC())
|
||||
.providerChanged(selectedService);
|
||||
}
|
||||
case CODECOMPLETION_ROLE -> {
|
||||
this.codeCompletionService = selectedService;
|
||||
}
|
||||
default -> {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedService(ServiceType selectedService) {
|
||||
this.selectedService = selectedService;
|
||||
setSelectedService(CHAT_ROLE, selectedService);
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication()
|
||||
.getMessageBus()
|
||||
.syncPublisher(ProviderChangeNotifier.getTOPIC())
|
||||
.providerChanged(selectedService);
|
||||
public void setSelectedCodeCompletionService(ServiceType selectedService) {
|
||||
setSelectedService(CODECOMPLETION_ROLE, selectedService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package ee.carlrobert.codegpt.settings.service;
|
||||
|
||||
public enum ModelRole {
|
||||
CHAT_ROLE,
|
||||
CODECOMPLETION_ROLE
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.intellij.openapi.components.service
|
|||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionService
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.CODECOMPLETION_ROLE
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType.*
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
|
|
@ -17,7 +18,7 @@ abstract class CodeCompletionFeatureToggleActions(
|
|||
private val enableFeatureAction: Boolean
|
||||
) : DumbAwareAction() {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) = when (GeneralSettings.getSelectedService()) {
|
||||
override fun actionPerformed(e: AnActionEvent) = when (GeneralSettings.getSelectedService(CODECOMPLETION_ROLE)) {
|
||||
CODEGPT -> service<CodeGPTServiceSettings>().state.codeCompletionSettings::codeCompletionsEnabled::set
|
||||
|
||||
OPENAI -> OpenAISettings.getCurrentState()::setCodeCompletionsEnabled
|
||||
|
|
@ -34,7 +35,7 @@ abstract class CodeCompletionFeatureToggleActions(
|
|||
}(enableFeatureAction)
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val selectedService = GeneralSettings.getSelectedService()
|
||||
val selectedService = GeneralSettings.getSelectedService(CODECOMPLETION_ROLE)
|
||||
val codeCompletionEnabled =
|
||||
e.project?.service<CodeCompletionService>()?.isCodeCompletionsEnabled(selectedService)
|
||||
?: false
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import ee.carlrobert.codegpt.CodeGPTKeys
|
|||
import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.CODECOMPLETION_ROLE
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.treesitter.CodeCompletionParserFactory
|
||||
|
|
@ -156,7 +157,7 @@ class CodeCompletionEventListener(
|
|||
}
|
||||
|
||||
override fun onError(error: ErrorDetails, ex: Throwable) {
|
||||
val isCodeGPTService = GeneralSettings.getSelectedService() == ServiceType.CODEGPT
|
||||
val isCodeGPTService = GeneralSettings.getSelectedService(CODECOMPLETION_ROLE) == ServiceType.CODEGPT
|
||||
if (isCodeGPTService && "RATE_LIMIT_EXCEEDED" == error.code) {
|
||||
service<CodeGPTServiceSettings>().state
|
||||
.codeCompletionSettings
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ object CodeCompletionRequestFactory {
|
|||
}
|
||||
|
||||
return OllamaCompletionRequest.Builder(
|
||||
settings.model,
|
||||
settings.codeCompletionModel,
|
||||
prompt
|
||||
)
|
||||
.setSuffix(if (settings.fimOverride) null else details.suffix)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
|
|||
import ee.carlrobert.codegpt.completions.CompletionClientProvider
|
||||
import ee.carlrobert.codegpt.completions.llama.LlamaModel
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.CODECOMPLETION_ROLE
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType.*
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
|
|
@ -41,12 +42,12 @@ class CodeCompletionService(private val project: Project) {
|
|||
.getOrDefault("model", null) as String
|
||||
|
||||
LLAMA_CPP -> LlamaModel.findByHuggingFaceModel(LlamaSettings.getCurrentState().huggingFaceModel).label
|
||||
OLLAMA -> service<OllamaSettings>().state.model
|
||||
OLLAMA -> service<OllamaSettings>().state.codeCompletionModel
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun isCodeCompletionsEnabled(): Boolean = isCodeCompletionsEnabled(GeneralSettings.getSelectedService())
|
||||
fun isCodeCompletionsEnabled(): Boolean = isCodeCompletionsEnabled(GeneralSettings.getSelectedService(CODECOMPLETION_ROLE))
|
||||
|
||||
fun isCodeCompletionsEnabled(selectedService: ServiceType): Boolean =
|
||||
when (selectedService) {
|
||||
|
|
@ -62,7 +63,7 @@ class CodeCompletionService(private val project: Project) {
|
|||
infillRequest: InfillRequest,
|
||||
eventListener: CompletionEventListener<String>
|
||||
): EventSource {
|
||||
return when (val selectedService = GeneralSettings.getSelectedService()) {
|
||||
return when (val selectedService = GeneralSettings.getSelectedService(CODECOMPLETION_ROLE)) {
|
||||
OPENAI -> CompletionClientProvider.getOpenAIClient()
|
||||
.getCompletionAsync(buildOpenAIRequest(infillRequest), eventListener)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.intellij.openapi.components.service
|
|||
import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_CODE_COMPLETION
|
||||
import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.*
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
|
|
@ -68,7 +69,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
|
|||
|
||||
var eventListener = CodeCompletionEventListener(request.editor, this)
|
||||
|
||||
if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT) {
|
||||
if (GeneralSettings.getSelectedService(CODECOMPLETION_ROLE) == ServiceType.CODEGPT) {
|
||||
project.service<GrpcClientService>().getCodeCompletionAsync(eventListener, request, this)
|
||||
return@channelFlow
|
||||
}
|
||||
|
|
@ -97,7 +98,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
|
|||
}
|
||||
|
||||
override fun isEnabled(event: InlineCompletionEvent): Boolean {
|
||||
val selectedService = GeneralSettings.getSelectedService()
|
||||
val selectedService = GeneralSettings.getSelectedService(CODECOMPLETION_ROLE)
|
||||
val codeCompletionsEnabled = when (selectedService) {
|
||||
ServiceType.CODEGPT -> service<CodeGPTServiceSettings>().state.codeCompletionSettings.codeCompletionsEnabled
|
||||
ServiceType.OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package ee.carlrobert.codegpt.settings.service
|
||||
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.*
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState
|
||||
|
|
@ -23,24 +24,28 @@ class ServiceConfigurable : Configurable {
|
|||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return component.getSelectedService() != service<GeneralSettings>().state.selectedService
|
||||
return component.getSelectedService(CHAT_ROLE) != service<GeneralSettings>().state.getSelectedService(CHAT_ROLE)
|
||||
|| component.getSelectedService(CODECOMPLETION_ROLE) != service<GeneralSettings>().state.getSelectedService(CODECOMPLETION_ROLE)
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
val state = service<GeneralSettings>().state
|
||||
state.selectedService = component.getSelectedService()
|
||||
state.setSelectedService(CHAT_ROLE, component.getSelectedService(CHAT_ROLE))
|
||||
|
||||
val serviceChanged = component.getSelectedService() != state.selectedService
|
||||
val serviceChanged = component.getSelectedService(CHAT_ROLE) != state.selectedService
|
||||
if (serviceChanged) {
|
||||
resetActiveTab()
|
||||
TelemetryAction.SETTINGS_CHANGED.createActionMessage()
|
||||
.property("service", component.getSelectedService().code.lowercase())
|
||||
.property("service", component.getSelectedService(CHAT_ROLE).code.lowercase())
|
||||
.send()
|
||||
}
|
||||
|
||||
state.setSelectedService(CODECOMPLETION_ROLE, component.getSelectedService(CODECOMPLETION_ROLE))
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
component.setSelectedService(service<GeneralSettings>().state.selectedService)
|
||||
component.setSelectedService(CHAT_ROLE,service<GeneralSettings>().state.getSelectedService(CHAT_ROLE))
|
||||
component.setSelectedService(CODECOMPLETION_ROLE,service<GeneralSettings>().state.getSelectedService(CODECOMPLETION_ROLE))
|
||||
}
|
||||
|
||||
private fun resetActiveTab() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package ee.carlrobert.codegpt.settings.service
|
||||
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.*
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.options.ex.Settings
|
||||
|
|
@ -23,15 +24,25 @@ class ServiceConfigurableComponent {
|
|||
|
||||
private var serviceComboBox: ComboBox<ServiceType> =
|
||||
ComboBox(EnumComboBoxModel(ServiceType::class.java)).apply {
|
||||
selectedItem = service<GeneralSettings>().state.selectedService
|
||||
selectedItem = service<GeneralSettings>().state.getSelectedService(CHAT_ROLE)
|
||||
}
|
||||
private var codeCompletionServiceComboBox: ComboBox<ServiceType> =
|
||||
ComboBox(EnumComboBoxModel(ServiceType::class.java)).apply {
|
||||
selectedItem = service<GeneralSettings>().state.getSelectedService(CODECOMPLETION_ROLE)
|
||||
}
|
||||
|
||||
fun getSelectedService(): ServiceType {
|
||||
return serviceComboBox.selectedItem as ServiceType
|
||||
fun getSelectedService(role: ModelRole): ServiceType {
|
||||
return when(role) {
|
||||
CHAT_ROLE -> serviceComboBox
|
||||
CODECOMPLETION_ROLE -> codeCompletionServiceComboBox
|
||||
}.selectedItem as ServiceType
|
||||
}
|
||||
|
||||
fun setSelectedService(serviceType: ServiceType) {
|
||||
serviceComboBox.selectedItem = serviceType
|
||||
fun setSelectedService(role: ModelRole, serviceType: ServiceType) {
|
||||
when(role) {
|
||||
CHAT_ROLE -> serviceComboBox
|
||||
CODECOMPLETION_ROLE -> codeCompletionServiceComboBox
|
||||
}.selectedItem = serviceType
|
||||
}
|
||||
|
||||
fun getPanel(): JPanel = FormBuilder.createFormBuilder()
|
||||
|
|
@ -39,6 +50,10 @@ class ServiceConfigurableComponent {
|
|||
CodeGPTBundle.get("settingsConfigurable.service.label"),
|
||||
serviceComboBox
|
||||
)
|
||||
.addLabeledComponent(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.codeCompletion.label"),
|
||||
codeCompletionServiceComboBox
|
||||
)
|
||||
.addVerticalGap(8)
|
||||
.addComponent(JBLabel("All available providers that can be used with CodeGPT:"))
|
||||
.addVerticalGap(8)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class OllamaSettings :
|
|||
class OllamaSettingsState : BaseState() {
|
||||
var host by string("http://localhost:11434")
|
||||
var model by string()
|
||||
var codeCompletionModel by string()
|
||||
var codeCompletionsEnabled by property(false)
|
||||
var fimOverride by property(true)
|
||||
var fimTemplate by enum<InfillPromptTemplate>(InfillPromptTemplate.CODE_QWEN_2_5)
|
||||
|
|
|
|||
|
|
@ -19,13 +19,15 @@ import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.OllamaAp
|
|||
import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential
|
||||
import ee.carlrobert.codegpt.settings.service.CodeCompletionConfigurationForm
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.*
|
||||
import ee.carlrobert.codegpt.ui.OverlayUtil
|
||||
import ee.carlrobert.codegpt.ui.UIUtil
|
||||
import ee.carlrobert.codegpt.ui.URLTextField
|
||||
import ee.carlrobert.llm.client.ollama.OllamaClient
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.lang.String.format
|
||||
import java.net.ConnectException
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
|
@ -39,7 +41,7 @@ class OllamaSettingsForm {
|
|||
private val refreshModelsButton =
|
||||
JButton(CodeGPTBundle.get("settingsConfigurable.service.ollama.models.refresh"))
|
||||
private val hostField: JBTextField
|
||||
private val modelComboBox: ComboBox<String>
|
||||
private val modelComboBoxes: Map<ModelRole, ComboBox<String>>
|
||||
private val codeCompletionConfigurationForm: CodeCompletionConfigurationForm
|
||||
private val apiKeyField: JBPasswordField
|
||||
|
||||
|
|
@ -56,18 +58,24 @@ class OllamaSettingsForm {
|
|||
)
|
||||
val emptyModelsComboBoxModel =
|
||||
DefaultComboBoxModel(arrayOf("Hit refresh to see models for this host"))
|
||||
modelComboBox = ComboBox(emptyModelsComboBoxModel).apply {
|
||||
modelComboBoxes = ModelRole.entries.associate { it to ComboBox(emptyModelsComboBoxModel).apply {
|
||||
isEnabled = false
|
||||
}
|
||||
hostField = URLTextField().apply {
|
||||
preferredSize = Dimension(280, preferredSize.height)
|
||||
} }
|
||||
hostField = URLTextField(30).apply {
|
||||
text = settings.host
|
||||
whenTextChangedFromUi {
|
||||
modelComboBox.model = emptyModelsComboBoxModel
|
||||
modelComboBox.isEnabled = false
|
||||
modelComboBoxes.values.forEach { comboBox ->
|
||||
comboBox.model = emptyModelsComboBoxModel
|
||||
comboBox.isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshModelsButton.addActionListener {
|
||||
refreshModels(getModel() ?: settings.model)
|
||||
refreshModels(mapOf(
|
||||
CHAT_ROLE to (getModel(CHAT_ROLE) ?: settings.model),
|
||||
CODECOMPLETION_ROLE to (getModel(CODECOMPLETION_ROLE) ?: settings.codeCompletionModel),
|
||||
))
|
||||
}
|
||||
apiKeyField = JBPasswordField().apply {
|
||||
columns = 30
|
||||
|
|
@ -88,11 +96,13 @@ class OllamaSettingsForm {
|
|||
)
|
||||
.addLabeledComponent(
|
||||
CodeGPTBundle.get("settingsConfigurable.shared.model.label"),
|
||||
JPanel(BorderLayout(8, 0)).apply {
|
||||
add(modelComboBox, BorderLayout.CENTER)
|
||||
add(refreshModelsButton, BorderLayout.EAST)
|
||||
}
|
||||
modelComboBoxes[CHAT_ROLE]!!
|
||||
)
|
||||
.addLabeledComponent(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.ollama.codeCompletionModel.label"),
|
||||
modelComboBoxes[CODECOMPLETION_ROLE]!!
|
||||
)
|
||||
.addComponent(refreshModelsButton)
|
||||
.addComponent(TitledSeparator(CodeGPTBundle.get("settingsConfigurable.shared.authentication.title")))
|
||||
.setFormLeftIndent(32)
|
||||
.addLabeledComponent(
|
||||
|
|
@ -107,9 +117,9 @@ class OllamaSettingsForm {
|
|||
.addComponentFillVertically(JPanel(), 0)
|
||||
.panel
|
||||
|
||||
fun getModel(): String? {
|
||||
return if (modelComboBox.isEnabled) {
|
||||
modelComboBox.item
|
||||
fun getModel(role: ModelRole): String? {
|
||||
return if (modelComboBoxes[role]!!.isEnabled) {
|
||||
modelComboBoxes[role]!!.item
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -120,7 +130,8 @@ class OllamaSettingsForm {
|
|||
fun resetForm() {
|
||||
service<OllamaSettings>().state.run {
|
||||
hostField.text = host
|
||||
modelComboBox.item = model ?: ""
|
||||
modelComboBoxes[CHAT_ROLE]!!.item = model ?: ""
|
||||
modelComboBoxes[CODECOMPLETION_ROLE]!!.item = codeCompletionModel ?: ""
|
||||
codeCompletionConfigurationForm.isCodeCompletionsEnabled = codeCompletionsEnabled
|
||||
codeCompletionConfigurationForm.fimTemplate = fimTemplate
|
||||
codeCompletionConfigurationForm.fimOverride != fimOverride
|
||||
|
|
@ -131,7 +142,10 @@ class OllamaSettingsForm {
|
|||
fun applyChanges() {
|
||||
service<OllamaSettings>().state.run {
|
||||
host = hostField.text
|
||||
model = modelComboBox.item
|
||||
if (modelComboBoxes[CHAT_ROLE]!!.isEnabled)
|
||||
model = modelComboBoxes[CHAT_ROLE]!!.item
|
||||
if (modelComboBoxes[CODECOMPLETION_ROLE]!!.isEnabled)
|
||||
codeCompletionModel = modelComboBoxes[CODECOMPLETION_ROLE]!!.item
|
||||
codeCompletionsEnabled = codeCompletionConfigurationForm.isCodeCompletionsEnabled
|
||||
fimTemplate = codeCompletionConfigurationForm.fimTemplate!!
|
||||
fimOverride = codeCompletionConfigurationForm.fimOverride ?: false
|
||||
|
|
@ -141,14 +155,15 @@ class OllamaSettingsForm {
|
|||
|
||||
fun isModified() = service<OllamaSettings>().state.run {
|
||||
hostField.text != host
|
||||
|| (modelComboBox.item != model && modelComboBox.isEnabled)
|
||||
|| (modelComboBoxes[CHAT_ROLE]!!.item != model && modelComboBoxes[CHAT_ROLE]!!.isEnabled)
|
||||
|| (modelComboBoxes[CODECOMPLETION_ROLE]!!.item != codeCompletionModel && modelComboBoxes[CODECOMPLETION_ROLE]!!.isEnabled)
|
||||
|| codeCompletionConfigurationForm.isCodeCompletionsEnabled != codeCompletionsEnabled
|
||||
|| codeCompletionConfigurationForm.fimTemplate != fimTemplate
|
||||
|| codeCompletionConfigurationForm.fimOverride != fimOverride
|
||||
|| getApiKey() != getCredential(OllamaApikey)
|
||||
}
|
||||
|
||||
private fun refreshModels(currentModel: String?) {
|
||||
private fun refreshModels(currentModels: Map<ModelRole, String?>) {
|
||||
disableModelComboBoxWithPlaceholder(DefaultComboBoxModel(arrayOf("Loading")))
|
||||
ReadAction.nonBlocking<List<String>> {
|
||||
try {
|
||||
|
|
@ -168,31 +183,33 @@ class OllamaSettingsForm {
|
|||
}
|
||||
}
|
||||
.finishOnUiThread(ModalityState.defaultModalityState()) { models ->
|
||||
updateModelComboBoxState(models, currentModel)
|
||||
updateModelComboBoxState(models, currentModels)
|
||||
}
|
||||
.submit(AppExecutorUtil.getAppExecutorService())
|
||||
}
|
||||
|
||||
private fun updateModelComboBoxState(models: List<String>, currentModel: String?) {
|
||||
private fun updateModelComboBoxState(models: List<String>, currentModels: Map<ModelRole, String?>) {
|
||||
if (models.isNotEmpty()) {
|
||||
modelComboBox.model = DefaultComboBoxModel(models.toTypedArray())
|
||||
modelComboBox.isEnabled = true
|
||||
currentModel?.let {
|
||||
if (models.contains(currentModel)) {
|
||||
modelComboBox.selectedItem = currentModel
|
||||
} else {
|
||||
OverlayUtil.showBalloon(
|
||||
format(
|
||||
CodeGPTBundle.get("validation.error.model.notExists"),
|
||||
currentModel
|
||||
),
|
||||
MessageType.ERROR,
|
||||
modelComboBox
|
||||
)
|
||||
modelComboBoxes.forEach { (role, comboBox) ->
|
||||
comboBox.model = DefaultComboBoxModel(models.toTypedArray())
|
||||
comboBox.isEnabled = true
|
||||
currentModels[role]?.let {
|
||||
if (models.contains(currentModels[role])) {
|
||||
comboBox.selectedItem = currentModels[role]
|
||||
} else {
|
||||
OverlayUtil.showBalloon(
|
||||
format(
|
||||
CodeGPTBundle.get("validation.error.model.notExists"),
|
||||
currentModels[role]
|
||||
),
|
||||
MessageType.ERROR,
|
||||
comboBox
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modelComboBox.model = DefaultComboBoxModel(arrayOf("No models"))
|
||||
disableModelComboBoxWithPlaceholder(DefaultComboBoxModel(arrayOf("No models")))
|
||||
}
|
||||
val availableModels = ApplicationManager.getApplication()
|
||||
.getService(OllamaSettings::class.java)
|
||||
|
|
@ -229,9 +246,11 @@ class OllamaSettingsForm {
|
|||
}
|
||||
|
||||
private fun disableModelComboBoxWithPlaceholder(placeholderModel: ComboBoxModel<String>) {
|
||||
modelComboBox.apply {
|
||||
model = placeholderModel
|
||||
isEnabled = false
|
||||
modelComboBoxes.values.forEach { comboBox ->
|
||||
comboBox.apply {
|
||||
model = placeholderModel
|
||||
isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ settings.displayName=ProxyAI: Settings
|
|||
settings.openaiQuotaExceeded=OpenAI quota exceeded.
|
||||
settingsConfigurable.displayName.label=Display name:
|
||||
settingsConfigurable.service.label=Selected provider:
|
||||
settingsConfigurable.service.codeCompletion.label=Code completion provider:
|
||||
settingsConfigurable.service.codegpt.apiKey.comment=You can find the API key in your <a href="https://tryproxy.io/account">User settings</a>.
|
||||
settingsConfigurable.service.codegpt.chatCompletionModel.comment=Choose a model optimized for conversational interactions, including assistance with general queries and explanations.
|
||||
settingsConfigurable.service.codegpt.codeCompletionModel.comment=Choose a model tailored for code completion-related tasks.
|
||||
|
|
@ -169,6 +170,7 @@ settingsConfigurable.prompts.exportDialog.exportError=Error exporting prompts se
|
|||
settingsConfigurable.prompts.exportDialog.title=Target File
|
||||
settingsConfigurable.prompts.importDialog.importError=Error importing prompts settings
|
||||
settingsConfigurable.service.ollama.models.refresh=Refresh Models
|
||||
settingsConfigurable.service.ollama.codeCompletionModel.label=Model for code completion:
|
||||
advancedSettingsConfigurable.displayName=ProxyAI: Advanced Settings
|
||||
advancedSettingsConfigurable.proxy.title=HTTP/SOCKS Proxy
|
||||
advancedSettingsConfigurable.proxy.typeComboBoxField.label=Proxy:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import com.intellij.openapi.components.service
|
|||
import com.intellij.openapi.editor.VisualPosition
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_EDITOR_COMPLETION
|
||||
import ee.carlrobert.codegpt.completions.HuggingFaceModel
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.*
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil
|
||||
import ee.carlrobert.llm.client.http.RequestEntity
|
||||
|
|
@ -16,54 +18,15 @@ import testsupport.IntegrationTest
|
|||
|
||||
class CodeCompletionServiceTest : IntegrationTest() {
|
||||
|
||||
fun `test code completion with ProxyAI provider`() {
|
||||
useCodeGPTService()
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
FileUtil.getResourceContent("/codecompletions/code-completion-file.txt")
|
||||
)
|
||||
myFixture.editor.caretModel.moveToVisualPosition(VisualPosition(3, 0))
|
||||
val prefix = """
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
|
||||
[INPUT]
|
||||
p
|
||||
""".trimIndent()
|
||||
val suffix = """
|
||||
|
||||
[\INPUT]
|
||||
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
""".trimIndent()
|
||||
expectCodeGPT(StreamHttpExchange { request: RequestEntity ->
|
||||
assertThat(request.uri.path).isEqualTo("/v1/code/completions")
|
||||
assertThat(request.method).isEqualTo("POST")
|
||||
assertThat(request.body)
|
||||
.extracting("model", "prefix", "suffix", "fileExtension")
|
||||
.containsExactly("TEST_CODE_MODEL", prefix, suffix, "java")
|
||||
listOf(
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("text", "ublic "))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("text", "void"))),
|
||||
jsonMapResponse("choices", jsonArray(jsonMap("text", " main")))
|
||||
)
|
||||
})
|
||||
|
||||
myFixture.type('p')
|
||||
|
||||
assertInlineSuggestion("Failed to display initial inline suggestion.") {
|
||||
"ublic void main" == it
|
||||
}
|
||||
}
|
||||
|
||||
fun `test code completion with OpenAI provider`() {
|
||||
useOpenAIService()
|
||||
useOpenAIService("gpt-4", CODECOMPLETION_ROLE)
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
FileUtil.getResourceContent("/codecompletions/code-completion-file.txt")
|
||||
)
|
||||
myFixture.editor.caretModel.moveToVisualPosition(VisualPosition(3, 0))
|
||||
project.service<CodeCompletionCacheService>().clear()
|
||||
val prefix = """
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
|
||||
|
|
@ -96,14 +59,58 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
}
|
||||
}
|
||||
|
||||
fun `test code completion with Ollama provider and separate model settings`() {
|
||||
useOllamaService(CODECOMPLETION_ROLE)
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
FileUtil.getResourceContent("/codecompletions/code-completion-file.txt")
|
||||
)
|
||||
myFixture.editor.caretModel.moveToVisualPosition(VisualPosition(3, 0))
|
||||
project.service<CodeCompletionCacheService>().clear()
|
||||
val prefix = """
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
|
||||
[INPUT]
|
||||
p
|
||||
""".trimIndent()
|
||||
val suffix = """
|
||||
|
||||
[\INPUT]
|
||||
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
""".trimIndent()
|
||||
expectOllama(StreamHttpExchange { request: RequestEntity ->
|
||||
assertThat(request.uri.path).isEqualTo("/api/generate")
|
||||
assertThat(request.method).isEqualTo("POST")
|
||||
assertThat(request.body)
|
||||
.extracting("model", "prompt", "suffix")
|
||||
.containsExactly(HuggingFaceModel.CODE_QWEN_2_5_3B_Q4_K_M.code, prefix, suffix)
|
||||
listOf(
|
||||
jsonMapResponse(
|
||||
e("model", HuggingFaceModel.CODE_QWEN_2_5_3B_Q4_K_M.code),
|
||||
e("created_at", "2023-08-04T08:52:19.385406455-07:00"),
|
||||
e("response", "rivate void main"),
|
||||
e("done", true),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
myFixture.type('p')
|
||||
|
||||
assertInlineSuggestion("Failed to display initial inline suggestion.") {
|
||||
"rivate void main" == it
|
||||
}
|
||||
}
|
||||
|
||||
fun `_test apply inline suggestions without initial following text`() {
|
||||
useCodeGPTService()
|
||||
useCodeGPTService(CODECOMPLETION_ROLE)
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
"class Node {\n "
|
||||
)
|
||||
myFixture.editor.caretModel.moveToVisualPosition(VisualPosition(1, 2))
|
||||
project.service<CodeCompletionCacheService>().clear()
|
||||
expectCodeGPT(StreamHttpExchange { request: RequestEntity ->
|
||||
assertThat(request.uri.path).isEqualTo("/v1/code/completions")
|
||||
assertThat(request.method).isEqualTo("POST")
|
||||
|
|
@ -214,13 +221,14 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
}
|
||||
|
||||
fun `_test apply inline suggestions with initial following text`() {
|
||||
useCodeGPTService()
|
||||
useCodeGPTService(CODECOMPLETION_ROLE)
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
"if () {\n \n} else {\n}"
|
||||
)
|
||||
myFixture.editor.caretModel.moveToVisualPosition(VisualPosition(0, 4))
|
||||
project.service<CodeCompletionCacheService>().clear()
|
||||
expectCodeGPT(StreamHttpExchange { request: RequestEntity ->
|
||||
assertThat(request.uri.path).isEqualTo("/v1/code/completions")
|
||||
assertThat(request.method).isEqualTo("POST")
|
||||
|
|
@ -285,7 +293,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
}
|
||||
|
||||
fun `_test adjust completion line whitespaces`() {
|
||||
useCodeGPTService()
|
||||
useCodeGPTService(CODECOMPLETION_ROLE)
|
||||
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
|
||||
myFixture.configureByText(
|
||||
"CompletionTest.java",
|
||||
|
|
@ -294,6 +302,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
|
|||
"}"
|
||||
)
|
||||
myFixture.editor.caretModel.moveToVisualPosition(VisualPosition(1, 3))
|
||||
project.service<CodeCompletionCacheService>().clear()
|
||||
expectCodeGPT(StreamHttpExchange { request: RequestEntity ->
|
||||
assertThat(request.uri.path).isEqualTo("/v1/code/completions")
|
||||
assertThat(request.method).isEqualTo("POST")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import ee.carlrobert.codegpt.completions.HuggingFaceModel
|
|||
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.*
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole
|
||||
import ee.carlrobert.codegpt.settings.service.ModelRole.*
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.settings.service.google.GoogleSettings
|
||||
|
|
@ -17,8 +19,8 @@ import java.util.function.BooleanSupplier
|
|||
|
||||
interface ShortcutsTestMixin {
|
||||
|
||||
fun useCodeGPTService() {
|
||||
service<GeneralSettings>().state.selectedService = ServiceType.CODEGPT
|
||||
fun useCodeGPTService(role: ModelRole = CHAT_ROLE) {
|
||||
service<GeneralSettings>().state.setSelectedService(role,ServiceType.CODEGPT)
|
||||
setCredential(CodeGptApiKey, "TEST_API_KEY")
|
||||
service<CodeGPTServiceSettings>().state.run {
|
||||
chatCompletionSettings.model = "TEST_MODEL"
|
||||
|
|
@ -27,8 +29,8 @@ interface ShortcutsTestMixin {
|
|||
}
|
||||
}
|
||||
|
||||
fun useOpenAIService(chatModel: String? = "gpt-4o") {
|
||||
service<GeneralSettings>().state.selectedService = ServiceType.OPENAI
|
||||
fun useOpenAIService(chatModel: String? = "gpt-4o", role: ModelRole = CHAT_ROLE) {
|
||||
service<GeneralSettings>().state.setSelectedService(role, ServiceType.OPENAI)
|
||||
setCredential(OpenaiApiKey, "TEST_API_KEY")
|
||||
service<OpenAISettings>().state.run {
|
||||
model = chatModel
|
||||
|
|
@ -36,24 +38,27 @@ interface ShortcutsTestMixin {
|
|||
}
|
||||
}
|
||||
|
||||
fun useLlamaService(codeCompletionsEnabled: Boolean = false) {
|
||||
GeneralSettings.getCurrentState().selectedService = ServiceType.LLAMA_CPP
|
||||
fun useLlamaService(codeCompletionsEnabled: Boolean = false, role: ModelRole = CHAT_ROLE) {
|
||||
GeneralSettings.getCurrentState().setSelectedService(role,ServiceType.LLAMA_CPP)
|
||||
LlamaSettings.getCurrentState().serverPort = null
|
||||
LlamaSettings.getCurrentState().isCodeCompletionsEnabled = codeCompletionsEnabled
|
||||
LlamaSettings.getCurrentState().huggingFaceModel = HuggingFaceModel.CODE_LLAMA_7B_Q4
|
||||
}
|
||||
|
||||
fun useOllamaService() {
|
||||
GeneralSettings.getCurrentState().selectedService = ServiceType.OLLAMA
|
||||
fun useOllamaService(role: ModelRole = CHAT_ROLE) {
|
||||
GeneralSettings.getCurrentState().setSelectedService(role, ServiceType.OLLAMA)
|
||||
setCredential(OllamaApikey, "TEST_API_KEY")
|
||||
service<OllamaSettings>().state.apply {
|
||||
model = HuggingFaceModel.LLAMA_3_8B_Q6_K.code
|
||||
codeCompletionModel = HuggingFaceModel.CODE_QWEN_2_5_3B_Q4_K_M.code
|
||||
codeCompletionsEnabled = true
|
||||
fimOverride = false
|
||||
host = null
|
||||
}
|
||||
}
|
||||
|
||||
fun useGoogleService() {
|
||||
GeneralSettings.getCurrentState().selectedService = ServiceType.GOOGLE
|
||||
fun useGoogleService(role: ModelRole = CHAT_ROLE) {
|
||||
GeneralSettings.getCurrentState().setSelectedService(role, ServiceType.GOOGLE)
|
||||
setCredential(GoogleApiKey, "TEST_API_KEY")
|
||||
service<GoogleSettings>().state.model = GoogleModel.GEMINI_PRO.code
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue