mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-22 03:30:04 +00:00
fix: migrate Custom OpenAI services to use UUIDs and several other fixes
This commit is contained in:
parent
fdfde4243d
commit
163758a2be
27 changed files with 527 additions and 380 deletions
|
|
@ -58,13 +58,32 @@ public class CustomServiceFormTabbedPane extends JBTabbedPane {
|
|||
|
||||
private void setTableData(JBTable table, Map<String, ?> values) {
|
||||
DefaultTableModel model = (DefaultTableModel) table.getModel();
|
||||
model.setRowCount(0);
|
||||
|
||||
for (var entry : values.entrySet()) {
|
||||
model.addRow(new Object[]{entry.getKey(), entry.getValue()});
|
||||
if (hasTableDataChanged(model, values)) {
|
||||
model.setRowCount(0);
|
||||
for (var entry : values.entrySet()) {
|
||||
model.addRow(new Object[]{entry.getKey(), entry.getValue()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTableDataChanged(DefaultTableModel model, Map<String, ?> newValues) {
|
||||
if (model.getRowCount() != newValues.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < model.getRowCount(); i++) {
|
||||
String key = (String) model.getValueAt(i, 0);
|
||||
Object value = model.getValueAt(i, 1);
|
||||
|
||||
if (!newValues.containsKey(key) || !java.util.Objects.equals(newValues.get(key), value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Map<String, Object> getTableData(JBTable table) {
|
||||
var model = (DefaultTableModel) table.getModel();
|
||||
var data = new HashMap<String, Object>();
|
||||
|
|
|
|||
|
|
@ -312,11 +312,11 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
break;
|
||||
case CUSTOM_OPENAI:
|
||||
ModelRegistry.getInstance().getCustomOpenAIModels().stream()
|
||||
.filter(it -> Objects.requireNonNull(modelCode).equals(it.getModel()))
|
||||
.filter(it -> it.getModel().equals(modelCode))
|
||||
.findFirst()
|
||||
.ifPresent(selection -> {
|
||||
templatePresentation.setIcon(Icons.OpenAI);
|
||||
templatePresentation.setText(selection.getModel());
|
||||
templatePresentation.setText(selection.getDisplayName());
|
||||
});
|
||||
break;
|
||||
case ANTHROPIC:
|
||||
|
|
@ -463,7 +463,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
Icons.OpenAI,
|
||||
comboBoxPresentation,
|
||||
() -> ApplicationManager.getApplication().getService(ModelSettings.class)
|
||||
.setModel(featureType, model.getName(), CUSTOM_OPENAI));
|
||||
.setModel(featureType, model.getId(), CUSTOM_OPENAI));
|
||||
}
|
||||
|
||||
private AnAction createGoogleModelAction(GoogleModel model, Presentation comboBoxPresentation) {
|
||||
|
|
|
|||
|
|
@ -139,8 +139,7 @@ object CodeCompletionRequestFactory {
|
|||
val activeService = service<CustomServicesSettings>()
|
||||
.customServiceStateForFeatureType(FeatureType.CODE_COMPLETION)
|
||||
val settings = activeService.codeCompletionSettings
|
||||
val credential =
|
||||
getCredential(CredentialKey.CustomServiceApiKey(activeService.name.orEmpty()))
|
||||
val credential = getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(activeService.id)))
|
||||
return buildCustomRequest(
|
||||
details,
|
||||
settings.url!!,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package ee.carlrobert.codegpt.codecompletions
|
|||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildChatBasedFIMRequest
|
||||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildChatBasedFIMHttpRequest
|
||||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildCustomRequest
|
||||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildLlamaRequest
|
||||
|
|
@ -68,13 +67,13 @@ class CodeCompletionService(private val project: Project) {
|
|||
}
|
||||
|
||||
CUSTOM_OPENAI -> {
|
||||
val activeService = service<CustomServicesSettings>().state.active
|
||||
val activeService =
|
||||
service<CustomServicesSettings>().customServiceStateForFeatureType(FeatureType.CODE_COMPLETION)
|
||||
val customSettings = activeService.codeCompletionSettings
|
||||
val isChatBasedFIM = customSettings.infillTemplate == InfillPromptTemplate.CHAT_COMPLETION
|
||||
|
||||
val isChatBasedFIM =
|
||||
customSettings.infillTemplate == InfillPromptTemplate.CHAT_COMPLETION
|
||||
if (isChatBasedFIM) {
|
||||
// Use chat completion endpoint for chat-based FIM with proper API key substitution
|
||||
val credential = getCredential(CredentialKey.CustomServiceApiKey(activeService.name.orEmpty()))
|
||||
val credential = getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(activeService.id)))
|
||||
createFactory(
|
||||
CompletionClientProvider.getDefaultClientBuilder().build()
|
||||
).newEventSource(
|
||||
|
|
@ -88,7 +87,6 @@ class CodeCompletionService(private val project: Project) {
|
|||
OpenAIChatCompletionEventSourceListener(eventListener)
|
||||
)
|
||||
} else {
|
||||
// Use traditional completion endpoint
|
||||
createFactory(
|
||||
CompletionClientProvider.getDefaultClientBuilder().build()
|
||||
).newEventSource(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class CustomOpenAIRequestFactory : BaseRequestFactory() {
|
|||
params.psiStructure
|
||||
),
|
||||
true,
|
||||
getCredential(CredentialKey.CustomServiceApiKey(service.name.orEmpty()))
|
||||
getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(service.id)))
|
||||
)
|
||||
return CustomOpenAIRequest(request)
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ class CustomOpenAIRequestFactory : BaseRequestFactory() {
|
|||
OpenAIChatCompletionStandardMessage("user", userPrompt)
|
||||
),
|
||||
stream,
|
||||
getCredential(CredentialKey.CustomServiceApiKey(service.name.orEmpty()))
|
||||
getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(service.id)))
|
||||
)
|
||||
return CustomOpenAIRequest(request)
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ class CustomOpenAIRequestFactory : BaseRequestFactory() {
|
|||
service.chatCompletionSettings,
|
||||
messages,
|
||||
true,
|
||||
getCredential(CredentialKey.CustomServiceApiKey(service.name.orEmpty()))
|
||||
getCredential(CredentialKey.CustomServiceApiKeyById(requireNotNull(service.id)))
|
||||
)
|
||||
return CustomOpenAIRequest(request)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,10 +48,15 @@ object CredentialsStore {
|
|||
override val value: String = "OPENAI_API_KEY"
|
||||
}
|
||||
|
||||
@Deprecated("Only for migration")
|
||||
data class CustomServiceApiKey(val name: String) : CredentialKey() {
|
||||
override val value: String = "CUSTOM_SERVICE_API_KEY:$name"
|
||||
}
|
||||
|
||||
data class CustomServiceApiKeyById(val id: String) : CredentialKey() {
|
||||
override val value: String = "CUSTOM_SERVICE_API_KEY_ID:$id"
|
||||
}
|
||||
|
||||
@Deprecated("Only for migration")
|
||||
data object CustomServiceApiKeyLegacy : CredentialKey() {
|
||||
override val value: String = "CUSTOM_SERVICE_API_KEY"
|
||||
|
|
@ -73,4 +78,4 @@ object CredentialsStore {
|
|||
override val value: String = "MISTRAL_API_KEY"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package ee.carlrobert.codegpt.settings
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.project.Project
|
||||
import git4idea.GitUtil
|
||||
import ee.carlrobert.codegpt.util.GitUtil
|
||||
import git4idea.branch.GitBranchUtil
|
||||
import java.time.LocalDate
|
||||
|
||||
|
|
@ -16,7 +17,10 @@ enum class Placeholder(val description: String, val code: String) {
|
|||
"The complete source code contents of all files currently open in the IDE editor tabs, maintaining their formatting and structure.",
|
||||
"$" + "OPEN_FILES"
|
||||
),
|
||||
ACTIVE_CONVERSATION("The complete conversation history with the AI assistant, including the most recent response and any relevant context from the current interaction.", "$" + "ACTIVE_CONVERSATION"),
|
||||
ACTIVE_CONVERSATION(
|
||||
"The complete conversation history with the AI assistant, including the most recent response and any relevant context from the current interaction.",
|
||||
"$" + "ACTIVE_CONVERSATION"
|
||||
),
|
||||
PREFIX("Code before the cursor.", "$" + "PREFIX"),
|
||||
SUFFIX("Code after the cursor.", "$" + "SUFFIX"),
|
||||
FIM_PROMPT(
|
||||
|
|
@ -36,15 +40,19 @@ class DatePlaceholderStrategy : PlaceholderStrategy {
|
|||
}
|
||||
|
||||
class BranchNamePlaceholderStrategy(val project: Project) : PlaceholderStrategy {
|
||||
private val logger = thisLogger()
|
||||
|
||||
override fun getReplacementValue(): String {
|
||||
return try {
|
||||
val repositories = GitUtil.getRepositoryManager(project).repositories
|
||||
if (repositories.isEmpty() || repositories.size != 1) {
|
||||
val repository = GitUtil.getProjectRepository(project)
|
||||
if (repository == null) {
|
||||
logger.error("Couldn't find repository for project")
|
||||
return "BRANCH-UNKNOWN"
|
||||
}
|
||||
|
||||
GitBranchUtil.getBranchNameOrRev(repositories[0])
|
||||
} catch (ignore: Exception) {
|
||||
GitBranchUtil.getBranchNameOrRev(repository)
|
||||
} catch (ex: Exception) {
|
||||
logger.error("Couldn't get git branch name replacement value", ex)
|
||||
"BRANCH-UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ object LegacySettingsMigration {
|
|||
return try {
|
||||
val generalState = GeneralSettings.getCurrentState()
|
||||
val selectedService = generalState.selectedService
|
||||
|
||||
|
||||
if (selectedService != null) {
|
||||
generalState.selectedService = null
|
||||
createMigratedState(selectedService)
|
||||
|
|
@ -40,7 +40,7 @@ object LegacySettingsMigration {
|
|||
private fun createMigratedState(selectedService: ServiceType): ModelSettingsState {
|
||||
return ModelSettingsState().apply {
|
||||
val chatModel = getLegacyChatModelForService(selectedService)
|
||||
|
||||
|
||||
setModelSelection(FeatureType.CHAT, chatModel, selectedService)
|
||||
setModelSelection(FeatureType.AUTO_APPLY, chatModel, selectedService)
|
||||
setModelSelection(FeatureType.COMMIT_MESSAGE, chatModel, selectedService)
|
||||
|
|
@ -49,7 +49,7 @@ object LegacySettingsMigration {
|
|||
|
||||
val codeModel = getLegacyCodeModelForService(selectedService)
|
||||
setModelSelection(FeatureType.CODE_COMPLETION, codeModel, selectedService)
|
||||
|
||||
|
||||
if (selectedService == ServiceType.PROXYAI) {
|
||||
setModelSelection(FeatureType.NEXT_EDIT, ModelRegistry.ZETA, ServiceType.PROXYAI)
|
||||
} else {
|
||||
|
|
@ -71,7 +71,8 @@ object LegacySettingsMigration {
|
|||
}
|
||||
|
||||
ServiceType.ANTHROPIC -> {
|
||||
AnthropicSettings.getCurrentState().model ?: ModelRegistry.CLAUDE_SONNET_4_20250514
|
||||
AnthropicSettings.getCurrentState().model
|
||||
?: ModelRegistry.CLAUDE_SONNET_4_20250514
|
||||
}
|
||||
|
||||
ServiceType.GOOGLE -> {
|
||||
|
|
@ -94,15 +95,10 @@ object LegacySettingsMigration {
|
|||
}
|
||||
|
||||
ServiceType.CUSTOM_OPENAI -> {
|
||||
val customServicesSettings = service<CustomServicesSettings>()
|
||||
val services = customServicesSettings.state.services
|
||||
|
||||
val activeServiceName = customServicesSettings.state.active.name
|
||||
if (!activeServiceName.isNullOrBlank()) {
|
||||
activeServiceName
|
||||
} else {
|
||||
services.map { it.name }.lastOrNull()?.takeIf { it.isNotBlank() } ?: "Default"
|
||||
}
|
||||
service<CustomServicesSettings>().state.services
|
||||
.map { it.name }
|
||||
.lastOrNull()
|
||||
?.takeIf { it.isNotBlank() } ?: "Default"
|
||||
}
|
||||
|
||||
ServiceType.MISTRAL -> {
|
||||
|
|
@ -111,7 +107,7 @@ object LegacySettingsMigration {
|
|||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Failed to get legacy chat model for $serviceType", e)
|
||||
getDefaultModelForService(serviceType)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,28 +158,4 @@ object LegacySettingsMigration {
|
|||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultModelForService(serviceType: ServiceType): String {
|
||||
return when (serviceType) {
|
||||
ServiceType.PROXYAI -> ModelRegistry.GEMINI_FLASH_2_5
|
||||
ServiceType.OPENAI -> ModelRegistry.GPT_4O
|
||||
ServiceType.ANTHROPIC -> ModelRegistry.CLAUDE_SONNET_4_20250514
|
||||
ServiceType.GOOGLE -> ModelRegistry.GEMINI_2_0_FLASH
|
||||
ServiceType.MISTRAL -> ModelRegistry.DEVSTRAL_MEDIUM_2507
|
||||
ServiceType.OLLAMA -> ModelRegistry.LLAMA_3_2
|
||||
ServiceType.LLAMA_CPP -> ModelRegistry.LLAMA_3_2_3B_INSTRUCT
|
||||
ServiceType.CUSTOM_OPENAI -> {
|
||||
// For Custom OpenAI, try to use the active service name if available
|
||||
// If not available, use a placeholder that won't break model selection
|
||||
try {
|
||||
val customServicesSettings = service<CustomServicesSettings>()
|
||||
val activeService = customServicesSettings.state.active
|
||||
activeService?.name?.takeIf { it.isNotBlank() } ?: "Custom OpenAI"
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Could not access CustomServicesSettings for default model, using placeholder", e)
|
||||
"Custom OpenAI"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -286,17 +286,15 @@ class ModelRegistry {
|
|||
return try {
|
||||
val customServicesSettings = service<CustomServicesSettings>()
|
||||
customServicesSettings.state.services.mapNotNull { service ->
|
||||
if (service.name.isNullOrBlank()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
val serviceId = service.id ?: return@mapNotNull null
|
||||
val serviceName = service.name ?: ""
|
||||
val modelFromBody = service.codeCompletionSettings.body["model"]
|
||||
val modelName = (modelFromBody as? String)
|
||||
val displayName = if (!modelName.isNullOrEmpty()) {
|
||||
if (modelName.length > 20) "$serviceName (...${modelName.takeLast(20)})" else "$serviceName ($modelName)"
|
||||
} else serviceName
|
||||
|
||||
service.name?.let { serviceName ->
|
||||
val modelFromBody = service.codeCompletionSettings.body["model"]
|
||||
val modelName = (modelFromBody as? String) ?: "Unknown Model"
|
||||
val displayName = "$serviceName ($modelName)"
|
||||
|
||||
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceName, displayName)
|
||||
}
|
||||
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceId, displayName)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to get Custom OpenAI code models", e)
|
||||
|
|
@ -524,20 +522,14 @@ class ModelRegistry {
|
|||
return try {
|
||||
val customServicesSettings = service<CustomServicesSettings>()
|
||||
customServicesSettings.state.services.mapNotNull { service ->
|
||||
if (service.name.isNullOrBlank()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
val serviceId = service.id ?: return@mapNotNull null
|
||||
val serviceName = service.name ?: ""
|
||||
val modelName = service.chatCompletionSettings.body["model"] as? String
|
||||
val displayName = if (!modelName.isNullOrEmpty()) {
|
||||
if (modelName.length > 20) "$serviceName (...${modelName.takeLast(20)})" else "$serviceName ($modelName)"
|
||||
} else serviceName
|
||||
|
||||
service.name?.let { serviceName ->
|
||||
val modelName = service.chatCompletionSettings.body["model"] as? String
|
||||
val displayName = if (modelName != null) {
|
||||
"$serviceName ($modelName)"
|
||||
} else {
|
||||
serviceName
|
||||
}
|
||||
|
||||
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceName, displayName)
|
||||
}
|
||||
ModelSelection(ServiceType.CUSTOM_OPENAI, serviceId, displayName)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to get Custom OpenAI models", e)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.intellij.openapi.components.*
|
|||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ModelChangeNotifier
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
|
||||
@Service
|
||||
@State(
|
||||
|
|
@ -57,6 +58,7 @@ class ModelSettings : SimplePersistentStateComponent<ModelSettingsState>(ModelSe
|
|||
val oldState = this.state
|
||||
super.loadState(state)
|
||||
|
||||
migrateCustomOpenAIModelCodesToIds()
|
||||
migrateMissingProviderInformation()
|
||||
notifyIfChanged(oldState, this.state)
|
||||
}
|
||||
|
|
@ -74,29 +76,15 @@ class ModelSettings : SimplePersistentStateComponent<ModelSettingsState>(ModelSe
|
|||
notifyModelChange(featureType, model, serviceType)
|
||||
}
|
||||
|
||||
fun getModelSelection(featureType: FeatureType): ModelSelection {
|
||||
fun getModelSelection(featureType: FeatureType): ModelSelection? {
|
||||
val details = getModelDetailsState(featureType)
|
||||
if (details == null) {
|
||||
val defaultModel = ModelRegistry.getInstance().getDefaultModelForFeature(featureType)
|
||||
state.setModelSelection(featureType, defaultModel.model, defaultModel.provider)
|
||||
return defaultModel
|
||||
}
|
||||
|
||||
return details.model?.let { model ->
|
||||
return details?.model?.let { model ->
|
||||
details.provider?.let { provider ->
|
||||
ModelRegistry.getInstance().findModel(provider, model)
|
||||
}
|
||||
} ?: run {
|
||||
val defaultModel = ModelRegistry.getInstance().getDefaultModelForFeature(featureType)
|
||||
state.setModelSelection(featureType, defaultModel.model, defaultModel.provider)
|
||||
defaultModel
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrCreateModelSelection(featureType: FeatureType): ModelSelection {
|
||||
return getModelSelection(featureType)
|
||||
}
|
||||
|
||||
fun getModelForFeature(featureType: FeatureType): String? {
|
||||
return getModelDetailsState(featureType)?.model
|
||||
}
|
||||
|
|
@ -136,19 +124,7 @@ class ModelSettings : SimplePersistentStateComponent<ModelSettingsState>(ModelSe
|
|||
}
|
||||
|
||||
private fun findServiceTypeForModel(featureType: FeatureType, modelId: String?): ServiceType {
|
||||
if (modelId == null) return ServiceType.PROXYAI
|
||||
|
||||
val provider = getProviderForFeature(featureType)
|
||||
val models = getModelsForFeatureType(featureType)
|
||||
|
||||
if (provider != null) {
|
||||
val modelWithProvider = models.find { it.model == modelId && it.provider == provider }
|
||||
if (modelWithProvider != null) {
|
||||
return modelWithProvider.provider
|
||||
}
|
||||
}
|
||||
|
||||
return models.find { it.model == modelId }?.provider ?: ServiceType.PROXYAI
|
||||
return ServiceType.CUSTOM_OPENAI
|
||||
}
|
||||
|
||||
private fun migrateMissingProviderInformation() {
|
||||
|
|
@ -172,7 +148,33 @@ class ModelSettings : SimplePersistentStateComponent<ModelSettingsState>(ModelSe
|
|||
return models.find { it.model == modelCode }?.provider
|
||||
}
|
||||
|
||||
private fun migrateCustomOpenAIModelCodesToIds() {
|
||||
val servicesByName: Map<String, List<String>> = try {
|
||||
CustomServicesSettings::class.java
|
||||
val settings = service<CustomServicesSettings>()
|
||||
settings.state.services.groupBy({ it.name ?: "" }, { it.id ?: "" }).filterKeys { it.isNotEmpty() }
|
||||
} catch (_: Exception) {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
if (servicesByName.isEmpty()) return
|
||||
|
||||
FeatureType.entries.forEach { featureType ->
|
||||
val details = state.getModelSelection(featureType) ?: return@forEach
|
||||
if (details.provider == ServiceType.CUSTOM_OPENAI && !details.model.isNullOrBlank()) {
|
||||
val current = details.model!!
|
||||
val ids = servicesByName[current]
|
||||
if (ids != null && ids.size == 1) {
|
||||
val id = ids.first()
|
||||
if (id.isNotBlank() && id != current) {
|
||||
state.setModelSelection(featureType, id, ServiceType.CUSTOM_OPENAI)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(): ModelSettings = service()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ class ModelSettingsState : BaseState() {
|
|||
val registry = ModelRegistry.getInstance()
|
||||
FeatureType.entries.forEach { featureType ->
|
||||
val defaultModel = registry.getDefaultModelForFeature(featureType)
|
||||
setModelSelection(featureType, defaultModel.model, defaultModel.provider)
|
||||
if (defaultModel != null) {
|
||||
setModelSelection(featureType, defaultModel.model, defaultModel.provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,15 +9,13 @@ import ee.carlrobert.codegpt.settings.models.ModelRegistry
|
|||
import ee.carlrobert.codegpt.settings.models.ModelSelection
|
||||
import ee.carlrobert.codegpt.settings.models.ModelSettings
|
||||
import ee.carlrobert.codegpt.settings.models.ModelSettingsForm
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTAvailableModels
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTModel
|
||||
import ee.carlrobert.codegpt.util.ApplicationUtil
|
||||
import ee.carlrobert.llm.client.codegpt.PricingPlan
|
||||
import javax.swing.JComponent
|
||||
|
||||
class ModelReplacementDialog(
|
||||
private val project: Project?,
|
||||
serviceType: ServiceType
|
||||
serviceType: ServiceType,
|
||||
private val preferredCustomServiceId: String? = null
|
||||
) : DialogWrapper(project) {
|
||||
|
||||
private val modelSettingsForm =
|
||||
|
|
@ -67,19 +65,28 @@ class ModelReplacementDialog(
|
|||
super.dispose()
|
||||
}
|
||||
|
||||
private fun generateInitialModelSelections(serviceType: ServiceType): Map<FeatureType, ModelSelection?>? {
|
||||
private fun generateInitialModelSelections(serviceType: ServiceType): Map<FeatureType, ModelSelection?> {
|
||||
val registry = ModelRegistry.getInstance()
|
||||
|
||||
return when (serviceType) {
|
||||
ServiceType.PROXYAI -> {
|
||||
val userDetails = project?.let { CODEGPT_USER_DETAILS.get(it) }
|
||||
FeatureType.entries.associateWith { featureType ->
|
||||
return FeatureType.entries.associateWith { featureType ->
|
||||
when (serviceType) {
|
||||
ServiceType.PROXYAI -> {
|
||||
val userDetails = project?.let { CODEGPT_USER_DETAILS.get(it) }
|
||||
registry.getDefaultModelForFeature(featureType, userDetails?.pricingPlan)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
FeatureType.entries.associateWith { featureType ->
|
||||
ServiceType.CUSTOM_OPENAI -> {
|
||||
val models = registry.getAllModelsForFeature(featureType)
|
||||
.filter { it.provider == serviceType }
|
||||
|
||||
if (!preferredCustomServiceId.isNullOrBlank()) {
|
||||
models.firstOrNull { it.model == preferredCustomServiceId }
|
||||
?: models.firstOrNull()
|
||||
} else {
|
||||
models.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
registry.getAllModelsForFeature(featureType)
|
||||
.firstOrNull { it.provider == serviceType }
|
||||
}
|
||||
|
|
@ -95,70 +102,59 @@ class ModelReplacementDialog(
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun showDialog(serviceType: ServiceType): DialogResult {
|
||||
val dialog = ModelReplacementDialog(ApplicationUtil.findCurrentProject(), serviceType)
|
||||
fun showDialog(
|
||||
serviceType: ServiceType,
|
||||
preferredCustomServiceId: String? = null
|
||||
): DialogResult {
|
||||
val dialog = ModelReplacementDialog(
|
||||
ApplicationUtil.findCurrentProject(),
|
||||
serviceType,
|
||||
preferredCustomServiceId
|
||||
)
|
||||
dialog.show()
|
||||
return dialog.result
|
||||
}
|
||||
|
||||
fun showDialogIfNeeded(serviceType: ServiceType): DialogResult {
|
||||
return if (shouldShowDialog(serviceType)) {
|
||||
showDialog(serviceType)
|
||||
fun showDialogIfNeeded(
|
||||
serviceType: ServiceType,
|
||||
preferredCustomServiceId: String? = null
|
||||
): DialogResult {
|
||||
return if (shouldShowDialog(serviceType, preferredCustomServiceId)) {
|
||||
showDialog(serviceType, preferredCustomServiceId)
|
||||
} else {
|
||||
DialogResult.KEEP_MODELS
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldShowDialog(serviceType: ServiceType): Boolean {
|
||||
if (serviceType == ServiceType.PROXYAI) {
|
||||
val project = ApplicationUtil.findCurrentProject()
|
||||
val userDetails = project?.let { CODEGPT_USER_DETAILS.get(it) }
|
||||
val modelSettings = service<ModelSettings>()
|
||||
val registry = service<ModelRegistry>()
|
||||
|
||||
return FeatureType.entries.any { featureType ->
|
||||
val currentSelection = modelSettings.getModelSelection(featureType)
|
||||
val suggestedSelection =
|
||||
registry.getDefaultModelForFeature(featureType, userDetails?.pricingPlan)
|
||||
|
||||
when {
|
||||
currentSelection == null -> true
|
||||
currentSelection.provider != serviceType -> true
|
||||
currentSelection.model != suggestedSelection.model -> {
|
||||
val suggestedModelAccessible =
|
||||
CodeGPTAvailableModels.findByCode(suggestedSelection.model)?.let {
|
||||
isModelAccessible(it, userDetails?.pricingPlan)
|
||||
} == true
|
||||
val currentModelNotAccessible =
|
||||
CodeGPTAvailableModels.findByCode(currentSelection.model)?.let {
|
||||
!isModelAccessible(it, userDetails?.pricingPlan)
|
||||
} == true
|
||||
|
||||
suggestedModelAccessible || currentModelNotAccessible
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldShowDialog(
|
||||
serviceType: ServiceType,
|
||||
preferredCustomServiceId: String? = null
|
||||
): Boolean {
|
||||
return FeatureType.entries.any { featureType ->
|
||||
val registry = service<ModelRegistry>()
|
||||
if (!registry.isFeatureSupportedByProvider(
|
||||
featureType,
|
||||
serviceType
|
||||
)
|
||||
) return@any false
|
||||
|
||||
val currentSelection = service<ModelSettings>().getModelSelection(featureType)
|
||||
val availableModels = service<ModelRegistry>().getAllModelsForFeature(featureType)
|
||||
val availableModels = registry.getAllModelsForFeature(featureType)
|
||||
.filter { it.provider == serviceType }
|
||||
|
||||
if (availableModels.isEmpty()) return@any false
|
||||
|
||||
when {
|
||||
currentSelection == null -> true
|
||||
currentSelection.provider != serviceType -> true
|
||||
availableModels.none { it.model == currentSelection.model } -> true
|
||||
currentSelection?.provider != null && currentSelection.provider != serviceType -> true
|
||||
|
||||
serviceType == ServiceType.CUSTOM_OPENAI && !preferredCustomServiceId.isNullOrBlank() &&
|
||||
currentSelection != null && currentSelection.model != preferredCustomServiceId -> true
|
||||
|
||||
currentSelection != null && availableModels.none { it.model == currentSelection.model } -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isModelAccessible(model: CodeGPTModel, userPricingPlan: PricingPlan?): Boolean {
|
||||
if (userPricingPlan == null) return false
|
||||
return userPricingPlan.ordinal >= model.pricingPlan.ordinal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,32 @@ class ModelSelectionService {
|
|||
}
|
||||
}
|
||||
|
||||
fun syncWithAvailableCustomOpenAIModels(preferredServiceId: String? = null) {
|
||||
val registry = service<ModelRegistry>()
|
||||
val settings = service<ModelSettings>()
|
||||
|
||||
FeatureType.entries.forEach { featureType ->
|
||||
if (!registry.isFeatureSupportedByProvider(featureType, ServiceType.CUSTOM_OPENAI)) return@forEach
|
||||
|
||||
val current = settings.getModelSelection(featureType)
|
||||
if (current?.provider != ServiceType.CUSTOM_OPENAI) return@forEach
|
||||
|
||||
val available = registry.getAllModelsForFeature(featureType)
|
||||
.filter { it.provider == ServiceType.CUSTOM_OPENAI }
|
||||
|
||||
val isCurrentValid = available.any { it.model == current.model }
|
||||
|
||||
if (!isCurrentValid) {
|
||||
val newId = when {
|
||||
!preferredServiceId.isNullOrBlank() && available.any { it.model == preferredServiceId } -> preferredServiceId
|
||||
available.isNotEmpty() -> available.first().model
|
||||
else -> null
|
||||
}
|
||||
settings.setModelWithProvider(featureType, newId, ServiceType.CUSTOM_OPENAI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val logger = thisLogger()
|
||||
|
|
@ -68,4 +94,4 @@ class ModelSelectionService {
|
|||
return ApplicationManager.getApplication().getService(ModelSelectionService::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ package ee.carlrobert.codegpt.settings.service.custom
|
|||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import ee.carlrobert.codegpt.settings.service.ModelReplacementDialog
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.custom.form.CustomServiceListForm
|
||||
import ee.carlrobert.codegpt.settings.service.ModelSelectionService
|
||||
import ee.carlrobert.codegpt.settings.service.custom.form.CustomServiceForm
|
||||
import ee.carlrobert.codegpt.util.coroutines.EdtDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
|
@ -14,14 +13,14 @@ import javax.swing.JComponent
|
|||
class CustomServiceConfigurable : Configurable {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + EdtDispatchers.Default)
|
||||
private lateinit var component: CustomServiceListForm
|
||||
private lateinit var component: CustomServiceForm
|
||||
|
||||
override fun getDisplayName(): String {
|
||||
return "ProxyAI: Custom Service"
|
||||
}
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
component = CustomServiceListForm(service<CustomServicesSettings>(), coroutineScope)
|
||||
component = CustomServiceForm(service<CustomServicesSettings>(), coroutineScope)
|
||||
return component.getForm()
|
||||
}
|
||||
|
||||
|
|
@ -29,8 +28,8 @@ class CustomServiceConfigurable : Configurable {
|
|||
|
||||
override fun apply() {
|
||||
component.applyChanges()
|
||||
|
||||
ModelReplacementDialog.showDialogIfNeeded(ServiceType.CUSTOM_OPENAI)
|
||||
ModelSelectionService.getInstance()
|
||||
.syncWithAvailableCustomOpenAIModels(component.getSelectedServiceId())
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
|
|
@ -40,4 +39,4 @@ class CustomServiceConfigurable : Configurable {
|
|||
override fun disposeUIResources() {
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceChatC
|
|||
import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceCodeCompletionTemplate
|
||||
import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTemplate
|
||||
import ee.carlrobert.codegpt.util.BaseConverter
|
||||
import java.util.UUID
|
||||
import ee.carlrobert.codegpt.util.MapConverter
|
||||
|
||||
private const val DEFAULT_SERVICE_SETTINGS_NANE = "Default"
|
||||
|
|
@ -63,13 +64,8 @@ class CustomServicesSettings :
|
|||
val migrated = CustomServiceSettingsState().apply { copyFrom(oldSettingsService.state) }
|
||||
state.services.clear()
|
||||
state.services.add(migrated)
|
||||
state.active = migrated
|
||||
|
||||
CredentialsStore.setCredential(CredentialsStore.CredentialKey.CustomServiceApiKeyLegacy, null)
|
||||
CredentialsStore.setCredential(
|
||||
CredentialsStore.CredentialKey.CustomServiceApiKey(state.active.name.orEmpty()),
|
||||
oldApiKey
|
||||
)
|
||||
|
||||
oldSettingsService.state.apply {
|
||||
template = CustomServiceTemplate.OPENAI
|
||||
|
|
@ -91,21 +87,49 @@ class CustomServicesSettings :
|
|||
headers = mutableMapOf()
|
||||
}
|
||||
}
|
||||
|
||||
state.services.forEach { svc ->
|
||||
if (svc.id.isNullOrBlank()) {
|
||||
svc.id = UUID.randomUUID().toString()
|
||||
}
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val services = state.services.filter { !it.id.isNullOrBlank() }
|
||||
val groups = services.groupBy { it.name ?: "" }
|
||||
services.forEach { svc ->
|
||||
val id = svc.id ?: return@forEach
|
||||
val name = svc.name ?: return@forEach
|
||||
val unique = name.isNotEmpty() && (groups[name]?.size == 1)
|
||||
if (unique) {
|
||||
val idKey = CredentialsStore.CredentialKey.CustomServiceApiKeyById(id)
|
||||
val hasId = !CredentialsStore.getCredential(idKey).isNullOrEmpty()
|
||||
if (!hasId) {
|
||||
val legacy = CredentialsStore.getCredential(
|
||||
CredentialsStore.CredentialKey.CustomServiceApiKey(name)
|
||||
)
|
||||
if (!legacy.isNullOrEmpty()) {
|
||||
CredentialsStore.setCredential(idKey, legacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun customServiceStateForFeatureType(featureType: FeatureType): CustomServiceSettingsState {
|
||||
val modelSelection = service<ModelSelectionService>()
|
||||
val featureSelection = modelSelection.getModelSelectionForFeature(featureType)
|
||||
|
||||
if (featureSelection.provider != ServiceType.CUSTOM_OPENAI)
|
||||
if (featureSelection?.provider != ServiceType.CUSTOM_OPENAI)
|
||||
throw IllegalStateException(
|
||||
"Current selected ServiceType (${featureSelection}) is not of type 'CUSTOM_OPENAI'. " +
|
||||
"This function should not be called in this context!"
|
||||
)
|
||||
|
||||
return this.state.services
|
||||
.find { it.name == featureSelection.model }
|
||||
?: throw IllegalStateException("Unable to find custom service with name '${featureSelection.model}'.")
|
||||
.find { it.id == featureSelection.model }
|
||||
?: throw IllegalStateException("Unable to find custom service with id '${featureSelection.model}'.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,8 +144,6 @@ class CustomServicesState(
|
|||
@get:OptionTag(converter = CustomServiceSettingsListConverter::class)
|
||||
var services by list<CustomServiceSettingsState>()
|
||||
|
||||
var active by property(initialState)
|
||||
|
||||
init {
|
||||
services.add(initialState)
|
||||
}
|
||||
|
|
@ -129,6 +151,7 @@ class CustomServicesState(
|
|||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class CustomServiceSettingsState : BaseState() {
|
||||
var id by string(UUID.randomUUID().toString())
|
||||
var name by string(DEFAULT_SERVICE_SETTINGS_NANE)
|
||||
var template by enum(CustomServiceTemplate.OPENAI)
|
||||
var chatCompletionSettings by property(CustomServiceChatCompletionSettingsState())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package ee.carlrobert.codegpt.settings.service.custom.form
|
||||
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.observable.util.whenTextChanged
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
|
|
@ -31,7 +29,9 @@ class CustomServiceChatCompletionForm(
|
|||
)
|
||||
|
||||
init {
|
||||
testConnectionButton.addActionListener { testConnection() }
|
||||
testConnectionButton.addActionListener {
|
||||
testConnection()
|
||||
}
|
||||
}
|
||||
|
||||
var url: String
|
||||
|
|
@ -73,42 +73,70 @@ class CustomServiceChatCompletionForm(
|
|||
}
|
||||
|
||||
private fun testConnection() {
|
||||
testConnectionButton.isEnabled = false
|
||||
testConnectionButton.text = "Testing..."
|
||||
|
||||
val request = CustomOpenAIRequestFactory.buildCustomOpenAICompletionRequest(
|
||||
"Test",
|
||||
urlField.text,
|
||||
tabbedPane.headers,
|
||||
tabbedPane.body,
|
||||
getApiKey.invoke()
|
||||
|
||||
)
|
||||
|
||||
CompletionRequestService.getInstance().getCustomOpenAIChatCompletionAsync(
|
||||
CustomOpenAIRequestFactory.buildCustomOpenAICompletionRequest(
|
||||
"Test",
|
||||
urlField.text,
|
||||
tabbedPane.headers,
|
||||
tabbedPane.body,
|
||||
getApiKey.invoke()
|
||||
),
|
||||
request,
|
||||
TestConnectionEventListener()
|
||||
)
|
||||
}
|
||||
|
||||
internal inner class TestConnectionEventListener : CompletionEventListener<String?> {
|
||||
private var responseReceived = false
|
||||
|
||||
override fun onMessage(value: String?, eventSource: EventSource) {
|
||||
if (!value.isNullOrEmpty()) {
|
||||
runInEdt {
|
||||
OverlayUtil.showBalloon(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.connectionSuccess"),
|
||||
MessageType.INFO,
|
||||
testConnectionButton
|
||||
)
|
||||
eventSource.cancel()
|
||||
}
|
||||
if (!responseReceived) {
|
||||
responseReceived = true
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
OverlayUtil.showBalloon(
|
||||
"Connection successful!",
|
||||
MessageType.INFO,
|
||||
testConnectionButton
|
||||
)
|
||||
eventSource.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: ErrorDetails, ex: Throwable) {
|
||||
runInEdt {
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
OverlayUtil.showBalloon(
|
||||
"Connection failed: ${error.message}",
|
||||
MessageType.ERROR,
|
||||
testConnectionButton
|
||||
)
|
||||
}
|
||||
|
||||
override fun onComplete(messageBuilder: StringBuilder) {
|
||||
if (!responseReceived) {
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
OverlayUtil.showBalloon(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.connectionFailed")
|
||||
+ "\n\n"
|
||||
+ error.message,
|
||||
MessageType.ERROR,
|
||||
"Connection successful!",
|
||||
MessageType.INFO,
|
||||
testConnectionButton
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(messageBuilder: StringBuilder) {
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package ee.carlrobert.codegpt.settings.service.custom.form
|
|||
|
||||
import com.intellij.icons.AllIcons.General
|
||||
import com.intellij.ide.HelpTooltip
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.openapi.ui.panel.ComponentPanelBuilder
|
||||
|
|
@ -162,6 +161,9 @@ class CustomServiceCodeCompletionForm(
|
|||
}
|
||||
|
||||
private fun testConnection() {
|
||||
testConnectionButton.isEnabled = false
|
||||
testConnectionButton.text = "Testing..."
|
||||
|
||||
val selectedTemplate = promptTemplateComboBox.selectedItem as InfillPromptTemplate
|
||||
val testRequest = InfillRequest.Builder("Hello", "!", 0).build()
|
||||
|
||||
|
|
@ -193,31 +195,54 @@ class CustomServiceCodeCompletionForm(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
internal inner class TestConnectionEventListener : CompletionEventListener<String?> {
|
||||
private var responseReceived = false
|
||||
|
||||
override fun onMessage(value: String?, eventSource: EventSource) {
|
||||
if (!value.isNullOrEmpty()) {
|
||||
runInEdt {
|
||||
OverlayUtil.showBalloon(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.connectionSuccess"),
|
||||
MessageType.INFO,
|
||||
testConnectionButton
|
||||
)
|
||||
eventSource.cancel()
|
||||
}
|
||||
if (!responseReceived) {
|
||||
responseReceived = true
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
OverlayUtil.showBalloon(
|
||||
"Connection successful!",
|
||||
MessageType.INFO,
|
||||
testConnectionButton
|
||||
)
|
||||
eventSource.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: ErrorDetails, ex: Throwable) {
|
||||
runInEdt {
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
OverlayUtil.showBalloon(
|
||||
"Connection failed: ${error.message}",
|
||||
MessageType.ERROR,
|
||||
testConnectionButton
|
||||
)
|
||||
}
|
||||
|
||||
override fun onComplete(messageBuilder: StringBuilder) {
|
||||
if (!responseReceived) {
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
OverlayUtil.showBalloon(
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.connectionFailed")
|
||||
+ "\n\n"
|
||||
+ error.message,
|
||||
MessageType.ERROR,
|
||||
"Connection successful!",
|
||||
MessageType.INFO,
|
||||
testConnectionButton
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(messageBuilder: StringBuilder) {
|
||||
testConnectionButton.isEnabled = true
|
||||
testConnectionButton.text =
|
||||
CodeGPTBundle.get("settingsConfigurable.service.custom.openai.testConnection.label")
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePromptTemplateHelpTooltip(template: InfillPromptTemplate) {
|
||||
|
|
|
|||
|
|
@ -31,22 +31,26 @@ import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTempl
|
|||
import ee.carlrobert.codegpt.ui.OverlayUtil
|
||||
import ee.carlrobert.codegpt.ui.UIUtil
|
||||
import ee.carlrobert.codegpt.util.ApplicationUtil
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.toImmutableList
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.awt.FlowLayout
|
||||
import java.awt.event.ItemEvent
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import javax.swing.*
|
||||
|
||||
class CustomServiceListForm(
|
||||
class CustomServiceForm(
|
||||
private val service: CustomServicesSettings,
|
||||
coroutineScope: CoroutineScope
|
||||
private val coroutineScope: CoroutineScope
|
||||
) {
|
||||
|
||||
private val formState = MutableStateFlow(service.state.mapToData())
|
||||
|
|
@ -55,6 +59,9 @@ class CustomServiceListForm(
|
|||
private val customSettingsFileProvider = CustomSettingsFileProvider()
|
||||
|
||||
private var lastSelectedIndex = 0
|
||||
private var selectedServiceId: String? = null
|
||||
private var pendingSelectedId: String? = null
|
||||
private var suppressSelectionEvents: Boolean = false
|
||||
|
||||
private val customProvidersJBList = JBList(formState.value.services)
|
||||
.apply {
|
||||
|
|
@ -62,6 +69,7 @@ class CustomServiceListForm(
|
|||
selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||
|
||||
addListSelectionListener { _ ->
|
||||
if (suppressSelectionEvents) return@addListSelectionListener
|
||||
val localSelectedIndex = selectedIndex
|
||||
if (localSelectedIndex != -1) {
|
||||
if (lastSelectedIndex != -1) {
|
||||
|
|
@ -69,6 +77,7 @@ class CustomServiceListForm(
|
|||
}
|
||||
|
||||
lastSelectedIndex = localSelectedIndex
|
||||
selectedServiceId = model.getElementAt(localSelectedIndex).id
|
||||
updateFormData(lastSelectedIndex)
|
||||
}
|
||||
}
|
||||
|
|
@ -76,9 +85,36 @@ class CustomServiceListForm(
|
|||
|
||||
init {
|
||||
formState
|
||||
.onEach {
|
||||
customProvidersJBList.setListData(it.services.toTypedArray())
|
||||
customProvidersJBList.repaint()
|
||||
.onEach { newState ->
|
||||
val model = customProvidersJBList.model
|
||||
val current = (0 until model.size).map { model.getElementAt(it) }
|
||||
val currentIds = current.map { it.id }
|
||||
val newIds = newState.services.map { it.id }
|
||||
val idsChanged = currentIds != newIds
|
||||
val namesChanged = !idsChanged && current.indices.any { i ->
|
||||
i < newState.services.size && current[i].name != newState.services[i].name
|
||||
}
|
||||
if (idsChanged || namesChanged) {
|
||||
SwingUtilities.invokeLater {
|
||||
suppressSelectionEvents = true
|
||||
try {
|
||||
customProvidersJBList.setListData(newState.services.toTypedArray())
|
||||
val targetId = pendingSelectedId ?: selectedServiceId
|
||||
val idx = newState.services.indexOfFirst { it.id == targetId }
|
||||
val targetIndex = if (idx >= 0) idx else 0
|
||||
if (newState.services.isNotEmpty()) {
|
||||
customProvidersJBList.selectedIndex = targetIndex
|
||||
lastSelectedIndex = targetIndex
|
||||
selectedServiceId = newState.services[targetIndex].id
|
||||
updateFormDataSilently(newState.services[targetIndex])
|
||||
}
|
||||
pendingSelectedId = null
|
||||
customProvidersJBList.repaint()
|
||||
} finally {
|
||||
suppressSelectionEvents = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
|
@ -99,10 +135,7 @@ class CustomServiceListForm(
|
|||
|
||||
init {
|
||||
val selectedItem = formState.value.services.first()
|
||||
|
||||
apiKeyField.text = runBlocking(Dispatchers.IO) {
|
||||
getCredential(CredentialKey.CustomServiceApiKey(selectedItem.name.orEmpty()))
|
||||
}
|
||||
apiKeyField.text = getCredential(CredentialKey.CustomServiceApiKeyById(selectedItem.id))
|
||||
chatCompletionsForm =
|
||||
CustomServiceChatCompletionForm(selectedItem.chatCompletionSettings, this::getApiKey)
|
||||
codeCompletionsForm =
|
||||
|
|
@ -110,31 +143,8 @@ class CustomServiceListForm(
|
|||
tabbedPane = JBTabbedPane().apply {
|
||||
add(CodeGPTBundle.get("shared.chatCompletions"), chatCompletionsForm.form)
|
||||
add(CodeGPTBundle.get("shared.codeCompletions"), codeCompletionsForm.form)
|
||||
templateComboBox.selectedItem = selectedItem.template
|
||||
}
|
||||
nameField.text = selectedItem.name
|
||||
templateComboBox.addItemListener {
|
||||
val template = it.item as CustomServiceTemplate
|
||||
updateTemplateHelpTextTooltip(template)
|
||||
chatCompletionsForm.run {
|
||||
url = template.chatCompletionTemplate.url
|
||||
headers = template.chatCompletionTemplate.headers
|
||||
body = template.chatCompletionTemplate.body
|
||||
}
|
||||
if (template.codeCompletionTemplate != null) {
|
||||
codeCompletionsForm.run {
|
||||
url = template.codeCompletionTemplate.url
|
||||
headers = template.codeCompletionTemplate.headers
|
||||
body = template.codeCompletionTemplate.body
|
||||
parseResponseAsChatCompletions =
|
||||
template.codeCompletionTemplate.parseResponseAsChatCompletions
|
||||
}
|
||||
tabbedPane.setEnabledAt(1, true)
|
||||
} else {
|
||||
tabbedPane.selectedIndex = 0
|
||||
tabbedPane.setEnabledAt(1, false)
|
||||
}
|
||||
}
|
||||
|
||||
exportButton =
|
||||
JButton(CodeGPTBundle.get("settingsConfigurable.service.custom.openai.exportSettings")).apply {
|
||||
addActionListener { exportSettingsToFile() }
|
||||
|
|
@ -143,34 +153,83 @@ class CustomServiceListForm(
|
|||
JButton(CodeGPTBundle.get("settingsConfigurable.service.custom.openai.importSettings")).apply {
|
||||
addActionListener { importSettingsFromFile() }
|
||||
}
|
||||
updateTemplateHelpTextTooltip(selectedItem.template)
|
||||
|
||||
templateComboBox.addItemListener { event ->
|
||||
if (event.stateChange == ItemEvent.SELECTED) {
|
||||
val template = event.item as CustomServiceTemplate
|
||||
updateTemplateHelpTextTooltip(template)
|
||||
|
||||
chatCompletionsForm.run {
|
||||
url = template.chatCompletionTemplate.url
|
||||
headers = template.chatCompletionTemplate.headers
|
||||
body = template.chatCompletionTemplate.body
|
||||
}
|
||||
if (template.codeCompletionTemplate != null) {
|
||||
codeCompletionsForm.run {
|
||||
url = template.codeCompletionTemplate.url
|
||||
headers = template.codeCompletionTemplate.headers
|
||||
body = template.codeCompletionTemplate.body
|
||||
parseResponseAsChatCompletions =
|
||||
template.codeCompletionTemplate.parseResponseAsChatCompletions
|
||||
}
|
||||
tabbedPane.setEnabledAt(1, true)
|
||||
} else {
|
||||
tabbedPane.selectedIndex = 0
|
||||
tabbedPane.setEnabledAt(1, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateFormDataSilently(selectedItem)
|
||||
SwingUtilities.invokeLater {
|
||||
if (customProvidersJBList.model.size > 0) {
|
||||
customProvidersJBList.selectedIndex = 0
|
||||
lastSelectedIndex = 0
|
||||
selectedServiceId = customProvidersJBList.model.getElementAt(0).id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFormData(index: Int) {
|
||||
val selectedItem = formState.value.services[index]
|
||||
SwingUtilities.invokeLater {
|
||||
updateFormDataSilently(selectedItem)
|
||||
}
|
||||
}
|
||||
|
||||
chatCompletionsForm.apply {
|
||||
val chatCompletionSettings = selectedItem.chatCompletionSettings
|
||||
url = chatCompletionSettings.url.orEmpty()
|
||||
body = chatCompletionSettings.body.toMutableMap()
|
||||
headers = chatCompletionSettings.headers.toMutableMap()
|
||||
private fun updateFormDataSilently(selectedItem: CustomServiceSettingsData) {
|
||||
val templateListener = templateComboBox.itemListeners.firstOrNull()
|
||||
templateListener?.let { templateComboBox.removeItemListener(it) }
|
||||
|
||||
try {
|
||||
chatCompletionsForm.apply {
|
||||
val chatCompletionSettings = selectedItem.chatCompletionSettings
|
||||
url = chatCompletionSettings.url.orEmpty()
|
||||
body = chatCompletionSettings.body.toMutableMap()
|
||||
headers = chatCompletionSettings.headers.toMutableMap()
|
||||
}
|
||||
codeCompletionsForm.apply {
|
||||
val codeCompletionSettings = selectedItem.codeCompletionSettings
|
||||
url = codeCompletionSettings.url.orEmpty()
|
||||
body = codeCompletionSettings.body.toMutableMap()
|
||||
headers = codeCompletionSettings.headers.toMutableMap()
|
||||
infillTemplate = codeCompletionSettings.infillTemplate
|
||||
codeCompletionsEnabled = codeCompletionSettings.codeCompletionsEnabled
|
||||
parseResponseAsChatCompletions =
|
||||
codeCompletionSettings.parseResponseAsChatCompletions
|
||||
}
|
||||
|
||||
apiKeyField.text = getCredential(CredentialKey.CustomServiceApiKeyById(selectedItem.id))
|
||||
nameField.text = selectedItem.name
|
||||
templateComboBox.selectedItem = selectedItem.template
|
||||
updateTemplateHelpTextTooltip(selectedItem.template)
|
||||
} finally {
|
||||
templateListener?.let { templateComboBox.addItemListener(it) }
|
||||
}
|
||||
codeCompletionsForm.apply {
|
||||
val codeCompletionSettings = selectedItem.codeCompletionSettings
|
||||
url = codeCompletionSettings.url.orEmpty()
|
||||
body = codeCompletionSettings.body.toMutableMap()
|
||||
headers = codeCompletionSettings.headers.toMutableMap()
|
||||
infillTemplate = codeCompletionSettings.infillTemplate
|
||||
codeCompletionsEnabled = codeCompletionSettings.codeCompletionsEnabled
|
||||
parseResponseAsChatCompletions = codeCompletionSettings.parseResponseAsChatCompletions
|
||||
}
|
||||
apiKeyField.text = selectedItem.apiKey
|
||||
nameField.text = selectedItem.name
|
||||
templateComboBox.selectedItem = selectedItem.template
|
||||
updateTemplateHelpTextTooltip(selectedItem.template)
|
||||
}
|
||||
|
||||
private fun updateStateFromForm(editedIndex: Int) {
|
||||
if (editedIndex < 0 || editedIndex >= formState.value.services.size) return
|
||||
formState.update { state ->
|
||||
val editedItem = state.services[editedIndex]
|
||||
|
||||
|
|
@ -252,41 +311,48 @@ class CustomServiceListForm(
|
|||
|
||||
private fun handleRemoveAction() {
|
||||
val prevSelectedIndex = customProvidersJBList.selectedIndex
|
||||
|
||||
// Update form state before deletion to ensure current edits are saved
|
||||
if (lastSelectedIndex != -1 && lastSelectedIndex < formState.value.services.size) {
|
||||
updateStateFromForm(lastSelectedIndex)
|
||||
}
|
||||
|
||||
val current = formState.value.services
|
||||
val targetNeighborId = when {
|
||||
current.isEmpty() -> null
|
||||
prevSelectedIndex <= 0 && current.size >= 2 -> current[1].id
|
||||
prevSelectedIndex > 0 -> current[prevSelectedIndex - 1].id
|
||||
else -> null
|
||||
}
|
||||
|
||||
formState.update { state ->
|
||||
state.copy(services = state.services.filterIndexed { index, _ ->
|
||||
index != customProvidersJBList.selectedIndex
|
||||
})
|
||||
}
|
||||
val newSelectedIndex = if (prevSelectedIndex == 0) {
|
||||
0
|
||||
} else {
|
||||
prevSelectedIndex - 1
|
||||
state.copy(services = state.services.filterIndexed { index, _ -> index != prevSelectedIndex })
|
||||
}
|
||||
|
||||
pendingSelectedId = targetNeighborId
|
||||
lastSelectedIndex = -1
|
||||
updateFormData(newSelectedIndex)
|
||||
customProvidersJBList.selectedIndex = newSelectedIndex
|
||||
}
|
||||
|
||||
private fun handleDuplicateAction() {
|
||||
formState.update {
|
||||
val selectedIndex = customProvidersJBList.selectedIndex
|
||||
val copiedService =
|
||||
it.services[selectedIndex].copy(name = it.services[selectedIndex].name + "Copied")
|
||||
val src = it.services[selectedIndex]
|
||||
val copiedService = src.copy(
|
||||
id = java.util.UUID.randomUUID().toString(),
|
||||
name = src.name + "Copied"
|
||||
)
|
||||
it.copy(
|
||||
services = it.services + copiedService
|
||||
)
|
||||
}
|
||||
customProvidersJBList.selectedIndex = formState.value.services.lastIndex
|
||||
pendingSelectedId = formState.value.services.last().id
|
||||
}
|
||||
|
||||
private fun handleAddAction() {
|
||||
formState.update {
|
||||
it.copy(
|
||||
services = it.services + CustomServiceSettingsState().apply { name += it.services.size }
|
||||
.mapToData()
|
||||
)
|
||||
}
|
||||
customProvidersJBList.selectedIndex = formState.value.services.lastIndex
|
||||
val newData = CustomServiceSettingsState().apply { name += formState.value.services.size }
|
||||
.mapToData()
|
||||
formState.update { it.copy(services = it.services + newData) }
|
||||
pendingSelectedId = newData.id
|
||||
}
|
||||
|
||||
private fun createContentPanel(): JPanel = FormBuilder.createFormBuilder()
|
||||
|
|
@ -317,7 +383,9 @@ class CustomServiceListForm(
|
|||
fun getApiKey() = String(apiKeyField.password).ifEmpty { null }
|
||||
|
||||
fun isModified(): Boolean {
|
||||
updateStateFromForm(lastSelectedIndex)
|
||||
if (lastSelectedIndex >= 0 && lastSelectedIndex < formState.value.services.size) {
|
||||
updateStateFromForm(lastSelectedIndex)
|
||||
}
|
||||
return service.state.mapToData() != formState.value
|
||||
}
|
||||
|
||||
|
|
@ -376,12 +444,8 @@ class CustomServiceListForm(
|
|||
.inSmartMode(it)
|
||||
.finishOnUiThread(ModalityState.defaultModalityState()) { settings ->
|
||||
if (settings != null) {
|
||||
val newActualService =
|
||||
settings.firstOrNull { it.name == formState.value.active.name }
|
||||
?: settings.first()
|
||||
|
||||
formState.update { state ->
|
||||
state.copy(services = settings, active = newActualService)
|
||||
state.copy(services = settings)
|
||||
}
|
||||
updateFormData(0)
|
||||
}
|
||||
|
|
@ -432,51 +496,41 @@ class CustomServiceListForm(
|
|||
}
|
||||
|
||||
fun applyChanges() {
|
||||
if (!validateServiceNames()) {
|
||||
OverlayUtil.showBalloon(
|
||||
"Service names must be unique",
|
||||
MessageType.ERROR,
|
||||
customProvidersJBList,
|
||||
)
|
||||
return
|
||||
if (lastSelectedIndex != -1 && lastSelectedIndex < formState.value.services.size) {
|
||||
updateStateFromForm(lastSelectedIndex)
|
||||
}
|
||||
|
||||
val formStateValue = formState.value
|
||||
|
||||
val newActualService =
|
||||
formStateValue.services.firstOrNull { it.name == formStateValue.active.name }
|
||||
?: formStateValue.services.first()
|
||||
|
||||
// Cleanup saved api keys
|
||||
val savedServicesName = service.state.services.mapNotNull { it.name }
|
||||
val deletedServices =
|
||||
savedServicesName.subtract(formStateValue.services.mapNotNull { it.name }.toSet())
|
||||
deletedServices.forEach { deletedServiceName ->
|
||||
CredentialsStore.setCredential(
|
||||
CredentialKey.CustomServiceApiKey(deletedServiceName),
|
||||
null
|
||||
)
|
||||
val prevById = service.state.services.associateBy { it.id }
|
||||
val savedIds = prevById.keys.filterNotNull().toSet()
|
||||
val newIds = formStateValue.services.map { it.id }.toSet()
|
||||
val deletedIds = savedIds.subtract(newIds)
|
||||
deletedIds.forEach { deletedId ->
|
||||
CredentialsStore.setCredential(CredentialKey.CustomServiceApiKeyById(deletedId), null)
|
||||
}
|
||||
// Save apiKeys
|
||||
formStateValue.services.forEach {
|
||||
CredentialsStore.setCredential(
|
||||
CredentialKey.CustomServiceApiKey(it.name.orEmpty()),
|
||||
it.apiKey
|
||||
)
|
||||
if (it.id.isNotBlank()) {
|
||||
CredentialsStore.setCredential(
|
||||
CredentialKey.CustomServiceApiKeyById(it.id),
|
||||
it.apiKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Save settings
|
||||
service.state.run {
|
||||
services = formStateValue.services.mapTo(mutableListOf()) { it.mapToState() }
|
||||
active = newActualService.mapToState()
|
||||
}
|
||||
formState.value = service.state.mapToData()
|
||||
}
|
||||
|
||||
private fun validateServiceNames(): Boolean {
|
||||
val serviceNames = formState.value.services.mapNotNull { it.name }
|
||||
val uniqueNames = serviceNames.toSet()
|
||||
return serviceNames.size == uniqueNames.size
|
||||
fun getSelectedServiceId(): String? {
|
||||
val idx = customProvidersJBList.selectedIndex
|
||||
return if (idx >= 0 && idx < formState.value.services.size) {
|
||||
formState.value.services[idx].id
|
||||
} else {
|
||||
selectedServiceId
|
||||
}
|
||||
}
|
||||
|
||||
fun resetForm() {
|
||||
|
|
@ -503,4 +557,4 @@ class CustomServiceListForm(
|
|||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
package ee.carlrobert.codegpt.settings.service.custom.form
|
||||
|
||||
import com.intellij.ui.render.LabelBasedRenderer
|
||||
import ee.carlrobert.codegpt.settings.service.custom.form.model.CustomServiceSettingsData
|
||||
import java.awt.Component
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JList
|
||||
import javax.swing.ListCellRenderer
|
||||
|
||||
internal class CustomServiceNameListRenderer : LabelBasedRenderer(), ListCellRenderer<CustomServiceSettingsData> {
|
||||
private val delegate = List<CustomServiceSettingsData>()
|
||||
internal class CustomServiceNameListRenderer : JLabel(), ListCellRenderer<CustomServiceSettingsData> {
|
||||
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<out CustomServiceSettingsData>,
|
||||
|
|
@ -16,9 +14,12 @@ internal class CustomServiceNameListRenderer : LabelBasedRenderer(), ListCellRen
|
|||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): Component =
|
||||
delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
|
||||
.also { component ->
|
||||
(component as? JLabel)?.text = value?.name ?: ""
|
||||
}
|
||||
}
|
||||
): Component {
|
||||
text = value?.name ?: ""
|
||||
isOpaque = true
|
||||
background = if (isSelected) list.selectionBackground else list.background
|
||||
foreground = if (isSelected) list.selectionForeground else list.foreground
|
||||
font = list.font
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package ee.carlrobert.codegpt.settings.service.custom.form.model
|
||||
|
||||
import ee.carlrobert.codegpt.settings.service.custom.template.CustomServiceTemplate
|
||||
import java.util.UUID
|
||||
|
||||
data class CustomServiceSettingsData(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val name: String?,
|
||||
val template: CustomServiceTemplate,
|
||||
val apiKey: String?,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
package ee.carlrobert.codegpt.settings.service.custom.form.model
|
||||
|
||||
data class CustomServicesStateData(
|
||||
val services: List<CustomServiceSettingsData>,
|
||||
val active: CustomServiceSettingsData
|
||||
)
|
||||
data class CustomServicesStateData(val services: List<CustomServiceSettingsData>)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package ee.carlrobert.codegpt.settings.service.custom.form.model
|
|||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceCodeCompletionSettingsState
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettingsState
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
fun CustomServiceSettingsData.mapToState(): CustomServiceSettingsState =
|
||||
CustomServiceSettingsState().also { serviceState ->
|
||||
serviceState.id = if (id.isBlank()) UUID.randomUUID().toString() else id
|
||||
serviceState.name = name
|
||||
serviceState.template = template
|
||||
serviceState.chatCompletionSettings = chatCompletionSettings.mapToState()
|
||||
|
|
|
|||
|
|
@ -5,25 +5,26 @@ import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletion
|
|||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceCodeCompletionSettingsState
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettingsState
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesState
|
||||
import java.util.UUID
|
||||
|
||||
fun CustomServicesState.mapToData(): CustomServicesStateData =
|
||||
CustomServicesStateData(
|
||||
services = services.map { it.mapToData() },
|
||||
active = active.mapToData()
|
||||
)
|
||||
CustomServicesStateData(services.map { it.mapToData() })
|
||||
|
||||
fun CustomServiceSettingsState.mapToData(): CustomServiceSettingsData =
|
||||
CustomServiceSettingsData(
|
||||
id = id ?: UUID.randomUUID().toString(),
|
||||
name = name,
|
||||
template = template,
|
||||
apiKey = CredentialsStore.getCredential(CredentialsStore.CredentialKey.CustomServiceApiKey(name.orEmpty())),
|
||||
apiKey = if (!id.isNullOrEmpty())
|
||||
CredentialsStore.getCredential(CredentialsStore.CredentialKey.CustomServiceApiKeyById(id!!))
|
||||
else null,
|
||||
chatCompletionSettings = chatCompletionSettings.mapToData(),
|
||||
codeCompletionSettings = codeCompletionSettings.mapToData()
|
||||
)
|
||||
|
||||
fun CustomServiceChatCompletionSettingsState.mapToData(): CustomServiceChatCompletionSettingsData =
|
||||
CustomServiceChatCompletionSettingsData(
|
||||
url = url,
|
||||
url = url ?: "",
|
||||
headers = headers,
|
||||
body = body
|
||||
)
|
||||
|
|
@ -33,7 +34,7 @@ fun CustomServiceCodeCompletionSettingsState.mapToData(): CustomServiceCodeCompl
|
|||
codeCompletionsEnabled = codeCompletionsEnabled,
|
||||
parseResponseAsChatCompletions = parseResponseAsChatCompletions,
|
||||
infillTemplate = infillTemplate,
|
||||
url = url,
|
||||
url = url ?: "",
|
||||
headers = headers,
|
||||
body = body
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,8 +26,13 @@ object GitUtil {
|
|||
@JvmStatic
|
||||
fun getProjectRepository(project: Project): GitRepository? {
|
||||
val repositoryManager = project.service<GitRepositoryManager>()
|
||||
return repositoryManager.getRepositoryForFile(project.guessProjectDir())
|
||||
?: repositoryManager.repositories.firstOrNull()
|
||||
return try {
|
||||
repositoryManager.getRepositoryForFile(project.guessProjectDir())
|
||||
?: repositoryManager.repositories.firstOrNull()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Failed to get git repository", e)
|
||||
repositoryManager.repositories.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentChanges(project: Project): String? {
|
||||
|
|
|
|||
|
|
@ -173,23 +173,14 @@ class ModelSettingsTest : IntegrationTest() {
|
|||
assertThat(notification.serviceType).isEqualTo(ServiceType.ANTHROPIC)
|
||||
}
|
||||
|
||||
fun `test getOrCreateModelSelection with existing selection returns stored model`() {
|
||||
modelSettings.setModelWithProvider(FeatureType.CHAT, "gpt-4o", ServiceType.OPENAI)
|
||||
|
||||
val result = modelSettings.getOrCreateModelSelection(FeatureType.CHAT)
|
||||
|
||||
assertThat(result.provider).isEqualTo(ServiceType.OPENAI)
|
||||
assertThat(result.model).isEqualTo("gpt-4o")
|
||||
}
|
||||
|
||||
fun `test getModelSelection with valid feature returns model selection`() {
|
||||
modelSettings.setModelWithProvider(FeatureType.CHAT, "gpt-4o", ServiceType.OPENAI)
|
||||
|
||||
val result = modelSettings.getModelSelection(FeatureType.CHAT)
|
||||
|
||||
assertThat(result).isNotNull
|
||||
assertThat(result.provider).isEqualTo(ServiceType.OPENAI)
|
||||
assertThat(result.model).isEqualTo("gpt-4o")
|
||||
assertThat(result?.provider).isEqualTo(ServiceType.OPENAI)
|
||||
assertThat(result?.model).isEqualTo("gpt-4o")
|
||||
}
|
||||
|
||||
fun `test getModelForFeature returns stored model`() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue