diff --git a/gradle.properties b/gradle.properties index 2b8076ee..4f2aa4fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ pluginName=intellij-aicoder pluginRepositoryUrl=https://github.com/SimiaCryptus/intellij-aicoder -pluginVersion=1.9.2 +pluginVersion=1.9.3 jvmArgs=-Xmx8g org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/AutoPlanChatAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/AutoPlanChatAction.kt index b7b61b12..47c51e59 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/AutoPlanChatAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/AutoPlanChatAction.kt @@ -36,7 +36,7 @@ class AutoPlanChatAction : BaseAction() { override fun getActionUpdateThread() = ActionUpdateThread.BGT override fun handle(e: AnActionEvent) { - val dialog = PlanAheadConfigDialog( + val dialog = PlanConfigDialog( e.project, PlanSettings( defaultModel = AppSettingsState.instance.smartModel.chatModel(), parsingModel = AppSettingsState.instance.fastModel.chatModel(), @@ -63,7 +63,7 @@ class AutoPlanChatAction : BaseAction() { } } - private fun initializeChat(e: AnActionEvent, dialog: PlanAheadConfigDialog, progress: ProgressIndicator) { + private fun initializeChat(e: AnActionEvent, dialog: PlanConfigDialog, progress: ProgressIndicator) { progress.text = "Setting up session..." val session = Session.newGlobalID() val root = getProjectRoot(e) ?: throw RuntimeException("Could not determine project root") @@ -81,7 +81,7 @@ class AutoPlanChatAction : BaseAction() { } } - private fun setupChatSession(session: Session, root: File, e: AnActionEvent, dialog: PlanAheadConfigDialog) { + private fun setupChatSession(session: Session, root: File, e: AnActionEvent, dialog: PlanConfigDialog) { DataStorage.sessionPaths[session] = root SessionProxyServer.chats[session] = createChatApp(root, e, dialog) ApplicationServer.appInfoMap[session] = AppInfoData( @@ -94,7 +94,7 @@ class AutoPlanChatAction : BaseAction() { SessionProxyServer.metadataStorage.setSessionName(null, session, "${javaClass.simpleName} @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}") } - private fun createChatApp(root: File, e: AnActionEvent, dialog: PlanAheadConfigDialog) = + private fun createChatApp(root: File, e: AnActionEvent, dialog: PlanConfigDialog) = object : AutoPlanChatApp( planSettings = dialog.settings.copy( env = mapOf(), diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadAction.kt index 81d04aa2..67678f29 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadAction.kt @@ -28,7 +28,7 @@ class PlanAheadAction : BaseAction() { override fun getActionUpdateThread() = ActionUpdateThread.BGT override fun handle(e: AnActionEvent) { - val dialog = PlanAheadConfigDialog( + val dialog = PlanConfigDialog( e.project, PlanSettings( defaultModel = AppSettingsState.instance.smartModel.chatModel(), parsingModel = AppSettingsState.instance.fastModel.chatModel(), diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadConfigDialog.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadConfigDialog.kt deleted file mode 100644 index 57ac200b..00000000 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanAheadConfigDialog.kt +++ /dev/null @@ -1,286 +0,0 @@ -package com.github.simiacryptus.aicoder.actions.plan - -import com.github.simiacryptus.aicoder.config.AppSettingsState -import com.intellij.openapi.fileChooser.FileChooser -import com.intellij.openapi.fileChooser.FileChooserDescriptor -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.ui.components.JBScrollPane -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.RowLayout -import com.intellij.ui.dsl.builder.panel -import com.intellij.ui.table.JBTable -import com.simiacryptus.jopenai.models.ChatModel -import com.simiacryptus.skyenet.apps.plan.PlanSettings -import com.simiacryptus.skyenet.apps.plan.TaskSettings -import com.simiacryptus.skyenet.apps.plan.TaskType -import java.awt.Component -import java.awt.Dimension -import javax.swing.* -import javax.swing.table.DefaultTableCellRenderer -import javax.swing.table.DefaultTableModel - -class PlanAheadConfigDialog( - project: Project?, - val settings: PlanSettings, -) : DialogWrapper(project) { - private val temperatureSlider = JSlider(0, 100, (settings.temperature * 100).toInt()) - private val autoFixCheckbox = JCheckBox("Auto-apply fixes", settings.autoFix) - private val allowBlockingCheckbox = JCheckBox("Allow blocking", settings.allowBlocking) - private val taskTableModel = object : DefaultTableModel(arrayOf("Enabled", "Task Type", "Model"), 0) { - override fun getColumnClass(columnIndex: Int) = when (columnIndex) { - 0 -> java.lang.Boolean::class.java - else -> super.getColumnClass(columnIndex) - } - - override fun isCellEditable(row: Int, column: Int) = column == 0 || column == 2 - } - private val taskTable = JBTable(taskTableModel).apply { putClientProperty("terminateEditOnFocusLost", true) } - - // Add a function to retrieve visible models - private fun getVisibleModels() = - ChatModel.values().map { it.value }.filter { isVisible(it) }.toList() - .sortedBy { "${it.provider.name} - ${it.modelName}" } - - // Custom renderer to display provider name and model name - private fun getModelRenderer() = object : DefaultTableCellRenderer() { - override fun getTableCellRendererComponent( - table: JTable, - value: Any, - isSelected: Boolean, - hasFocus: Boolean, - row: Int, - column: Int - ): Component { - val label = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) as JLabel - if (value is String) { - val model = getVisibleModels().find { it.modelName == value } - label.text = "${model?.provider?.name} - $value" - } - return label - } - } - - companion object { - fun isVisible(it: ChatModel): Boolean { - val hasApiKey = - AppSettingsState.instance.apiKey?.filter { it.value.isNotBlank() }?.keys?.contains(it.provider.name) - return false != hasApiKey - } - } - - private val checkboxStates = AppSettingsState.instance.executables.map { true }.toMutableList() - private val tableModel = object : DefaultTableModel(arrayOf("Enabled", "Command"), 0) { - - init { - AppSettingsState.instance.executables.forEach { command -> - addRow(arrayOf(true, command)) - } - } - - override fun getColumnClass(columnIndex: Int) = when (columnIndex) { - 0 -> java.lang.Boolean::class.java - else -> super.getColumnClass(columnIndex) - } - - override fun isCellEditable(row: Int, column: Int) = column == 0 - - override fun setValueAt(aValue: Any?, row: Int, column: Int) { - super.setValueAt(aValue, row, column) - if (column == 0 && aValue is Boolean) { - checkboxStates[row] = aValue - } else { - throw IllegalArgumentException("Invalid column index: $column") - } - } - - override fun getValueAt(row: Int, column: Int): Any = - try { - if (column == 0) { - checkboxStates[row] - } else super.getValueAt(row, column) - } catch (e: IndexOutOfBoundsException) { - false - } - } - private val commandTable = JBTable(tableModel).apply { putClientProperty("terminateEditOnFocusLost", true) } - private val addCommandButton = JButton("Add Command") - private val editCommandButton = JButton("Edit Command") - - - init { - taskTable.columnModel.getColumn(2).apply { - preferredWidth = 200 - val modelComboBox = JComboBox(getVisibleModels().map { it.modelName }.toTypedArray()) - cellEditor = DefaultCellEditor(modelComboBox) - cellRenderer = getModelRenderer() - } - - init() - title = "Configure Plan Ahead Action" - // Add model combobox and change listener to update the settings based on slider value - - temperatureSlider.addChangeListener { - settings.temperature = temperatureSlider.value / 100.0 - } - // Update parsingModel based on modelComboBox selection - val fileChooserDescriptor = FileChooserDescriptor(true, false, false, false, false, false) - .withTitle("Select Command") - .withDescription("Choose an executable file for the auto-fix command") - addCommandButton.addActionListener { - val chosenFile = FileChooser.chooseFile(fileChooserDescriptor, project, null) - if (chosenFile != null) { - val newCommand = chosenFile.path - val confirmResult = JOptionPane.showConfirmDialog( - null, - "Add command: $newCommand?", - "Confirm Command", - JOptionPane.YES_NO_OPTION - ) - if (confirmResult == JOptionPane.YES_OPTION) { - tableModel.addRow(arrayOf(true, newCommand)) - checkboxStates.add(true) - AppSettingsState.instance.executables.add(newCommand) - } - } - } - editCommandButton.addActionListener { - val selectedRow = commandTable.selectedRow - if (selectedRow != -1) { - val currentCommand = tableModel.getValueAt(selectedRow, 1) as String - val newCommand = JOptionPane.showInputDialog( - null, - "Edit command:", - currentCommand - ) - if (newCommand != null && newCommand.isNotEmpty()) { - val confirmResult = JOptionPane.showConfirmDialog( - null, - "Update command to: $newCommand?", - "Confirm Edit", - JOptionPane.YES_NO_OPTION - ) - if (confirmResult == JOptionPane.YES_OPTION) { - tableModel.setValueAt(newCommand, selectedRow, 1) - AppSettingsState.instance.executables.remove(currentCommand) - AppSettingsState.instance.executables.add(newCommand) - } - } - } else { - JOptionPane.showMessageDialog(null, "Please select a command to edit.") - } - } - commandTable.columnModel.getColumn(0).apply { - preferredWidth = 50 - maxWidth = 100 - } - commandTable.selectionModel.addListSelectionListener { - editCommandButton.isEnabled = commandTable.selectedRow != -1 - } - editCommandButton.isEnabled = false - // Initialize task table - val values = TaskType.values() - values.forEach { taskType -> - val taskSettings = settings.getTaskSettings(taskType) - taskTableModel.addRow( - arrayOf( - taskSettings.enabled, - taskType.name, - taskSettings.model?.modelName ?: AppSettingsState.instance.smartModel, - ) - ) - } - taskTable.columnModel.getColumn(0).preferredWidth = 50 - taskTable.columnModel.getColumn(0).maxWidth = 100 - taskTable.columnModel.getColumn(1).preferredWidth = 200 - taskTable.columnModel.getColumn(2).preferredWidth = 200 - // Call setupCommandTable to initialize commandTable - setupCommandTable() - } - - override fun createCenterPanel(): JComponent = panel { - group("Tasks") { - row("Task Types:") { - cell(JBScrollPane(taskTable).apply { - minimumSize = Dimension(350, 100) - preferredSize = Dimension(350, 200) - }) - .align(Align.FILL) - .resizableColumn() - } - .resizableRow() - .layout(RowLayout.PARENT_GRID) - } - .resizableRow() - group("Settings") { - row { - cell(autoFixCheckbox) - .align(Align.FILL) - } - row { - cell(allowBlockingCheckbox) - .align(Align.FILL) - } - row("Temperature:") { - cell(temperatureSlider) - .align(Align.FILL) - } - } - group("Commands") { - row("Auto-Fix Commands:") { - cell(JBScrollPane(commandTable).apply { - minimumSize = Dimension(350, 100) - preferredSize = Dimension(350, 200) - }) - .align(Align.FILL) - .resizableColumn() - } - .resizableRow() - .layout(RowLayout.PARENT_GRID) - row { - cell(addCommandButton) - cell(editCommandButton) - } - } - .resizableRow() - } - - override fun doOKAction() { - // Update task settings - for (i in 0 until taskTableModel.rowCount) { - val taskType = TaskType.valueOf(taskTableModel.getValueAt(i, 1) as String) - val modelName = taskTableModel.getValueAt(i, 2) as String - val selectedModel = ChatModel.values().toList().find { it.first == modelName }?.second - settings.setTaskSettings(taskType, TaskSettings(taskTableModel.getValueAt(i, 0) as Boolean).apply { - this.model = selectedModel - }) - } - settings.autoFix = autoFixCheckbox.isSelected - settings.allowBlocking = allowBlockingCheckbox.isSelected - settings.commandAutoFixCommands = (0 until tableModel.rowCount) - .filter { tableModel.getValueAt(it, 0) as Boolean } - .map { tableModel.getValueAt(it, 1) as String } - // Update the global tool collection without removing deselected commands - settings.commandAutoFixCommands!!.forEach { command -> - if (!AppSettingsState.instance.executables.contains(command)) { - AppSettingsState.instance.executables.add(command) - } - } - super.doOKAction() - } - - private fun setupCommandTable() { - commandTable.columnModel.getColumn(0).apply { - cellEditor = DefaultCellEditor(JCheckBox()) - preferredWidth = 50 - maxWidth = 100 - } - tableModel.addTableModelListener { e -> - if (e.column == 0) { - val row = e.firstRow - val value = tableModel.getValueAt(row, 0) as Boolean - checkboxStates[row] = value - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanChatAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanChatAction.kt index 27ece3a6..f71b396a 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanChatAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanChatAction.kt @@ -50,7 +50,7 @@ class PlanChatAction : BaseAction() { } private fun initializeAndOpenChat(e: AnActionEvent) { - val dialog = PlanAheadConfigDialog( + val dialog = PlanConfigDialog( e.project, PlanSettings( defaultModel = AppSettingsState.instance.smartModel.chatModel(), parsingModel = AppSettingsState.instance.fastModel.chatModel(), diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanConfigDialog.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanConfigDialog.kt new file mode 100644 index 00000000..6793f385 --- /dev/null +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PlanConfigDialog.kt @@ -0,0 +1,714 @@ +package com.github.simiacryptus.aicoder.actions.plan + +import com.github.simiacryptus.aicoder.config.AppSettingsState +import com.intellij.openapi.fileChooser.FileChooser +import com.intellij.openapi.fileChooser.FileChooserDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.JBSplitter +import com.intellij.ui.components.JBList +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.RowLayout +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.table.JBTable +import com.simiacryptus.jopenai.models.ChatModel +import com.simiacryptus.skyenet.apps.plan.PlanSettings +import com.simiacryptus.skyenet.apps.plan.TaskSettingsBase +import com.simiacryptus.skyenet.apps.plan.TaskType +import java.awt.CardLayout +import java.awt.Component +import java.awt.Dimension +import java.awt.Font +import javax.swing.* +import javax.swing.table.DefaultTableModel + +class PlanConfigDialog( + project: Project?, + val settings: PlanSettings, +) : DialogWrapper(project) { + companion object { + private const val MIN_TEMP = 0 + private const val MAX_TEMP = 100 + private const val DEFAULT_LIST_WIDTH = 150 + private const val DEFAULT_LIST_HEIGHT = 200 + private const val DEFAULT_PANEL_WIDTH = 350 + private const val DEFAULT_PANEL_HEIGHT = 200 + private const val TEMPERATURE_SCALE = 100.0 + private const val TEMPERATURE_LABEL = "%.2f" + + fun isVisible(it: ChatModel): Boolean { + return AppSettingsState.instance.apiKey + ?.filter { it.value.isNotBlank() } + ?.keys + ?.contains(it.provider.name) + ?: false + } + } + + private data class CommandTableEntry( + var enabled: Boolean, + val command: String + ) + + private val temperatureSlider = JSlider(MIN_TEMP, MAX_TEMP, (settings.temperature * TEMPERATURE_SCALE).toInt()).apply { + addChangeListener { + settings.temperature = value / TEMPERATURE_SCALE + temperatureLabel.text = TEMPERATURE_LABEL.format(settings.temperature) + } + } + private val temperatureLabel = JLabel(TEMPERATURE_LABEL.format(settings.temperature)) + private val autoFixCheckbox = JCheckBox("Auto-apply fixes", settings.autoFix) + private val allowBlockingCheckbox = JCheckBox("Allow blocking", settings.allowBlocking) + private val taskTypeList = JBList(TaskType.values()) + private val configPanelContainer = JPanel(CardLayout()) + private val taskConfigs = mutableMapOf() + private val singleTaskModeCheckbox = JCheckBox("Single Task Mode", false) + private val removeCommandButton = JButton("Remove Command").apply { + isEnabled = false + } + + private inner class TaskTypeListCellRenderer : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + val component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus) + if (component is JLabel && value is TaskType<*, *>) { + // Set tooltip with detailed HTML description + toolTipText = getTaskTooltip(value) + // Access settings directly through the outer class + val isEnabled = settings.getTaskSettings(value).enabled + // Set font style and color based on enabled status + font = when (isEnabled) { + true -> { + font.deriveFont(Font.BOLD).deriveFont(14f) + } + + false -> { + font.deriveFont(Font.ITALIC).deriveFont(12f) + } + } + foreground = if (isEnabled) { + list?.foreground + } else { + list?.foreground?.darker()?.darker() + } + // Set task name and description + text = buildString { + val taskDescription = getTaskDescription(value) + append(value.name) + if (taskDescription.isNotEmpty()) { + append(" - ") + append(taskDescription) + } + } + } + return component + } + + private fun getTaskTooltip(taskType: TaskType<*, *>): String = """ + + +

