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:
Viktor Muraviev 2025-07-04 18:16:52 +03:00 committed by Carl-Robert Linnupuu
parent 2ead45efde
commit b5215ec45c
15 changed files with 233 additions and 119 deletions

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -0,0 +1,6 @@
package ee.carlrobert.codegpt.settings.service;
public enum ModelRole {
CHAT_ROLE,
CODECOMPLETION_ROLE
}

View file

@ -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

View file

@ -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

View file

@ -127,7 +127,7 @@ object CodeCompletionRequestFactory {
}
return OllamaCompletionRequest.Builder(
settings.model,
settings.codeCompletionModel,
prompt
)
.setSuffix(if (settings.fimOverride) null else details.suffix)

View file

@ -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)

View file

@ -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

View file

@ -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() {

View file

@ -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)

View file

@ -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)

View file

@ -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
}
}
}
}

View file

@ -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:

View file

@ -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")

View file

@ -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
}