diff --git a/README.md b/README.md
index 9dd8100..cff40e4 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,7 @@ plugin and configure a LLM API client in plugin's settings: Settings
- Open AI
- Ollama
- Qianfan (Ernie)
+- GigaChat
The plugin is implemented in a generic way and uses [langchain4j](https://github.com/langchain4j/langchain4j) for creating LLM API clients. If you would like to use some other LLM model that is supported by langchain4j, please make a feature request in GitHub issues.
diff --git a/build.gradle.kts b/build.gradle.kts
index 0c70eed..28d8776 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -60,6 +60,7 @@ dependencies {
implementation("dev.langchain4j:langchain4j-github-models")
implementation("dev.langchain4j:langchain4j-mistral-ai")
implementation("dev.langchain4j:langchain4j-bedrock")
+ implementation("chat.giga:langchain4j-gigachat:0.1.14")
implementation(platform("dev.langchain4j:langchain4j-community-bom:1.7.1-beta14"))
// The Baidu Qianfan Large Model Platform, including the ERNIE series, can be accessed at https://docs.langchain4j.dev/integrations/language-models/qianfan/.
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/Icons.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/Icons.kt
index 12e2313..e675e6e 100644
--- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/Icons.kt
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/Icons.kt
@@ -29,6 +29,7 @@ object Icons {
val GITHUB = AICommitsIcon("/icons/github15bright.svg", "/icons/github15dark.svg")
val MISTRAL = AICommitsIcon("/icons/mistral.svg", null)
val AMAZON_BEDROCK = AICommitsIcon("/icons/amazonBedrock15.svg", "/icons/amazonBedrock15.svg")
+ val GIGACHAT = AICommitsIcon("/icons/gigachat15.svg", "/icons/gigachat15.svg")
object Process {
val STOP = AICommitsIcon("/icons/stop.svg", "/icons/stop_dark.svg")
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings2.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings2.kt
index e01ea96..44531db 100644
--- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings2.kt
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings2.kt
@@ -10,6 +10,7 @@ import com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic.An
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.azureOpenAi.AzureOpenAiClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.geminiGoogle.GeminiGoogleClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.geminiVertex.GeminiClientConfiguration
+import com.github.blarc.ai.commits.intellij.plugin.settings.clients.gigachat.GigachatClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.githubModels.GitHubModelsClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.huggingface.HuggingFaceClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.mistral.MistralAIClientConfiguration
@@ -67,7 +68,8 @@ class AppSettings2 : PersistentStateComponent {
HuggingFaceClientConfiguration::class,
GitHubModelsClientConfiguration::class,
MistralAIClientConfiguration::class,
- AmazonBedrockClientConfiguration::class
+ AmazonBedrockClientConfiguration::class,
+ GigachatClientConfiguration::class
],
style = XCollection.Style.v2
)
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientService.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientService.kt
index 510e30e..a164268 100644
--- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientService.kt
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientService.kt
@@ -8,7 +8,6 @@ import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
import com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings2
import com.github.blarc.ai.commits.intellij.plugin.settings.ProjectSettings
-import com.github.blarc.ai.commits.intellij.plugin.settings.clients.mistral.MistralAIClientSharedState
import com.github.blarc.ai.commits.intellij.plugin.wrap
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.EDT
@@ -54,7 +53,7 @@ abstract class LLMClientService(private val cs: Coro
makeRequestWithTryCatch(function = {
val availableModels = getAvailableModels(client);
- MistralAIClientSharedState.getInstance().modelIds.addAll(availableModels)
+ client.getSharedState().modelIds.addAll(availableModels)
withContext(Dispatchers.EDT) {
label.text = message("settings.refreshModels.success")
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientTable.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientTable.kt
index a1e09ee..075a3d7 100644
--- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientTable.kt
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/LLMClientTable.kt
@@ -8,6 +8,7 @@ import com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic.An
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.azureOpenAi.AzureOpenAiClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.geminiGoogle.GeminiGoogleClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.geminiVertex.GeminiClientConfiguration
+import com.github.blarc.ai.commits.intellij.plugin.settings.clients.gigachat.GigachatClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.githubModels.GitHubModelsClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.huggingface.HuggingFaceClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.mistral.MistralAIClientConfiguration
@@ -159,7 +160,8 @@ class LLMClientTable {
HuggingFaceClientConfiguration(),
GitHubModelsClientConfiguration(),
MistralAIClientConfiguration(),
- AmazonBedrockClientConfiguration()
+ AmazonBedrockClientConfiguration(),
+ GigachatClientConfiguration()
).sortedBy { it.getClientName() }
} else {
listOf(newLLMClientConfiguration)
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientConfiguration.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientConfiguration.kt
new file mode 100644
index 0000000..dadb2ba
--- /dev/null
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientConfiguration.kt
@@ -0,0 +1,93 @@
+package com.github.blarc.ai.commits.intellij.plugin.settings.clients.gigachat
+
+import chat.giga.model.Scope
+import com.github.blarc.ai.commits.intellij.plugin.Icons
+import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientConfiguration
+import com.intellij.openapi.project.Project
+import com.intellij.util.xmlb.Converter
+import com.intellij.util.xmlb.annotations.Attribute
+import com.intellij.util.xmlb.annotations.OptionTag
+import com.intellij.util.xmlb.annotations.Transient
+import com.intellij.vcs.commit.AbstractCommitWorkflowHandler
+import kotlinx.coroutines.Job
+import javax.swing.Icon
+
+class GigachatClientConfiguration : LLMClientConfiguration (
+ "Gigachat",
+ "GigaChat-2-Max",
+ "0.7"
+) {
+
+ @Attribute
+ var apiUrl: String = "https://gigachat.devices.sberbank.ru/api/v1"
+ @Attribute
+ var authUrl: String = "https://ngw.devices.sberbank.ru:9443/api/v2"
+ @Attribute
+ var timeout: Int = 60
+ @Attribute
+ var tokenIsStored: Boolean = false
+ @Transient
+ var token: String? = null
+
+ @OptionTag(converter = ScopeConverter::class)
+ var scope: Scope? = null
+
+ companion object {
+ const val CLIENT_NAME = "Gigachat"
+ }
+
+ override fun getClientName(): String {
+ return CLIENT_NAME
+ }
+
+ override fun getClientIcon(): Icon {
+ return Icons.GIGACHAT.getThemeBasedIcon()
+ }
+
+ override fun getSharedState(): GigachatClientSharedState {
+ return GigachatClientSharedState.getInstance()
+ }
+
+ override fun generateCommitMessage(commitWorkflowHandler: AbstractCommitWorkflowHandler<*, *>, project: Project) {
+ return GigachatClientService.getInstance().generateCommitMessage(this, commitWorkflowHandler, project)
+ }
+
+ override fun getGenerateCommitMessageJob(): Job? {
+ return GigachatClientService.getInstance().generateCommitMessageJob
+ }
+
+ fun getAuthUrls(): Set {
+ return getSharedState().authUrls
+ }
+
+ fun addAuthUrl(authUrl: String) {
+ getSharedState().authUrls.add(authUrl)
+ }
+
+ override fun clone(): LLMClientConfiguration {
+ val copy = GigachatClientConfiguration()
+ copy.id = id
+ copy.name = name
+ copy.tokenIsStored = tokenIsStored
+ copy.apiUrl = apiUrl
+ copy.authUrl = authUrl
+ copy.scope = scope
+ copy.timeout = timeout
+ copy.modelId = modelId
+ copy.temperature = temperature
+ return copy
+ }
+
+ override fun panel() = GigachatClientPanel(this)
+
+
+ class ScopeConverter : Converter() {
+ override fun toString(value: Scope): String? {
+ return value.toString()
+ }
+
+ override fun fromString(value: String): Scope? {
+ return Scope.values().find { it.name == value }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientPanel.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientPanel.kt
new file mode 100644
index 0000000..63ad587
--- /dev/null
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientPanel.kt
@@ -0,0 +1,107 @@
+package com.github.blarc.ai.commits.intellij.plugin.settings.clients.gigachat
+
+import chat.giga.model.Scope
+import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle.message
+import com.github.blarc.ai.commits.intellij.plugin.emptyText
+import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientPanel
+import com.intellij.openapi.ui.ComboBox
+import com.intellij.ui.components.JBPasswordField
+import com.intellij.ui.dsl.builder.*
+import org.jetbrains.kotlin.fir.resolve.scopeSessionKey
+
+class GigachatClientPanel(private val clientConfiguration: GigachatClientConfiguration) :
+ LLMClientPanel(clientConfiguration) {
+ private val tokenPasswordField = JBPasswordField()
+
+ private val authUrlComboBox = ComboBox(clientConfiguration.getAuthUrls().toTypedArray())
+
+ private val scopeComboBox = ComboBox(Scope.values())
+
+ override fun create() = panel {
+ nameRow()
+ hostRow(clientConfiguration::apiUrl.toNullableProperty(), "settings.gigachat.host")
+ authUrlRow(clientConfiguration::authUrl.toNullableProperty())
+ timeoutRow(clientConfiguration::timeout)
+ tokenRow()
+ modelIdRow()
+ scopeRow()
+ temperatureRow()
+ verifyRow()
+ }
+
+ open fun Panel.scopeRow(labelKey: String = "settings.llmClient.modelId") {
+ row {
+ label(message("settings.gigachat.scope"))
+ .widthGroup("label")
+ cell(scopeComboBox)
+ .applyToComponent {
+ isEditable = false
+ }
+ .bindItem(
+ { clientConfiguration.scope },
+ {
+ if (it != null) {
+ clientConfiguration.scope = it
+ }
+ })
+ .align(Align.FILL)
+ .onApply { clientConfiguration.scope = scopeComboBox.item }
+ }
+ }
+
+ private fun Panel.authUrlRow(property: MutableProperty, labelKey: String = "settings.gigachat.authUrl") {
+ row {
+ label(message("settings.gigachat.authUrl"))
+ .widthGroup("label")
+ cell(authUrlComboBox)
+ .applyToComponent {
+ isEditable = true
+ }
+ .bindItem(property)
+ .align(Align.FILL)
+ .onApply { clientConfiguration.addAuthUrl(authUrlComboBox.item) }
+ }
+ }
+
+ override fun getRefreshModelsFunction() = fun() {
+ clientConfiguration.apiUrl = hostComboBox.item
+ clientConfiguration.authUrl = authUrlComboBox.item
+ clientConfiguration.modelId = modelComboBox.item
+ clientConfiguration.token = String(tokenPasswordField.password)
+ clientConfiguration.scope = scopeComboBox.item
+ GigachatClientService.getInstance().refreshModels(clientConfiguration, modelComboBox, verifyLabel)
+ }
+
+ private fun Panel.tokenRow() {
+ row {
+ label(message("settings.llmClient.token"))
+ .widthGroup("label")
+ cell(tokenPasswordField)
+ .bindText(getter = { "" }, setter = {
+ GigachatClientService.getInstance().saveToken(clientConfiguration, it)
+ })
+ .emptyText(
+ if (clientConfiguration.tokenIsStored) message("settings.llmClient.token.stored") else message(
+ "settings.gigachat.token.example"
+ )
+ )
+ .resizableColumn()
+ .align(Align.FILL)
+ // maxLineLength was eye-balled, but prevents the dialog getting wider
+ .comment(message("settings.gigachat.token.comment"), 50)
+ }
+ }
+
+ override fun verifyConfiguration() {
+
+ clientConfiguration.apiUrl = hostComboBox.item
+ clientConfiguration.authUrl = authUrlComboBox.item
+ clientConfiguration.timeout = socketTimeoutTextField.text.toInt()
+ clientConfiguration.modelId = modelComboBox.item
+ clientConfiguration.temperature = temperatureTextField.text
+ clientConfiguration.token = String(tokenPasswordField.password)
+ clientConfiguration.scope = scopeComboBox.item
+
+ GigachatClientService.getInstance().verifyConfiguration(clientConfiguration, verifyLabel)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientService.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientService.kt
new file mode 100644
index 0000000..f63548a
--- /dev/null
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientService.kt
@@ -0,0 +1,116 @@
+package com.github.blarc.ai.commits.intellij.plugin.settings.clients.gigachat
+
+import chat.giga.client.GigaChatClientImpl
+import chat.giga.client.auth.AuthClient
+import chat.giga.client.auth.AuthClientBuilder
+import chat.giga.client.auth.AuthClientBuilder.OAuthBuilder
+import chat.giga.langchain4j.GigaChatChatModel
+import chat.giga.langchain4j.GigaChatChatRequestParameters
+import chat.giga.langchain4j.GigaChatStreamingChatModel
+import com.github.blarc.ai.commits.intellij.plugin.AICommitsUtils.getCredentialAttributes
+import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
+import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
+import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientService
+import com.intellij.ide.passwordSafe.PasswordSafe
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import dev.langchain4j.model.chat.ChatModel
+import dev.langchain4j.model.chat.StreamingChatModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@Service(Service.Level.APP)
+class GigachatClientService(private val cs: CoroutineScope) : LLMClientService(cs) {
+
+ companion object {
+ @JvmStatic
+ fun getInstance(): GigachatClientService = service()
+ }
+
+ override suspend fun buildChatModel(client: GigachatClientConfiguration): ChatModel {
+ return GigaChatChatModel.builder()
+ .apiUrl(client.apiUrl)
+ .defaultChatRequestParameters(
+ GigaChatChatRequestParameters.builder()
+ .modelName(client.modelId)
+ .build()
+ )
+ .authClient(
+ AuthClient.builder()
+ .withOAuth(
+ OAuthBuilder.builder()
+ .scope(client.scope)
+ .authKey(client.token)
+ .authApiUrl(client.authUrl)
+ .verifySslCerts(false)
+ .build()
+ )
+ .build()
+ )
+ .logRequests(true)
+ .logResponses(true)
+ .build()
+ }
+
+ override suspend fun buildStreamingChatModel(client: GigachatClientConfiguration): StreamingChatModel? {
+ return GigaChatStreamingChatModel.builder()
+ .apiUrl(client.apiUrl)
+ .defaultChatRequestParameters(
+ GigaChatChatRequestParameters.builder()
+ .modelName(client.modelId)
+ .temperature(client.temperature.toDouble())
+ .build()
+ )
+ .authClient(
+ AuthClient.builder()
+ .withOAuth(
+ OAuthBuilder.builder()
+ .scope(client.scope)
+ .authKey(client.token)
+ .authApiUrl(client.authUrl)
+ .build()
+ )
+ .build()
+ )
+ .logRequests(true)
+ .logResponses(true)
+ .build()
+ }
+
+ fun saveToken(client: GigachatClientConfiguration, token: String) {
+ cs.launch(Dispatchers.Default) {
+ try {
+ PasswordSafe.instance.setPassword(getCredentialAttributes(client.id), token)
+ client.tokenIsStored = true
+ } catch (e: Exception) {
+ sendNotification(Notification.unableToSaveToken(e.message))
+ }
+ }
+ }
+
+ override suspend fun getAvailableModels(client: GigachatClientConfiguration): List {
+ val gigaChatClient1 = GigaChatClientImpl.builder()
+ .apiUrl(client.apiUrl)
+ .authClient(
+ AuthClientBuilder.builder()
+ .withOAuth(
+ OAuthBuilder.builder()
+ .scope(client.scope)
+ .authKey(client.token)
+ .authApiUrl(client.authUrl)
+ .build()
+ )
+ .build()
+ )
+ .build()
+
+ val availableModels = withContext(Dispatchers.IO) {
+ gigaChatClient1.models().data()
+ }
+ return availableModels
+ .filter { it.type().equals("chat") }
+ .map { it.id() }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientSharedState.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientSharedState.kt
new file mode 100644
index 0000000..3102b9f
--- /dev/null
+++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/clients/gigachat/GigachatClientSharedState.kt
@@ -0,0 +1,36 @@
+package com.github.blarc.ai.commits.intellij.plugin.settings.clients.gigachat
+
+import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientSharedState
+import com.intellij.openapi.components.*
+import com.intellij.util.xmlb.annotations.XCollection
+
+@Service(Service.Level.APP)
+@State(name = "GigachatClientSharedState", storages = [Storage("AICommitsGigachat.xml")])
+class GigachatClientSharedState : PersistentStateComponent, LLMClientSharedState {
+
+ companion object {
+ @JvmStatic
+ fun getInstance(): GigachatClientSharedState = service()
+
+ val MODELS: List = listOf("GigaChat", "GigaChat-2", "GigaChat-Pro", "GigaChat-2-Pro", "GigaChat-Max", "GigaChat-2-Max")
+
+ }
+
+ @XCollection(style = XCollection.Style.v2)
+ override val hosts = mutableSetOf("https://gigachat.devices.sberbank.ru/api/v1")
+
+ @XCollection(style = XCollection.Style.v2)
+ val authUrls = mutableSetOf("https://ngw.devices.sberbank.ru:9443/api/v2")
+
+ @XCollection(style = XCollection.Style.v2)
+ override val modelIds = MODELS.toMutableSet()
+
+ override fun getState(): GigachatClientSharedState = this
+
+ override fun loadState(state: GigachatClientSharedState) {
+ // Add all model IDs from enum in case they are not stored in xml
+ modelIds += state.modelIds
+ hosts += state.hosts
+ authUrls += state.authUrls
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/icons/gigachat15.svg b/src/main/resources/icons/gigachat15.svg
new file mode 100644
index 0000000..7072cd1
--- /dev/null
+++ b/src/main/resources/icons/gigachat15.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/src/main/resources/messages/AiCommitsBundle.properties b/src/main/resources/messages/AiCommitsBundle.properties
index aa58b9e..6997d7b 100644
--- a/src/main/resources/messages/AiCommitsBundle.properties
+++ b/src/main/resources/messages/AiCommitsBundle.properties
@@ -104,6 +104,11 @@ settings.openAI.token.example=sk-ABCdefgHIjKlxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
settings.openAi.token.comment=You can get your token here.
settings.openAi.organizationId=Organization ID
+settings.gigachat.token.example=sk-ABCdefgHIjKlxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+settings.gigachat.token.comment=You can get your token here.
+settings.gigachat.authUrl=Auth URL
+settings.gigachat.scope=Scope
+
settings.ollama.numCtx=Num ctx
settings.ollama.numCtx.comment=This controls how many tokens the LLM can use as context to generate the next token.
settings.ollama.numPredict=Num predict