${taskType.name}

+

${ + when (taskType) { + TaskType.PerformanceAnalysis -> """ + Analyzes code performance and provides optimization recommendations. +

+ """ + + TaskType.WebFetchAndTransform -> """ + Fetches content from web URLs and transforms it into desired formats. + + """ + + TaskType.GitHubSearch -> """ + Performs comprehensive searches across GitHub's content. + + """ + + TaskType.GoogleSearch -> """ + Executes Google web searches with customizable parameters. + + """ + + TaskType.Search -> """ + Performs pattern-based searches across project files with context. + + """ + + TaskType.EmbeddingSearch -> """ + Performs semantic search using AI embeddings across indexed content. + + """ + + TaskType.KnowledgeIndexing -> """ + Indexes documents and code for semantic search capabilities. + + """ + + TaskType.WebSearchAndIndex -> """ + Performs web searches and indexes results for future reference. + + """ + + TaskType.ForeachTask -> """ + Executes a set of subtasks for each item in a given list. + + """ + + TaskType.CommandSession -> """ + Manages interactive command-line sessions with state persistence. + + """ + + TaskType.SeleniumSession -> """ + Automates browser interactions using Selenium WebDriver. + + """ + + TaskType.RunShellCommand -> """ + Executes shell commands in a controlled environment. + + """ + + TaskType.TaskPlanning -> """ + Orchestrates complex development tasks by breaking them down into manageable subtasks. + + """ + + TaskType.Inquiry -> """ + Provides detailed answers and insights about code implementation by analyzing specified files. + + """ + + TaskType.FileModification -> """ + Creates or modifies source files with AI assistance while maintaining code quality. + + """ + + TaskType.Documentation -> """ + Generates comprehensive documentation for code files and APIs. + + """ + + TaskType.CodeReview -> """ + Performs automated code reviews focusing on quality and best practices. + + """ + + TaskType.TestGeneration -> """ + Creates comprehensive test suites for code reliability and correctness. + + """ + + TaskType.Optimization -> """ + Analyzes and optimizes code performance while maintaining readability. + + """ + + TaskType.SecurityAudit -> """ + Performs security analysis to identify and fix vulnerabilities. + + """ + + TaskType.RefactorTask -> """ + Analyzes and improves code structure while maintaining functionality. + + """ + + else -> "No detailed description available" + } + }

