Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ plugin and configure a LLM API client in plugin's settings: <kbd>Settings</kbd>
- 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.

Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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/.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,7 +68,8 @@ class AppSettings2 : PersistentStateComponent<AppSettings2> {
HuggingFaceClientConfiguration::class,
GitHubModelsClientConfiguration::class,
MistralAIClientConfiguration::class,
AmazonBedrockClientConfiguration::class
AmazonBedrockClientConfiguration::class,
GigachatClientConfiguration::class
],
style = XCollection.Style.v2
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -54,7 +53,7 @@ abstract class LLMClientService<C : LLMClientConfiguration>(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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -159,7 +160,8 @@ class LLMClientTable {
HuggingFaceClientConfiguration(),
GitHubModelsClientConfiguration(),
MistralAIClientConfiguration(),
AmazonBedrockClientConfiguration()
AmazonBedrockClientConfiguration(),
GigachatClientConfiguration()
).sortedBy { it.getClientName() }
} else {
listOf(newLLMClientConfiguration)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> {
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<Scope>() {
override fun toString(value: Scope): String? {
return value.toString()
}

override fun fromString(value: String): Scope? {
return Scope.values().find { it.name == value }
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String?>, 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)
}
}
Original file line number Diff line number Diff line change
@@ -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<GigachatClientConfiguration>(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<String> {
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() }
}
}
Loading