Skip to content

Commit

Permalink
feat: Table for prompts.
Browse files Browse the repository at this point in the history
  • Loading branch information
Blarc committed Apr 8, 2023
1 parent 456c119 commit 67e8f1f
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 74 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]
### Added
- Table for setting prompts.
- Different prompts to choose from.
- Bug report link to settings.
- Add generate commit action progress indicator.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.github.blarc.ai.commits.intellij.plugin

import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle.message
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.ui.layout.ValidationInfoBuilder
import com.intellij.util.ui.ColumnInfo

fun <T> createColumn(name: String, formatter: (T) -> String) : ColumnInfo<T, String> {
return object : ColumnInfo<T, String>(name) {
override fun valueOf(item: T): String {
return formatter(item)
}
}
}

fun ValidationInfoBuilder.notBlank(value: String): ValidationInfo? =
if (value.isBlank()) error(message("validation.required")) else null

fun ValidationInfoBuilder.unique(value: String, existingValues: Set<String>): ValidationInfo? =
if (existingValues.contains(value)) error(message("validation.unique")) else null

fun ValidationInfoBuilder.isLong(value: String): ValidationInfo? {
if (value.isBlank()){
return null
}

value.toLongOrNull().let {
if (it == null) {
return error(message("validation.number"))
} else {
return null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.blarc.ai.commits.intellij.plugin.settings

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.prompt.Prompt
import com.intellij.credentialStore.CredentialAttributes
import com.intellij.credentialStore.Credentials
import com.intellij.ide.passwordSafe.PasswordSafe
Expand All @@ -15,8 +16,8 @@ import com.intellij.util.xmlb.annotations.OptionTag
import java.util.*

@State(
name = AppSettings.SERVICE_NAME,
storages = [Storage("AICommit.xml")]
name = AppSettings.SERVICE_NAME,
storages = [Storage("AICommit.xml")]
)
class AppSettings : PersistentStateComponent<AppSettings> {

Expand All @@ -28,45 +29,25 @@ class AppSettings : PersistentStateComponent<AppSettings> {
var requestSupport = true
var lastVersion: String? = null

var currentPrompt: String = "basic"
var prompts: MutableMap<String, String> = mutableMapOf(
// Generate UUIDs for game objects in Mine.py and call the function in start_game().
"basic" to "Write an insightful but concise Git commit message in a complete sentence in present tense for the " +
"following diff without prefacing it with anything, the response must be in the language {locale} and must" +
"not be longer than 74 characters. The sent text will be the differences between files, where deleted lines" +
" are prefixed with a single minus sign and added lines are prefixed with a single plus sign.\n" +
"{diff}",
// feat: generate unique UUIDs for game objects on Mine game start
"conventional" to "Write a clean and comprehensive commit message in the conventional commit convention. " +
"I'll send you an output of 'git diff --staged' command, and you convert " +
"it into a commit message. " +
"Do NOT preface the commit with anything. " +
"Do NOT add any descriptions to the commit, only commit message. " +
"Use the present tense. " +
"Lines must not be longer than 74 characters. " +
"Use {locale} language to answer.\n" +
"{diff}",
// ✨ feat(mine): Generate objects UUIDs and start team timers on game start
"emoji" to "Write a clean and comprehensive commit messages in the conventional commit convention. " +
"I'll send you an output of 'git diff --staged' command, and you convert " +
"it into a commit message. " +
"Use GitMoji convention to preface the commit. " +
"Do NOT add any descriptions to the commit, only commit message. " +
"Use the present tense. " +
"Lines must not be longer than 74 characters. " +
"Use {locale} language to answer.\n" +
"{diff}",
)
var prompts: MutableMap<String, Prompt> = initPrompts()
var currentPrompt: Prompt = prompts["basic"]!!

companion object {
const val SERVICE_NAME = "com.github.blarc.ai.commits.intellij.plugin.settings.AppSettings"
val instance: AppSettings
get() = ApplicationManager.getApplication().getService(AppSettings::class.java)
}

fun getPrompt(diff: String) = prompts.getOrDefault(currentPrompt, prompts["basic"]!!)
.replace("{locale}", locale.displayName)
.replace("{diff}", diff)
fun getPrompt(diff: String): String {
val content = currentPrompt.content
content.replace("{locale}", locale.displayName)

return if (content.contains("{diff}")) {
content.replace("{diff}", diff)
} else {
"$content\n$diff"
}
}

fun saveOpenAIToken(token: String) {
try {
Expand All @@ -84,10 +65,10 @@ class AppSettings : PersistentStateComponent<AppSettings> {

private fun getCredentialAttributes(title: String): CredentialAttributes {
return CredentialAttributes(
title,
null,
this.javaClass,
false
title,
null,
this.javaClass,
false
)
}

Expand All @@ -104,6 +85,44 @@ class AppSettings : PersistentStateComponent<AppSettings> {
}
}

private fun initPrompts() = mutableMapOf(
// Generate UUIDs for game objects in Mine.py and call the function in start_game().
"basic" to Prompt("Basic",
"Basic prompt that generates a decent commit message.",
"Write an insightful but concise Git commit message in a complete sentence in present tense for the " +
"following diff without prefacing it with anything, the response must be in the language {locale} and must " +
"NOT be longer than 74 characters. The sent text will be the differences between files, where deleted lines" +
" are prefixed with a single minus sign and added lines are prefixed with a single plus sign.\n" +
"{diff}",
false),
// feat: generate unique UUIDs for game objects on Mine game start
"conventional" to Prompt("Conventional",
"Prompt for commit message in the conventional commit convention.",
"Write a clean and comprehensive commit message in the conventional commit convention. " +
"I'll send you an output of 'git diff --staged' command, and you convert " +
"it into a commit message. " +
"Do NOT preface the commit with anything. " +
"Do NOT add any descriptions to the commit, only commit message. " +
"Use the present tense. " +
"Lines must not be longer than 74 characters. " +
"Use {locale} language to answer.\n" +
"{diff}",
false),
// ✨ feat(mine): Generate objects UUIDs and start team timers on game start
"emoji" to Prompt("Emoji",
"Prompt for commit message in the conventional commit convention with GitMoji convention.",
"Write a clean and comprehensive commit message in the conventional commit convention. " +
"I'll send you an output of 'git diff --staged' command, and you convert " +
"it into a commit message. " +
"Use GitMoji convention to preface the commit. " +
"Do NOT add any descriptions to the commit, only commit message. " +
"Use the present tense. " +
"Lines must not be longer than 74 characters. " +
"Use {locale} language to answer.\n" +
"{diff}",
false)
)

class LocaleConverter : Converter<Locale>() {
override fun toString(value: Locale): String? {
return value.toLanguageTag()
Expand All @@ -113,5 +132,4 @@ class AppSettings : PersistentStateComponent<AppSettings> {
return Locale.forLanguageTag(value)
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,123 @@ import com.aallam.openai.api.exception.OpenAIAPIException
import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle
import com.github.blarc.ai.commits.intellij.plugin.AICommitsBundle.message
import com.github.blarc.ai.commits.intellij.plugin.OpenAIService
import com.github.blarc.ai.commits.intellij.plugin.settings.prompt.Prompt
import com.github.blarc.ai.commits.intellij.plugin.settings.prompt.PromptTable
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.options.BoundConfigurable
import com.intellij.openapi.progress.runBackgroundableTask
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.CommonActionsPanel
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBTextArea
import com.intellij.ui.dsl.builder.*
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
import javax.swing.JComponent
import javax.swing.JPasswordField
import javax.swing.JScrollPane

class AppSettingsConfigurable : BoundConfigurable(message("settings.general.group.title")) {

private val tokenPasswordField = JPasswordField()
private val verifyLabel = JBLabel()
private val promptTextArea = JBTextArea()
init {
promptTextArea.wrapStyleWord = true
promptTextArea.lineWrap = true
promptTextArea.isEditable = false
}
private val promptTable = PromptTable()
private lateinit var toolbarDecorator: ToolbarDecorator
private lateinit var promptComboBox: Cell<ComboBox<Prompt>>
override fun createPanel() = panel {

row {
cell(tokenPasswordField)
.label(message("settings.openAIToken"))
.bindText(
{ AppSettings.instance.getOpenAIToken().orEmpty() },
{ AppSettings.instance.saveOpenAIToken(it)}
)
.align(Align.FILL)
.resizableColumn()
.focused()
.label(message("settings.openAIToken"))
.bindText(
{ AppSettings.instance.getOpenAIToken().orEmpty() },
{ AppSettings.instance.saveOpenAIToken(it) }
)
.align(Align.FILL)
.resizableColumn()
.focused()
button(message("settings.verifyToken")) {
verifyToken()
}.align(AlignX.RIGHT)
}
row {
comment(message("settings.openAITokenComment"))
.align(AlignX.LEFT)
.align(AlignX.LEFT)
cell(verifyLabel)
.align(AlignX.RIGHT)
.align(AlignX.RIGHT)
}
row {
comboBox(Locale.getAvailableLocales().toList().sortedBy { it.displayName }, AppSettingsListCellRenderer())
.label(message("settings.locale"))
.bindItem(AppSettings.instance::locale.toNullableProperty())
.label(message("settings.locale"))
.bindItem(AppSettings.instance::locale.toNullableProperty())
}
row {
comboBox(AppSettings.instance.prompts.keys.toList(), AppSettingsListCellRenderer())
.label(message("settings.prompt"))
.bindItem(AppSettings.instance::currentPrompt.toNullableProperty())
.onChanged { promptTextArea.text = AppSettings.instance.prompts[it.item] }
promptComboBox = comboBox(AppSettings.instance.prompts.values, AppSettingsListCellRenderer())
.label(message("settings.prompt"))
.bindItem(AppSettings.instance::currentPrompt.toNullableProperty())
}
row {
cell(promptTextArea)
.bindText(
{ AppSettings.instance.getPrompt("") },
{ }
)
.align(Align.FILL)
.resizableColumn()
toolbarDecorator = ToolbarDecorator.createDecorator(promptTable.table)
.setAddAction {
promptTable.addPrompt().let {
promptComboBox.component.addItem(it)
}
}
.setEditAction {
promptTable.editPrompt()?.let {
promptComboBox.component.removeItem(it.first)
promptComboBox.component.addItem(it.second)
}
}
.setEditActionUpdater {
updateActionAvailability(CommonActionsPanel.Buttons.EDIT)
true
}
.setRemoveAction {
promptTable.removePrompt()?.let {
promptComboBox.component.removeItem(it)
}
}
.setRemoveActionUpdater {
updateActionAvailability(CommonActionsPanel.Buttons.REMOVE)
true
}
.disableUpDownActions()

cell(toolbarDecorator.createPanel())
.align(Align.FILL)
}.resizableRow()

row {
browserLink(message("settings.report-bug"), AICommitsBundle.URL_BUG_REPORT.toString())
}
}

private fun updateActionAvailability(action: CommonActionsPanel.Buttons) {
val selectedRow = promptTable.table.selectedRow
val selectedPrompt = promptTable.table.items[selectedRow]
toolbarDecorator.actionsPanel.setEnabled(action, selectedPrompt.canBeChanged)
}

override fun isModified(): Boolean {
return super.isModified() || promptTable.isModified()
}

override fun apply() {
promptTable.apply()
super.apply()
}

override fun reset() {
promptTable.reset()
super.reset()
}

@OptIn(DelicateCoroutinesApi::class)
private fun verifyToken() {
runBackgroundableTask(message("settings.verify.running")) {
Expand All @@ -89,12 +136,10 @@ class AppSettingsConfigurable : BoundConfigurable(message("settings.general.grou
OpenAIService.instance.verifyToken(String(tokenPasswordField.password))
verifyLabel.text = message("settings.verify.valid")
verifyLabel.icon = AllIcons.General.InspectionsOK
}
catch (e: OpenAIAPIException) {
} catch (e: OpenAIAPIException) {
verifyLabel.text = message("settings.verify.invalid", e.statusCode)
verifyLabel.icon = AllIcons.General.InspectionsError
}
catch (e: Exception) {
} catch (e: Exception) {
verifyLabel.text = message("settings.verify.invalid", "Unknown")
verifyLabel.icon = AllIcons.General.InspectionsError
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.github.blarc.ai.commits.intellij.plugin.settings

import ai.grazie.utils.capitalize
import com.github.blarc.ai.commits.intellij.plugin.settings.prompt.Prompt
import java.awt.Component
import java.util.*
import javax.swing.DefaultListCellRenderer
Expand All @@ -17,6 +19,9 @@ class AppSettingsListCellRenderer : DefaultListCellRenderer() {
if (value is Locale) {
text = value.displayName
}
if (value is Prompt) {
text = value.name
}
return component
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.github.blarc.ai.commits.intellij.plugin.settings.prompt

data class Prompt(
var name: String = "",
var description: String = "",
var content: String = "",
var canBeChanged: Boolean = true
)
Loading

0 comments on commit 67e8f1f

Please sign in to comment.