+ + + """ + + + private fun getTaskDescription(taskType: TaskType<*, *>): String = when (taskType) { + TaskType.Search -> "Search project files using patterns with contextual results" + TaskType.EmbeddingSearch -> "Perform semantic search using AI embeddings" + TaskType.KnowledgeIndexing -> "Index content for semantic search capabilities" + TaskType.WebSearchAndIndex -> "Search web content and create searchable indexes" + TaskType.ForeachTask -> "Execute subtasks for each item in a list" + TaskType.CommandSession -> "Manage interactive command-line sessions" + TaskType.SeleniumSession -> "Automate browser interactions with Selenium" + TaskType.RunShellCommand -> "Execute shell commands safely" + TaskType.TaskPlanning -> "Break down and coordinate complex development tasks with dependency management" + TaskType.Inquiry -> "Analyze code and provide detailed explanations of implementation patterns" + TaskType.FileModification -> "Create new files or modify existing code with AI-powered assistance" + TaskType.Documentation -> "Generate comprehensive documentation for code, APIs, and architecture" + TaskType.CodeReview -> "Perform thorough code review with quality and best practice analysis" + TaskType.TestGeneration -> "Generate comprehensive test suites with full coverage analysis" + TaskType.Optimization -> "Analyze performance bottlenecks and implement optimizations" + TaskType.SecurityAudit -> "Identify security vulnerabilities and provide mitigation strategies" + TaskType.RefactorTask -> "Improve code structure, readability and maintainability" + TaskType.PerformanceAnalysis -> "Analyze and optimize code performance with detailed metrics" + TaskType.WebFetchAndTransform -> "Fetch and transform web content into desired formats" + TaskType.GitHubSearch -> "Search GitHub repositories, code, issues and users" + TaskType.GoogleSearch -> "Perform Google web searches with custom filtering" + else -> "No description available" + } + } + + private fun getVisibleModels() = + ChatModel.values().map { it.value }.filter { isVisible(it) }.toList() + .sortedBy { "${it.provider.name} - ${it.modelName}" } + + private inner class TaskTypeConfigPanel(val taskType: TaskType<*, *>) : JPanel() { + val enabledCheckbox = JCheckBox("Enabled", settings.getTaskSettings(taskType).enabled) + private val modelComboBox = ComboBox(getVisibleModels().map { it.modelName }.toTypedArray()).apply { + maximumSize = Dimension(DEFAULT_PANEL_WIDTH - 50, 30) + preferredSize = Dimension(DEFAULT_PANEL_WIDTH - 50, 30) + } + + init { + removeCommandButton.addActionListener { + val selectedRow = commandTable.selectedRow + if (selectedRow != -1) { + val command = tableModel.getValueAt(selectedRow, 1) as String + val confirmResult = JOptionPane.showConfirmDialog( + null, + "Remove command: $command?", + "Confirm Remove", + JOptionPane.YES_NO_OPTION + ) + if (confirmResult == JOptionPane.YES_OPTION) { + tableModel.removeRow(selectedRow) + checkboxStates.removeAt(selectedRow) + AppSettingsState.instance.executables.remove(command) + } + } + } + layout = BoxLayout(this, BoxLayout.Y_AXIS) + alignmentX = Component.LEFT_ALIGNMENT + add(enabledCheckbox.apply { alignmentX = Component.LEFT_ALIGNMENT }) + add(Box.createVerticalStrut(5)) + add(JLabel("Model:").apply { alignmentX = Component.LEFT_ALIGNMENT }) + add(Box.createVerticalStrut(2)) + add(modelComboBox.apply { alignmentX = Component.LEFT_ALIGNMENT }) + add(Box.createVerticalGlue()) + val currentModel = settings.getTaskSettings(taskType).model + modelComboBox.selectedItem = currentModel?.modelName + enabledCheckbox.addItemListener { + // Update the settings immediately when checkbox state changes + settings.setTaskSettings(taskType, TaskSettingsBase(taskType.name, enabledCheckbox.isSelected).apply { + this.model = getVisibleModels().find { it.modelName == modelComboBox.selectedItem } + }) + taskTypeList.repaint() + } + modelComboBox.addActionListener { + settings.setTaskSettings(taskType, TaskSettingsBase(taskType.name, enabledCheckbox.isSelected).apply { + this.model = getVisibleModels().find { it.modelName == modelComboBox.selectedItem } + }) + } + } + + fun saveSettings() { + // Only need to save model selection since enabled state is saved immediately + settings.setTaskSettings(taskType, TaskSettingsBase(taskType.name, enabledCheckbox.isSelected).apply { + this.model = getVisibleModels().find { it.modelName == modelComboBox.selectedItem } + }) + } + } + + private val checkboxStates = AppSettingsState.instance.executables.map { true }.toMutableList() + private val tableModel = object : DefaultTableModel(arrayOf("Enabled", "Command"), 0) { + private val entries = mutableListOf() + + init { + AppSettingsState.instance.executables.forEach { command -> + entries.add(CommandTableEntry(true, command)) + addRow(arrayOf(true, command)) + } + } + + override fun getColumnClass(columnIndex: Int) = when (columnIndex) { + 0 -> java.lang.Boolean::class.java + else -> super.getColumnClass(columnIndex) + } + + override fun isCellEditable(row: Int, column: Int) = column == 0 + + override fun setValueAt(aValue: Any?, row: Int, column: Int) { + if (column == 0 && aValue is Boolean) { + entries[row].enabled = aValue + super.setValueAt(aValue, row, column) + fireTableCellUpdated(row, column) + } else { + throw IllegalArgumentException("Invalid column index: $column") + } + } + + override fun getValueAt(row: Int, column: Int): Any = + try { + if (column == 0) { + entries[row].enabled + } else super.getValueAt(row, column) + } catch (e: IndexOutOfBoundsException) { + false + } + + } + private val commandTable = JBTable(tableModel).apply { + putClientProperty("terminateEditOnFocusLost", true) + this.columnModel.getColumn(0).apply { + preferredWidth = 50 + maxWidth = 100 + } + } + private val addCommandButton = JButton("Add Command") + private val editCommandButton = JButton("Edit Command") + + init { + // Set the custom cell renderer for the task type list + taskTypeList.cellRenderer = TaskTypeListCellRenderer() + + taskTypeList.addListSelectionListener { e -> + if (!e.valueIsAdjusting) { + val selectedType = (taskTypeList.selectedValue as TaskType<*, *>).name + (configPanelContainer.layout as CardLayout).show(configPanelContainer, selectedType) + if (singleTaskModeCheckbox.isSelected) { + // Disable all tasks except the selected one + TaskType.values().forEach { taskType -> + val isSelected = taskType.name == selectedType + taskConfigs[taskType.name]?.enabledCheckbox?.isSelected = isSelected + } + } + } + } + // Initialize config panels for each task type + TaskType.values().forEach { taskType -> + val configPanel = TaskTypeConfigPanel(taskType) + taskConfigs[taskType.name] = configPanel + configPanelContainer.add(configPanel, taskType.name) + } + + init() + title = "Configure Plan Ahead Action" + // Add listener for single task mode checkbox + singleTaskModeCheckbox.addItemListener { e -> + if (e.stateChange == java.awt.event.ItemEvent.SELECTED) { + // When enabled, only keep the currently selected task enabled + val selectedType = (taskTypeList.selectedValue as TaskType<*, *>).name + TaskType.values().forEach { taskType -> + val isSelected = taskType.name == selectedType + taskConfigs[taskType.name]?.enabledCheckbox?.isSelected = isSelected + } + } + } + // Add model combobox and change listener to update the settings based on slider value + + temperatureSlider.addChangeListener { + settings.temperature = temperatureSlider.value / 100.0 + } + // Update parsingModel based on modelComboBox selection + val fileChooserDescriptor = FileChooserDescriptor(true, false, false, false, false, false) + .withTitle("Select Command") + .withDescription("Choose an executable file for the auto-fix command") + addCommandButton.addActionListener { + val newCommand = if ((it as? java.awt.event.ActionEvent)?.modifiers?.and(java.awt.event.InputEvent.CTRL_DOWN_MASK) != 0) { + // Control was held - prompt for direct path input + JOptionPane.showInputDialog( + null, + "Enter command path:", + "Add Command", + JOptionPane.PLAIN_MESSAGE + ) + } else { + // Normal click - show file chooser + FileChooser.chooseFile(fileChooserDescriptor, project, null)?.path + } + if (newCommand != null) { + val confirmResult = JOptionPane.showConfirmDialog( + null, + "Add command: $newCommand?", + "Confirm Command", + JOptionPane.YES_NO_OPTION + ) + if (confirmResult == JOptionPane.YES_OPTION) { + tableModel.addRow(arrayOf(true, newCommand)) + checkboxStates.add(true) + AppSettingsState.instance.executables.add(newCommand) + } + } + } + editCommandButton.addActionListener { + val selectedRow = commandTable.selectedRow + if (selectedRow != -1) { + val currentCommand = tableModel.getValueAt(selectedRow, 1) as String + val newCommand = JOptionPane.showInputDialog( + null, + "Edit command:", + currentCommand + ) + if (newCommand != null && newCommand.isNotEmpty()) { + val confirmResult = JOptionPane.showConfirmDialog( + null, + "Update command to: $newCommand?", + "Confirm Edit", + JOptionPane.YES_NO_OPTION + ) + if (confirmResult == JOptionPane.YES_OPTION) { + tableModel.setValueAt(newCommand, selectedRow, 1) + AppSettingsState.instance.executables.remove(currentCommand) + AppSettingsState.instance.executables.add(newCommand) + } + } + } else { + JOptionPane.showMessageDialog(null, "Please select a command to edit.") + } + } + commandTable.columnModel.getColumn(0).apply { + preferredWidth = 50 + maxWidth = 100 + } + commandTable.selectionModel.addListSelectionListener { + editCommandButton.isEnabled = commandTable.selectedRow != -1 + removeCommandButton.isEnabled = commandTable.selectedRow != -1 + } + editCommandButton.isEnabled = false + // Call setupCommandTable to initialize commandTable + setupCommandTable() + } + + override fun createCenterPanel(): JComponent = panel { + group("Tasks") { + row { + cell(singleTaskModeCheckbox) + .align(Align.FILL) + } + row { + cell( + JBSplitter(false, 0.3f).apply { + firstComponent = JBScrollPane(taskTypeList).apply { + minimumSize = Dimension(DEFAULT_LIST_WIDTH, DEFAULT_LIST_HEIGHT) + preferredSize = Dimension(DEFAULT_LIST_WIDTH + 100, DEFAULT_LIST_HEIGHT) + } + secondComponent = JBScrollPane(configPanelContainer).apply { + minimumSize = Dimension(DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT / 2) + preferredSize = Dimension(DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT) + } + dividerWidth = 3 + isShowDividerControls = true + isShowDividerIcon = true + } + ) + .align(Align.FILL) + .resizableColumn() + } + .resizableRow() + } + .layout(RowLayout.PARENT_GRID) + .resizableRow() + group("Settings") { + row { + cell(autoFixCheckbox) + .align(Align.FILL) + } + row { + cell(allowBlockingCheckbox) + .align(Align.FILL) + } + row("Temperature:") { + cell(temperatureSlider).align(Align.FILL) + cell(temperatureLabel) + } + row("Command Line Tools:") { + cell( + JBSplitter(true, 0.9f).apply { + firstComponent = JBScrollPane(commandTable).apply { + minimumSize = Dimension(350, 100) + preferredSize = Dimension(350, 200) + } + secondComponent = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + add(addCommandButton) + add(Box.createHorizontalStrut(5)) + add(editCommandButton) + add(Box.createHorizontalStrut(5)) + add(removeCommandButton) + } + dividerWidth = 3 + isShowDividerControls = true + isShowDividerIcon = true + splitterProportionKey = "planAhead.commands.splitter" + }) + .align(Align.FILL) + .resizableColumn() + } + } + } + + override fun doOKAction() { + // Save settings from all task type config panels + taskConfigs.values.forEach { configPanel -> + configPanel.saveSettings() + } + settings.autoFix = autoFixCheckbox.isSelected + settings.allowBlocking = allowBlockingCheckbox.isSelected + settings.commandAutoFixCommands = (0 until tableModel.rowCount) + .filter { tableModel.getValueAt(it, 0) as Boolean } + .map { tableModel.getValueAt(it, 1) as String } + // Update the global tool collection + settings.commandAutoFixCommands?.forEach { command -> + if (!AppSettingsState.instance.executables.contains(command)) { + AppSettingsState.instance.executables.add(command) + } + } + super.doOKAction() + } + + private fun setupCommandTable() { + commandTable.columnModel.getColumn(0).apply { + cellEditor = DefaultCellEditor(JCheckBox()) + preferredWidth = 50 + maxWidth = 100 + } + tableModel.addTableModelListener { e -> + if (e.column == 0) { + val row = e.firstRow + val value = tableModel.getValueAt(row, 0) as Boolean + checkboxStates[row] = value + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PrePlanAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PrePlanAction.kt index 65c98bf1..4f49a212 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PrePlanAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/plan/PrePlanAction.kt @@ -70,7 +70,7 @@ class PrePlanAction : BaseAction() { googleApiKey = AppSettingsState.instance.googleApiKey, googleSearchEngineId = AppSettingsState.instance.googleSearchEngineId, ) - planSettings = PlanAheadConfigDialog(e.project, planSettings).let { + planSettings = PlanConfigDialog(e.project, planSettings).let { if (!it.showAndGet()) throw RuntimeException("User cancelled") it.settings } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt index 1ce4c27e..857c0c49 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsComponent.kt @@ -1,7 +1,7 @@ package com.github.simiacryptus.aicoder.config -import com.github.simiacryptus.aicoder.actions.plan.PlanAheadConfigDialog.Companion.isVisible +import com.github.simiacryptus.aicoder.actions.plan.PlanConfigDialog.Companion.isVisible import com.github.simiacryptus.aicoder.util.IdeaChatClient import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.fileChooser.FileChooser @@ -21,7 +21,6 @@ import com.simiacryptus.jopenai.models.APIProvider import com.simiacryptus.jopenai.models.ChatModel import com.simiacryptus.jopenai.models.ImageModels import com.simiacryptus.skyenet.core.platform.ApplicationServices -import com.simiacryptus.skyenet.core.platform.AwsPlatform import java.awt.BorderLayout import java.awt.Dimension import java.awt.event.ActionEvent