Skip to content

Commit e6feea3

Browse files
committed
feat(providers): configuration migration
1 parent 81d623d commit e6feea3

File tree

6 files changed

+146
-112
lines changed

6 files changed

+146
-112
lines changed

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/AICommitsUtils.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
44
import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
55
import com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings
66
import com.github.blarc.ai.commits.intellij.plugin.settings.ProjectSettings
7+
import com.intellij.credentialStore.CredentialAttributes
8+
import com.intellij.credentialStore.Credentials
9+
import com.intellij.ide.passwordSafe.PasswordSafe
710
import com.intellij.openapi.components.service
811
import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder
912
import com.intellij.openapi.diff.impl.patch.UnifiedDiffWriter
@@ -127,11 +130,25 @@ object AICommitsUtils {
127130
* If no model type matches, let the request go through and let the OpenAI API handle it
128131
*/
129132
val modelType = ModelType.entries
130-
.filter { AppSettings.instance.openAIModelId.contains(it.name) }
133+
.filter { AppSettings.instance.currentLlmProvider.modelId.contains(it.name) }
131134
.maxByOrNull { it.name.length }
132135
?: return false
133136

134137
val encoding = registry.getEncoding(modelType.encodingType)
135138
return encoding.countTokens(prompt) > modelType.maxContextLength
136139
}
140+
141+
fun retrieveToken(title: String): String? {
142+
val credentials: Credentials? = PasswordSafe.instance.get(getCredentialAttributes(title))
143+
return credentials?.getPasswordAsString()
144+
}
145+
146+
fun getCredentialAttributes(title: String): CredentialAttributes {
147+
return CredentialAttributes(
148+
title,
149+
null,
150+
this.javaClass,
151+
false
152+
)
153+
}
137154
}

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/OpenAIService.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ class OpenAIService {
1515
}
1616

1717
suspend fun generateCommitMessage(prompt: String): String {
18-
return AppSettings.instance.AIProvider.generateCommitMessage(prompt)
18+
return AppSettings.instance.currentLlmProvider.generateCommitMessage(prompt)
1919
}
2020

2121
suspend fun refreshOpenAIModelIds() {
22-
AppSettings.instance.AIProvider.refreshModels()
22+
AppSettings.instance.currentLlmProvider.refreshModels()
2323
}
2424

2525
@Throws(Exception::class)
2626
suspend fun verifyOpenAIConfiguration(host: String, proxy: String?, timeout: String, token: String){
27-
AppSettings.instance.AIProvider.verifyConfiguration(host, proxy, timeout, token)
27+
AppSettings.instance.currentLlmProvider.verifyConfiguration(host, proxy, timeout, token)
2828
}
2929
}

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings.kt

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.github.blarc.ai.commits.intellij.plugin.settings
22

3+
import com.aallam.openai.client.OpenAI
4+
import com.aallam.openai.client.OpenAIHost
35
import com.github.blarc.ai.commits.intellij.plugin.AICommitsUtils
46
import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
57
import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
68
import com.github.blarc.ai.commits.intellij.plugin.settings.prompts.DefaultPrompts
7-
import com.github.blarc.ai.commits.intellij.plugin.settings.providers.OpenAIProvider
9+
import com.github.blarc.ai.commits.intellij.plugin.settings.providers.OpenAIClient
810
import com.intellij.openapi.application.ApplicationManager
911
import com.intellij.openapi.components.PersistentStateComponent
1012
import com.intellij.openapi.components.State
@@ -16,7 +18,10 @@ import java.util.*
1618

