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 @@ -40,6 +40,7 @@ plugin and configure a LLM API client in plugin's settings: <kbd>Settings</kbd>
- Amazon Bedrock
- Anthropic
- Azure Open AI
- Claude Code (via CLI)
- Gemini Google AI
- Gemini Vertex AI
- GitHub Models
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ object Icons {
val GEMINI_VERTEX = AICommitsIcon("/icons/geminiVertex.svg", null)
val GEMINI_GOOGLE = AICommitsIcon("/icons/geminiGoogle.svg", null)
val ANTHROPIC = AICommitsIcon("/icons/anthropic15bright.svg", "/icons/anthropic15dark.svg")
val CLAUDE_CODE = AICommitsIcon("/icons/claudeCode15.svg", null)
val AZURE_OPEN_AI = AICommitsIcon("/icons/azureOpenAi.svg", null)
val HUGGING_FACE = AICommitsIcon("/icons/huggingface.svg", null)
val GITHUB = AICommitsIcon("/icons/github15bright.svg", "/icons/github15dark.svg")
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.LLMClientCon
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.amazonBedrock.AmazonBedrockClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic.AnthropicClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.azureOpenAi.AzureOpenAiClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.claudeCode.ClaudeCodeClientConfiguration
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.githubModels.GitHubModelsClientConfiguration
Expand Down Expand Up @@ -68,7 +69,8 @@ class AppSettings2 : PersistentStateComponent<AppSettings2> {
HuggingFaceClientConfiguration::class,
GitHubModelsClientConfiguration::class,
MistralAIClientConfiguration::class,
AmazonBedrockClientConfiguration::class
AmazonBedrockClientConfiguration::class,
ClaudeCodeClientConfiguration::class
],
style = XCollection.Style.v2
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.github.blarc.ai.commits.intellij.plugin.settings.clients
import com.github.blarc.ai.commits.intellij.plugin.Icons
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.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
Expand Down Expand Up @@ -66,7 +67,10 @@ abstract class LLMClientConfiguration(
val projectSettings = project.service<ProjectSettings>()
projectSettings.splitButtonActionSelectedLLMClientId = this.id

generateCommitMessage(commitWorkflowHandler, project)
// Look up the current configuration by ID to ensure we use the latest settings
// (AnAction instances may be cached by IntelliJ and hold stale values)
val currentConfig = AppSettings2.instance.llmClientConfigurations.find { it.id == this.id } ?: this
currentConfig.generateCommitMessage(commitWorkflowHandler, project)
}

open fun setCommitMessage(commitWorkflowHandler: AbstractCommitWorkflowHandler<*, *>, prompt: String, result: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.github.blarc.ai.commits.intellij.plugin.createColumn
import com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings2
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.amazonBedrock.AmazonBedrockClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.anthropic.AnthropicClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.claudeCode.ClaudeCodeClientConfiguration
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
Expand Down Expand Up @@ -120,6 +121,7 @@ class LLMClientTable {
var llmClient = newLlmClientConfiguration ?: llmClientConfigurations[0]

private val cardLayout = JBCardLayout()
private var editPanel: DialogPanel? = null

init {
title = newLlmClientConfiguration?.let { "Edit LLM Client" } ?: "Add LLM Client"
Expand All @@ -130,15 +132,17 @@ class LLMClientTable {
override fun doOKAction() {
if (newLlmClientConfiguration == null) {
(cardLayout.findComponentById(llmClient.getClientName()) as DialogPanel).apply()
} else {
// Apply the edit panel to save typed values from editable comboboxes
editPanel?.apply()
}
// TODO: Figure out how to call apply of the currently active panel
super.doOKAction()
}

override fun createCenterPanel() = if (newLlmClientConfiguration == null) {
createCardSplitter()
} else {
llmClient.panel().create()
llmClient.panel().create().also { editPanel = it }
}.apply {
isResizable = false
// Add 200 so there is space for verification message.
Expand All @@ -159,7 +163,8 @@ class LLMClientTable {
HuggingFaceClientConfiguration(),
GitHubModelsClientConfiguration(),
MistralAIClientConfiguration(),
AmazonBedrockClientConfiguration()
AmazonBedrockClientConfiguration(),
ClaudeCodeClientConfiguration()
).sortedBy { it.getClientName() }
} else {
listOf(newLLMClientConfiguration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.github.blarc.ai.commits.intellij.plugin.settings.clients.claudeCode

import com.github.blarc.ai.commits.intellij.plugin.Icons
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientConfiguration
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientSharedState
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.annotations.Attribute
import com.intellij.vcs.commit.AbstractCommitWorkflowHandler
import kotlinx.coroutines.Job
import javax.swing.Icon

class ClaudeCodeClientConfiguration : LLMClientConfiguration(
"Claude Code",
"", // No default model - uses CLI's configured model
"" // No temperature - CLI doesn't support it
) {

@Attribute
var cliPath: String = "" // Empty means auto-detect

@Attribute
var timeout: Int = 120 // Longer default for CLI execution

companion object {
const val CLIENT_NAME = "Claude Code"
}

override fun getClientName(): String {
return CLIENT_NAME
}

override fun getClientIcon(): Icon {
return Icons.CLAUDE_CODE.getThemeBasedIcon()
}

override fun getSharedState(): LLMClientSharedState {
return ClaudeCodeClientSharedState.getInstance()
}

override fun generateCommitMessage(commitWorkflowHandler: AbstractCommitWorkflowHandler<*, *>, project: Project) {
return ClaudeCodeClientService.getInstance().generateCommitMessageCli(this, commitWorkflowHandler, project)
}

override fun getGenerateCommitMessageJob(): Job? {
return ClaudeCodeClientService.getInstance().generateCommitMessageJob
}

override fun clone(): LLMClientConfiguration {
val copy = ClaudeCodeClientConfiguration()
copy.id = id
copy.name = name
copy.cliPath = cliPath
copy.timeout = timeout
copy.modelId = modelId
copy.temperature = temperature
return copy
}

override fun panel() = ClaudeCodeClientPanel(this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.github.blarc.ai.commits.intellij.plugin.settings.clients.claudeCode

import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle.message
import com.github.blarc.ai.commits.intellij.plugin.settings.clients.LLMClientPanel
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.*

class ClaudeCodeClientPanel private constructor(
private val clientConfiguration: ClaudeCodeClientConfiguration,
val service: ClaudeCodeClientService
) : LLMClientPanel(clientConfiguration) {

private val cliPathTextField = JBTextField()
private val timeoutTextField = JBTextField()

constructor(configuration: ClaudeCodeClientConfiguration) : this(configuration, ClaudeCodeClientService.getInstance())

override fun create() = panel {
nameRow()
cliPathRow()
timeoutRow()
modelRow()
verifyRow()
}

private fun Panel.cliPathRow() {
row {
label(message("settings.claudeCode.cliPath"))
.widthGroup("label")
cell(cliPathTextField)
.bindText(clientConfiguration::cliPath)
.align(Align.FILL)
.resizableColumn()
.comment(message("settings.claudeCode.cliPath.comment"))
button(message("settings.claudeCode.browse")) {
val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor()
val chooser = com.intellij.openapi.fileChooser.FileChooser.chooseFile(descriptor, null, null)
chooser?.let {
cliPathTextField.text = it.path
}
}.widthGroup("button")
button(message("settings.claudeCode.detectPath")) {
val detectedPath = service.findClaudePath("")
if (detectedPath != null) {
cliPathTextField.text = detectedPath
}
}.widthGroup("button")
}
}

private fun Panel.timeoutRow() {
row {
label(message("settings.llmClient.timeout"))
.widthGroup("label")
cell(timeoutTextField)
.bindIntText(clientConfiguration::timeout)
.resizableColumn()
.align(Align.FILL)
.comment(message("settings.claudeCode.timeout.comment"))
}
}

private fun Panel.modelRow() {
row {
label(message("settings.claudeCode.model"))
.widthGroup("label")
cell(modelComboBox)
.applyToComponent {
isEditable = true
}
.bindItem({ clientConfiguration.modelId }, {
if (it != null) {
clientConfiguration.modelId = it
}
})
.onApply {
// Explicitly capture typed value from editable combobox
// bindItem doesn't reliably capture typed values not in the dropdown
modelComboBox.item?.let { clientConfiguration.modelId = it }
}
.align(Align.FILL)
.resizableColumn()
.comment(message("settings.claudeCode.model.comment"))
}
}

override fun verifyConfiguration() {
clientConfiguration.cliPath = cliPathTextField.text
clientConfiguration.timeout = timeoutTextField.text.toIntOrNull() ?: 120
clientConfiguration.modelId = modelComboBox.item ?: ""

service.verifyConfigurationCli(clientConfiguration, verifyLabel)
}
}
Loading