1719
@State(
1820
name = AppSettings.SERVICE_NAME,
19-
storages = [Storage("AICommit.xml")]
21+
storages = [
22+
Storage("AICommit.xml", deprecated = true),
23+
Storage("AICommits2.xml")
24+
]
2025
)
2126
class AppSettings : PersistentStateComponent<AppSettings> {
2227

@@ -27,24 +32,44 @@ class AppSettings : PersistentStateComponent<AppSettings> {
2732
@OptionTag(converter = LocaleConverter::class)
2833
var locale: Locale = Locale.ENGLISH
2934

30-
var AIProvider = OpenAIProvider.instance
31-
var AIProviders = setOf(OpenAIProvider.instance)
35+
var llmProviders = setOf(OpenAIClient.instance)
36+
var currentLlmProvider = OpenAIClient.instance
3237

3338
var prompts = DefaultPrompts.toPromptsMap()
3439
var currentPrompt = prompts["basic"]!!
3540

3641
var appExclusions: Set<String> = setOf()
3742

38-
companion object {
39-
const val SERVICE_NAME = "com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings"
40-
val instance: AppSettings
41-
get() = ApplicationManager.getApplication().getService(AppSettings::class.java)
42-
}
43+
// Old single LLM provider configuration - needed for migration
44+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
45+
var openAIHost = OpenAIHost.OpenAI.baseUrl
46+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
47+
var openAIHosts = mutableSetOf(OpenAIHost.OpenAI.baseUrl)
48+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
49+
var openAISocketTimeout = "30"
50+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
51+
var proxyUrl: String? = null
52+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
53+
var openAIModelId = "gpt-3.5-turbo"
54+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
55+
var openAIModelIds = listOf("gpt-3.5-turbo", "gpt-4")
56+
@Deprecated("Old configuration property that is no longer used. Needed only for migrating.")
57+
var openAITemperature = "0.7"
4358

4459
override fun getState() = this
4560

4661
override fun loadState(state: AppSettings) {
4762
XmlSerializerUtil.copyBean(state, this)
63+
64+
// Migration from single LLM provider to multiple LLM providers
65+
OpenAIClient.instance.host = openAIHost
66+
OpenAIClient.instance.hosts = openAIHosts
67+
openAISocketTimeout.toIntOrNull()?.let { OpenAIClient.instance.timeout = it }
68+
proxyUrl?.let { OpenAIClient.instance.proxyUrl = it }
69+
OpenAIClient.instance.modelId = openAIModelId
70+
OpenAIClient.instance.modelIds = openAIModelIds
71+
OpenAIClient.instance.temperature = openAITemperature
72+
AICommitsUtils.retrieveToken( "OpenAIToken")?.let { OpenAIClient.instance.token = it }
4873
}
4974

5075
fun recordHit() {
@@ -58,6 +83,12 @@ class AppSettings : PersistentStateComponent<AppSettings> {
5883
return AICommitsUtils.matchesGlobs(path, appExclusions)
5984
}
6085

86+
companion object {
87+
const val SERVICE_NAME = "com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings"
88+
val instance: AppSettings
89+
get() = ApplicationManager.getApplication().getService(AppSettings::class.java)
90+
}
91+
6192
class LocaleConverter : Converter<Locale>() {
6293
override fun toString(value: Locale): String? {
6394
return value.toLanguageTag()

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettingsConfigurable.kt

Lines changed: 75 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ class AppSettingsConfigurable : BoundConfigurable(message("settings.general.grou
3535

3636
init {
3737
hostComboBox.isEditable = true
38-
hostComboBox.model = DefaultComboBoxModel(Vector(AppSettings.instance.openAIHosts.naturalSorted()))
39-
modelComboBox.model = DefaultComboBoxModel(Vector(AppSettings.instance.openAIModelIds.naturalSorted()))
38+
hostComboBox.model = DefaultComboBoxModel(Vector(AppSettings.instance.currentLlmProvider.hosts.naturalSorted()))
39+
modelComboBox.model = DefaultComboBoxModel(Vector(AppSettings.instance.currentLlmProvider.modelIds.naturalSorted()))
4040
modelComboBox.renderer = AppSettingsListCellRenderer()
4141
}
4242

@@ -45,150 +45,150 @@ class AppSettingsConfigurable : BoundConfigurable(message("settings.general.grou
4545
group(JBLabel("OpenAI")) {
4646
row {
4747
label(message("settings.openAIHost"))
48-
.widthGroup("label")
48+
.widthGroup("label")
4949

5050
cell(hostComboBox)
51-
.bindItem(AppSettings.instance::openAIHost.toNullableProperty())
52-
.widthGroup("input")
51+
.bindItem(AppSettings.instance.currentLlmProvider::host.toNullableProperty())
52+
.widthGroup("input")
5353
}
5454
row {
5555
label(message("settings.openAIProxy")).widthGroup("label")
5656
cell(proxyTextField)
57-
.bindText(AppSettings.instance::proxyUrl.toNonNullableProperty(""))
58-
.applyToComponent { minimumWidth = 400 }
59-
.resizableColumn()
60-
.widthGroup("input")
57+
.bindText(AppSettings.instance.currentLlmProvider::proxyUrl.toNonNullableProperty(""))
58+
.applyToComponent { minimumWidth = 400 }
59+
.resizableColumn()
60+
.widthGroup("input")
6161
}
6262
row {
6363
comment(message("settings.openAIProxyComment"))
6464
}
6565
row {
6666
label(message("settings.openAISocketTimeout")).widthGroup("label")
6767
cell(socketTimeoutTextField)
68-
.bindIntText(AppSettings.instance::openAISocketTimeout)
68+
.bindIntText(AppSettings.instance.currentLlmProvider::timeout)
6969
.applyToComponent { minimumWidth = 400 }
7070
.resizableColumn()
7171
.widthGroup("input")
7272
.validationOnInput { isInt(it.text) }
7373
}
7474
row {
7575
label(message("settings.openAIToken"))
76-
.widthGroup("label")
76+
.widthGroup("label")
7777
cell(tokenPasswordField)
78-
.bindText(
79-
{ AppSettings.instance.getOpenAIToken().orEmpty() },
80-
{ AppSettings.instance.saveOpenAIToken(it) }
81-
)
82-
.emptyText(message("settings.openAITokenExample"))
83-
.align(Align.FILL)
84-
.resizableColumn()
85-
.focused()
78+
.bindText(
79+
{ AppSettings.instance.currentLlmProvider.token.orEmpty() },
80+
{ AppSettings.instance.currentLlmProvider.token = it }
81+
)
82+
.emptyText(message("settings.openAITokenExample"))
83+
.align(Align.FILL)
84+
.resizableColumn()
85+
.focused()
8686
button(message("settings.verifyToken")) { verifyToken() }
87-
.align(AlignX.RIGHT)
88-
.widthGroup("button")
87+
.align(AlignX.RIGHT)
88+
.widthGroup("button")
8989
}
9090
row {
9191
comment(message("settings.openAITokenComment"))
92-
.align(AlignX.LEFT)
92+
.align(AlignX.LEFT)
9393
cell(verifyLabel)
94-
.align(AlignX.RIGHT)
94+
.align(AlignX.RIGHT)
9595
}
9696
row {
9797
label(message("settings.openAIModel")).widthGroup("label")
9898

9999
cell(modelComboBox)
100-
.bindItem({ AppSettings.instance.openAIModelId }, {
101-
if (it != null) {
102-
AppSettings.instance.openAIModelId = it
103-
}
104-
})
105-
.resizableColumn()
106-
.align(Align.FILL)
100+
.bindItem({ AppSettings.instance.currentLlmProvider.modelId }, {
101+
if (it != null) {
102+
AppSettings.instance.currentLlmProvider.modelId = it
103+
}
104+
})
105+
.resizableColumn()
106+
.align(Align.FILL)
107107
button(message("settings.refreshModels")) {
108108
runBackgroundableTask(message("settings.loadingModels")) {
109109
runBlocking(Dispatchers.IO) {
110110
OpenAIService.instance.refreshOpenAIModelIds()
111-
modelComboBox.model = DefaultComboBoxModel(Vector(AppSettings.instance.openAIModelIds.naturalSorted()))
112-
modelComboBox.item = AppSettings.instance.openAIModelId
111+
modelComboBox.model = DefaultComboBoxModel(Vector(AppSettings.instance.currentLlmProvider.modelIds.naturalSorted()))
112+
modelComboBox.item = AppSettings.instance.currentLlmProvider.modelId
113113
}
114114
}
115115
}
116-
.align(AlignX.RIGHT)
117-
.widthGroup("button")
116+
.align(AlignX.RIGHT)
117+
.widthGroup("button")
118118
}
119119

120120
row {
121121
label(message("settings.openAITemperature"))
122-
.widthGroup("label")
122+
.widthGroup("label")
123123

124124
textField()
125-
.bindText(AppSettings.instance::openAITemperature)
126-
.applyToComponent { minimumWidth = 400 }
127-
.resizableColumn()
128-
.widthGroup("input")
129-
.validationOnInput { temperatureValid(it.text) }
125+
.bindText(AppSettings.instance.currentLlmProvider::temperature)
126+
.applyToComponent { minimumWidth = 400 }
127+
.resizableColumn()
128+
.widthGroup("input")
129+
.validationOnInput { temperatureValid(it.text) }
130130

131131
contextHelp(message("settings.openAITemperatureComment"))
132132
}
133133

134134
row {
135135
cell(verifyLabel)
136-
.align(AlignX.RIGHT)
136+
.align(AlignX.RIGHT)
137137
}
138138
}
139139

140140
group(JBLabel("Prompt")) {
141141
row {
142142
label(message("settings.locale")).widthGroup("labelPrompt")
143143
comboBox(Locale.getAvailableLocales()
144-
.distinctBy { it.displayLanguage }
145-
.sortedBy { it.displayLanguage },
146-
AppSettingsListCellRenderer()
144+
.distinctBy { it.displayLanguage }
145+
.sortedBy { it.displayLanguage },
146+
AppSettingsListCellRenderer()
147147
)
148-
.bindItem(AppSettings.instance::locale.toNullableProperty())
148+
.bindItem(AppSettings.instance::locale.toNullableProperty())
149149
browserLink(message("settings.more-prompts"), AICommitsBundle.URL_PROMPTS_DISCUSSION.toString())
150-
.align(AlignX.RIGHT)
150+
.align(AlignX.RIGHT)
151151
}
152152
row {
153153
label(message("settings.prompt")).widthGroup("labelPrompt")
154154
promptComboBox = comboBox(AppSettings.instance.prompts.values, AppSettingsListCellRenderer())
155-
.bindItem(AppSettings.instance::currentPrompt.toNullableProperty())
155+
.bindItem(AppSettings.instance::currentPrompt.toNullableProperty())
156156
}
157157
row {
158158
toolbarDecorator = ToolbarDecorator.createDecorator(promptTable.table)
159-
.setAddAction {
160-
promptTable.addPrompt().let {
161-
promptComboBox.component.addItem(it)
162-
}
163-
}
164-
.setEditAction {
165-
promptTable.editPrompt()?.let {
166-
val editingSelected = promptComboBox.component.selectedItem == it.first
167-
promptComboBox.component.removeItem(it.first)
168-
promptComboBox.component.addItem(it.second)
169-
170-
if (editingSelected) {
171-
promptComboBox.component.selectedItem = it.second
172-
}
173-
}
174-
}
175-
.setEditActionUpdater {
176-
updateActionAvailability(CommonActionsPanel.Buttons.EDIT)
177-
true
159+
.setAddAction {
160+
promptTable.addPrompt().let {
161+
promptComboBox.component.addItem(it)
178162
}
179-
.setRemoveAction {
180-
promptTable.removePrompt()?.let {
181-
promptComboBox.component.removeItem(it)
163+
}
164+
.setEditAction {
165+
promptTable.editPrompt()?.let {
166+
val editingSelected = promptComboBox.component.selectedItem == it.first
167+
promptComboBox.component.removeItem(it.first)
168+
promptComboBox.component.addItem(it.second)
169+
170+
if (editingSelected) {
171+
promptComboBox.component.selectedItem = it.second
182172
}
183173
}
184-
.setRemoveActionUpdater {
185-
updateActionAvailability(CommonActionsPanel.Buttons.REMOVE)
186-
true
174+
}
175+
.setEditActionUpdater {
176+
updateActionAvailability(CommonActionsPanel.Buttons.EDIT)
177+
true
178+
}
179+
.setRemoveAction {
180+
promptTable.removePrompt()?.let {
181+
promptComboBox.component.removeItem(it)
187182
}
188-
.disableUpDownActions()
183+
}
184+
.setRemoveActionUpdater {
185+
updateActionAvailability(CommonActionsPanel.Buttons.REMOVE)
186+
true
187+
}
188+
.disableUpDownActions()
189189

190190
cell(toolbarDecorator.createPanel())
191-
.align(Align.FILL)
191+
.align(Align.FILL)
192192
}.resizableRow()
193193
}.resizableRow()
194194

@@ -208,7 +208,7 @@ class AppSettingsConfigurable : BoundConfigurable(message("settings.general.grou
208208
}
209209

210210
override fun apply() {
211-
AppSettings.instance.openAIHosts.add(hostComboBox.item)
211+
AppSettings.instance.currentLlmProvider.hosts.add(hostComboBox.item)
212212
promptTable.apply()
213213
super.apply()
214214
}

0 commit comments

Comments
 (0)