diff --git a/core/src/main/kotlin/com/simiacryptus/cognotik/diff/FuzzyPatchMatcher.kt b/core/src/main/kotlin/com/simiacryptus/cognotik/diff/FuzzyPatchMatcher.kt index 01c2d18a..585c065d 100644 --- a/core/src/main/kotlin/com/simiacryptus/cognotik/diff/FuzzyPatchMatcher.kt +++ b/core/src/main/kotlin/com/simiacryptus/cognotik/diff/FuzzyPatchMatcher.kt @@ -650,7 +650,7 @@ open class FuzzyPatchMatcher( for (sourceLine in sourceLines) { val patchLine = sourceLine.matchingLine ?: continue // Skip if we've already processed this line - if (patchLine.type == DELETE || patchLine.type == ADD) continue + if (patchLine.type == ADD) continue val pairKey = Pair(sourceLine.index, patchLine.index) if (!processedPairs.add(pairKey)) continue diff --git a/core/src/test/kotlin/com/simiacryptus/diff/FuzzyPatchMatcherTest.kt b/core/src/test/kotlin/com/simiacryptus/diff/FuzzyPatchMatcherTest.kt index d772c23a..8b90a8bc 100644 --- a/core/src/test/kotlin/com/simiacryptus/diff/FuzzyPatchMatcherTest.kt +++ b/core/src/test/kotlin/com/simiacryptus/diff/FuzzyPatchMatcherTest.kt @@ -21,6 +21,7 @@ class FuzzyPatchMatcherTest { "/patch_add_2_lines_variant_3.json", "/patch_inner_block.json", "/patch_append_to_empty_file.json", + "/patch_wrap_panel.json" ) } diff --git a/core/src/test/kotlin/com/simiacryptus/diff/PatchTestCase.kt b/core/src/test/kotlin/com/simiacryptus/diff/PatchTestCase.kt index 005b684e..257707f9 100644 --- a/core/src/test/kotlin/com/simiacryptus/diff/PatchTestCase.kt +++ b/core/src/test/kotlin/com/simiacryptus/diff/PatchTestCase.kt @@ -9,7 +9,7 @@ data class PatchTestCase( val originalCode: String, val diff: String, val newCode: String, - val isValid: Boolean, + val isValid: Boolean?, val errors: String? ) { companion object { @@ -18,7 +18,7 @@ data class PatchTestCase( val stream = patcher.javaClass.getResourceAsStream(resourceName) ?: throw IllegalArgumentException("Resource not found: $resourceName") val testCase: PatchTestCase = JsonUtil.fromJson(String(stream.readAllBytes()), PatchTestCase::class.java) - if (!testCase.isValid) return + if (false == testCase.isValid) return val result = patcher.applyPatch(testCase.originalCode, testCase.diff) Assertions.assertEquals(normalize(testCase.newCode), normalize(result)) } diff --git a/core/src/test/kotlin/com/simiacryptus/diff/PythonPatchUtilTest.kt b/core/src/test/kotlin/com/simiacryptus/diff/PythonPatchUtilTest.kt index 6915ba3b..779ca3e9 100644 --- a/core/src/test/kotlin/com/simiacryptus/diff/PythonPatchUtilTest.kt +++ b/core/src/test/kotlin/com/simiacryptus/diff/PythonPatchUtilTest.kt @@ -12,12 +12,16 @@ class PythonPatchUtilTest { fun patchTestCases() = listOf( "/patch_exact_match.json", "/patch_add_line.json", + "/patch_append_line.json", + "/patch_prepend_line.json", "/patch_modify_line.json", +// "/yaml_min_repro.json", "/patch_remove_line.json", // "/patch_add_2_lines_variant_2.json", // "/patch_add_2_lines_variant_3.json", -// "/patch_from_data_1.json", -// "/patch_from_data_2.json" + "/patch_inner_block.json", + "/patch_append_to_empty_file.json", +// "/patch_wrap_panel.json" ) } diff --git a/core/src/test/kotlin/com/simiacryptus/diff/ThermodynamicPatchMatcherTest.kt b/core/src/test/kotlin/com/simiacryptus/diff/ThermodynamicPatchMatcherTest.kt new file mode 100644 index 00000000..a99a3eab --- /dev/null +++ b/core/src/test/kotlin/com/simiacryptus/diff/ThermodynamicPatchMatcherTest.kt @@ -0,0 +1,36 @@ +package com.simiacryptus.diff + +import com.simiacryptus.cognotik.diff.FuzzyPatchMatcher +import com.simiacryptus.cognotik.diff.ThermodynamicPatchMatcher +import com.simiacryptus.diff.PatchTestCase.Companion.test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource + +class ThermodynamicPatchMatcherTest { + + companion object { + @JvmStatic + fun testCases() = listOf( + "/patch_exact_match.json", + "/patch_add_line.json", + "/patch_append_line.json", + "/patch_prepend_line.json", + "/patch_modify_line.json", + "/yaml_min_repro.json", + "/patch_remove_line.json", +// "/patch_add_2_lines_variant_2.json", +// "/patch_add_2_lines_variant_3.json", +// "/patch_inner_block.json", +// "/patch_append_to_empty_file.json", +// "/patch_wrap_panel.json" + ) + } + + @ParameterizedTest + @MethodSource("testCases") + fun testPatchApplication(resourceName: String) { + test(resourceName, ThermodynamicPatchMatcher()) + } + +} + diff --git a/core/src/test/resources/patch_wrap_panel.json b/core/src/test/resources/patch_wrap_panel.json new file mode 100644 index 00000000..cd1b3043 --- /dev/null +++ b/core/src/test/resources/patch_wrap_panel.json @@ -0,0 +1,7 @@ +{ + "filename": "intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt", + "originalCode": "package cognotik.actions.plan\n\nimport com.intellij.openapi.project.Project\nimport com.intellij.openapi.ui.ComboBox\nimport com.intellij.openapi.ui.DialogWrapper\nimport com.intellij.openapi.ui.Messages\nimport com.intellij.ui.components.JBList\nimport com.intellij.ui.components.JBTextField\nimport com.intellij.ui.dsl.builder.Align\nimport com.intellij.ui.dsl.builder.panel\nimport com.simiacryptus.cognotik.chat.model.ChatModel\nimport com.simiacryptus.cognotik.config.AppSettingsState\nimport com.simiacryptus.cognotik.models.AIModel\nimport com.simiacryptus.cognotik.plan.OrchestrationConfig\nimport com.simiacryptus.cognotik.plan.TaskType\nimport com.simiacryptus.cognotik.plan.TaskTypeConfig\nimport com.simiacryptus.cognotik.plan.cognitive.CognitiveModeStrategies\nimport com.simiacryptus.cognotik.plan.newSettings\nimport com.simiacryptus.cognotik.platform.ApplicationServices\nimport com.simiacryptus.cognotik.platform.model.ApiChatModel\nimport com.simiacryptus.cognotik.platform.model.ApiData\nimport com.simiacryptus.cognotik.util.JsonUtil.fromJson\nimport com.simiacryptus.cognotik.util.JsonUtil.toJson\nimport org.slf4j.LoggerFactory\nimport java.awt.Component\nimport java.awt.Dimension\nimport java.awt.Font\nimport java.awt.Toolkit\nimport java.awt.datatransfer.DataFlavor\nimport java.awt.datatransfer.StringSelection\nimport javax.swing.*\n\nclass PlanConfigDialog(\n project: Project?,\n val settings: OrchestrationConfig,\n) : DialogWrapper(project) {\n \n override fun createCenterPanel(): JComponent = panel {\n group {\n row(\"Saved Configs:\") {\n cell(savedConfigsCombo).align(Align.FILL)\n .comment(\"Select a saved configuration to load or save current settings\")\n }\n row {\n button(\"Save\") { saveCurrentConfig() }\n button(\"Load\") {\n val selected = savedConfigsCombo.selectedItem as? String\n if (selected != null) {\n loadConfig(selected)\n } else {\n Messages.showWarningDialog(\n \"Please select a configuration to load\", \"No Configuration Selected\"\n )\n }\n }\n button(\"Delete\") {\n val selected = savedConfigsCombo.selectedItem as? String\n if (selected != null) {\n val confirmResult = JOptionPane.showConfirmDialog(\n null, \"Delete configuration '$selected'?\", \"Confirm Delete\", JOptionPane.YES_NO_OPTION\n )\n if (confirmResult == Messages.YES) {\n val configs = AppSettingsState.instance.savedPlanConfigs ?: mutableMapOf()\n configs.remove(selected)\n AppSettingsState.instance.savedPlanConfigs = configs\n savedConfigsCombo.removeItem(selected)\n }\n } else {\n Messages.showWarningDialog(\n \"Please select a configuration to delete\", \"No Configuration Selected\"\n )\n }\n }\n button(\"Copy\") { exportTaskConfigs() }\n button(\"Paste\") { importTaskConfigs() }\n }\n\n group(\"Planning Settings\") {\n row(\"Cognitive Mode:\") {\n cell(cognitiveModeCombo).align(Align.FILL).comment(\"Select the cognitive strategy for planning\")\n }\n row {\n cell(autoPlanPanel).align(Align.FILL)\n }\n }\n\n row {\n cell(autoFixCheckbox).align(Align.FILL)\n .comment(\"Automatically apply suggested fixes without confirmation\")\n }\n\n row(\"Temperature:\") {\n cell(temperatureSlider).align(Align.FILL)\n .comment(\"Adjust AI response creativity (higher = more creative)\")\n cell(temperatureLabel)\n }\n row(\"Default Model:\") {\n cell(globalModelCombo).align(Align.FILL)\n .comment(\"Default AI model for all tasks\")\n }\n row(\"Parsing Model:\") {\n cell(parsingModelCombo).align(Align.FILL)\n .comment(\"AI model for parsing and understanding tasks\")\n }\n row(\"Image Chat Model:\") {\n cell(imageChatModelCombo).align(Align.FILL)\n .comment(\"Multimodal AI model for image-related tasks\")\n }\n\n group(\"Task Configurations\") {\n row {\n scrollCell(taskConfigList)\n .align(Align.FILL)\n .comment(\"Double-click to edit a task configuration\")\n .resizableColumn()\n }.resizableRow()\n row {\n button(\"Add Task Config\") {\n addTaskConfig()\n }\n button(\"Edit\") {\n val selected = taskConfigList.selectedValue\n if (selected != null) {\n editTaskConfig(selected)\n } else {\n Messages.showWarningDialog(\n \"Please select a task configuration to edit\",\n \"No Selection\"\n )\n }\n }\n button(\"Delete\") {\n val selected = taskConfigList.selectedValue\n if (selected != null) {\n deleteTaskConfig(selected)\n } else {\n Messages.showWarningDialog(\n \"Please select a task configuration to delete\",\n \"No Selection\"\n )\n }\n }\n }\n }\n\n }\n }\n\n\n override fun doOKAction() {\n updateSettings() ?: return\n super.doOKAction()\n }\n}\n", + "diff": "-override fun createCenterPanel(): JComponent = panel {\n- group {\n- row(\"Saved Configs:\") {\n- cell(savedConfigsCombo).align(Align.FILL)\n- .comment(\"Select a saved configuration to load or save current settings\")\n- }\n- row {\n- button(\"Save\") { saveCurrentConfig() }\n- button(\"Load\") {\n- val selected = savedConfigsCombo.selectedItem as? String\n- if (selected != null) {\n- loadConfig(selected)\n- } else {\n- Messages.showWarningDialog(\n- \"Please select a configuration to load\", \"No Configuration Selected\"\n- )\n- }\n- }\n- button(\"Delete\") {\n- val selected = savedConfigsCombo.selectedItem as? String\n- if (selected != null) {\n- val confirmResult = JOptionPane.showConfirmDialog(\n- null, \"Delete configuration '$selected'?\", \"Confirm Delete\", JOptionPane.YES_NO_OPTION\n- )\n- if (confirmResult == Messages.YES) {\n- val configs = AppSettingsState.instance.savedPlanConfigs ?: mutableMapOf()\n- configs.remove(selected)\n- AppSettingsState.instance.savedPlanConfigs = configs\n- savedConfigsCombo.removeItem(selected)\n- }\n- } else {\n- Messages.showWarningDialog(\n- \"Please select a configuration to delete\", \"No Configuration Selected\"\n- )\n- }\n- }\n- button(\"Copy\") { exportTaskConfigs() }\n- button(\"Paste\") { importTaskConfigs() }\n- }\n-\n- group(\"Planning Settings\") {\n- row(\"Cognitive Mode:\") {\n- cell(cognitiveModeCombo).align(Align.FILL).comment(\"Select the cognitive strategy for planning\")\n- }\n- row {\n- cell(autoPlanPanel).align(Align.FILL)\n- }\n- }\n-\n- row {\n- cell(autoFixCheckbox).align(Align.FILL)\n- .comment(\"Automatically apply suggested fixes without confirmation\")\n- }\n-\n- row(\"Temperature:\") {\n- cell(temperatureSlider).align(Align.FILL)\n- .comment(\"Adjust AI response creativity (higher = more creative)\")\n- cell(temperatureLabel)\n- }\n- row(\"Default Model:\") {\n- cell(globalModelCombo).align(Align.FILL)\n- .comment(\"Default AI model for all tasks\")\n- }\n- row(\"Parsing Model:\") {\n- cell(parsingModelCombo).align(Align.FILL)\n- .comment(\"AI model for parsing and understanding tasks\")\n- }\n- row(\"Image Chat Model:\") {\n- cell(imageChatModelCombo).align(Align.FILL)\n- .comment(\"Multimodal AI model for image-related tasks\")\n- }\n-\n- group(\"Task Configurations\") {\n- row {\n- scrollCell(taskConfigList)\n- .align(Align.FILL)\n- .comment(\"Double-click to edit a task configuration\")\n- .resizableColumn()\n- }.resizableRow()\n- row {\n- button(\"Add Task Config\") {\n- addTaskConfig()\n- }\n- button(\"Edit\") {\n- val selected = taskConfigList.selectedValue\n- if (selected != null) {\n- editTaskConfig(selected)\n- } else {\n- Messages.showWarningDialog(\n- \"Please select a task configuration to edit\",\n- \"No Selection\"\n- )\n- }\n- }\n- button(\"Delete\") {\n- val selected = taskConfigList.selectedValue\n- if (selected != null) {\n- deleteTaskConfig(selected)\n- } else {\n- Messages.showWarningDialog(\n- \"Please select a task configuration to delete\",\n- \"No Selection\"\n- )\n- }\n- }\n- }\n- }\n-\n- }\n+ override fun createCenterPanel(): JComponent = JBScrollPane(panel {\n+ group {\n+ row(\"Saved Configs:\") {\n+ cell(savedConfigsCombo).align(Align.FILL)\n+ .comment(\"Select a saved configuration to load or save current settings\")\n+ }\n+ row {\n+ button(\"Save\") { saveCurrentConfig() }\n+ button(\"Load\") {\n+ val selected = savedConfigsCombo.selectedItem as? String\n+ if (selected != null) {\n+ loadConfig(selected)\n+ } else {\n+ Messages.showWarningDialog(\n+ \"Please select a configuration to load\", \"No Configuration Selected\"\n+ )\n+ }\n+ }\n+ button(\"Delete\") {\n+ val selected = savedConfigsCombo.selectedItem as? String\n+ if (selected != null) {\n+ val confirmResult = JOptionPane.showConfirmDialog(\n+ null, \"Delete configuration '$selected'?\", \"Confirm Delete\", JOptionPane.YES_NO_OPTION\n+ )\n+ if (confirmResult == Messages.YES) {\n+ val configs = AppSettingsState.instance.savedPlanConfigs ?: mutableMapOf()\n+ configs.remove(selected)\n+ AppSettingsState.instance.savedPlanConfigs = configs\n+ savedConfigsCombo.removeItem(selected)\n+ }\n+ } else {\n+ Messages.showWarningDialog(\n+ \"Please select a configuration to delete\", \"No Configuration Selected\"\n+ )\n+ }\n+ }\n+ button(\"Copy\") { exportTaskConfigs() }\n+ button(\"Paste\") { importTaskConfigs() }\n+ }\n+\n+ group(\"Planning Settings\") {\n+ row(\"Cognitive Mode:\") {\n+ cell(cognitiveModeCombo).align(Align.FILL).comment(\"Select the cognitive strategy for planning\")\n+ }\n+ row {\n+ cell(autoPlanPanel).align(Align.FILL)\n+ }\n+ }\n+\n+ row {\n+ cell(autoFixCheckbox).align(Align.FILL)\n+ .comment(\"Automatically apply suggested fixes without confirmation\")\n+ }\n+\n+ row(\"Temperature:\") {\n+ cell(temperatureSlider).align(Align.FILL)\n+ .comment(\"Adjust AI response creativity (higher = more creative)\")\n+ cell(temperatureLabel)\n+ }\n+ row(\"Default Model:\") {\n+ cell(globalModelCombo).align(Align.FILL)\n+ .comment(\"Default AI model for all tasks\")\n+ }\n+ row(\"Parsing Model:\") {\n+ cell(parsingModelCombo).align(Align.FILL)\n+ .comment(\"AI model for parsing and understanding tasks\")\n+ }\n+ row(\"Image Chat Model:\") {\n+ cell(imageChatModelCombo).align(Align.FILL)\n+ .comment(\"Multimodal AI model for image-related tasks\")\n+ }\n+\n+ group(\"Task Configurations\") {\n+ row {\n+ scrollCell(taskConfigList)\n+ .align(Align.FILL)\n+ .comment(\"Double-click to edit a task configuration\")\n+ .resizableColumn()\n+ }.resizableRow()\n+ row {\n+ button(\"Add Task Config\") {\n+ addTaskConfig()\n+ }\n+ button(\"Edit\") {\n+ val selected = taskConfigList.selectedValue\n+ if (selected != null) {\n+ editTaskConfig(selected)\n+ } else {\n+ Messages.showWarningDialog(\n+ \"Please select a task configuration to edit\",\n+ \"No Selection\"\n+ )\n+ }\n+ }\n+ button(\"Delete\") {\n+ val selected = taskConfigList.selectedValue\n+ if (selected != null) {\n+ deleteTaskConfig(selected)\n+ } else {\n+ Messages.showWarningDialog(\n+ \"Please select a task configuration to delete\",\n+ \"No Selection\"\n+ )\n+ }\n+ }\n+ }\n+ }\n+\n+ }\n+ }).apply {\n+ border = null\n+ viewport.border = null\n+ horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER\n}", + "newCode": "package cognotik.actions.plan\n\nimport com.intellij.openapi.project.Project\nimport com.intellij.openapi.ui.ComboBox\nimport com.intellij.openapi.ui.DialogWrapper\nimport com.intellij.openapi.ui.Messages\nimport com.intellij.ui.components.JBList\nimport com.intellij.ui.components.JBTextField\nimport com.intellij.ui.dsl.builder.Align\nimport com.intellij.ui.dsl.builder.panel\nimport com.simiacryptus.cognotik.chat.model.ChatModel\nimport com.simiacryptus.cognotik.config.AppSettingsState\nimport com.simiacryptus.cognotik.models.AIModel\nimport com.simiacryptus.cognotik.plan.OrchestrationConfig\nimport com.simiacryptus.cognotik.plan.TaskType\nimport com.simiacryptus.cognotik.plan.TaskTypeConfig\nimport com.simiacryptus.cognotik.plan.cognitive.CognitiveModeStrategies\nimport com.simiacryptus.cognotik.plan.newSettings\nimport com.simiacryptus.cognotik.platform.ApplicationServices\nimport com.simiacryptus.cognotik.platform.model.ApiChatModel\nimport com.simiacryptus.cognotik.platform.model.ApiData\nimport com.simiacryptus.cognotik.util.JsonUtil.fromJson\nimport com.simiacryptus.cognotik.util.JsonUtil.toJson\nimport org.slf4j.LoggerFactory\nimport java.awt.Component\nimport java.awt.Dimension\nimport java.awt.Font\nimport java.awt.Toolkit\nimport java.awt.datatransfer.DataFlavor\nimport java.awt.datatransfer.StringSelection\nimport javax.swing.*\n\nclass PlanConfigDialog(\n project: Project?,\n val settings: OrchestrationConfig,\n) : DialogWrapper(project) {\n \n\n\n\n\n\n override fun createCenterPanel(): JComponent = JBScrollPane(panel {\n group {\n row(\"Saved Configs:\") {\n cell(savedConfigsCombo).align(Align.FILL)\n .comment(\"Select a saved configuration to load or save current settings\")\n }\n row {\n button(\"Save\") { saveCurrentConfig() }\n button(\"Load\") {\n val selected = savedConfigsCombo.selectedItem as? String\n if (selected != null) {\n loadConfig(selected)\n } else {\n Messages.showWarningDialog(\n \"Please select a configuration to load\", \"No Configuration Selected\"\n )\n }\n }\n button(\"Delete\") {\n val selected = savedConfigsCombo.selectedItem as? String\n if (selected != null) {\n val confirmResult = JOptionPane.showConfirmDialog(\n null, \"Delete configuration '$selected'?\", \"Confirm Delete\", JOptionPane.YES_NO_OPTION\n )\n if (confirmResult == Messages.YES) {\n val configs = AppSettingsState.instance.savedPlanConfigs ?: mutableMapOf()\n configs.remove(selected)\n AppSettingsState.instance.savedPlanConfigs = configs\n savedConfigsCombo.removeItem(selected)\n }\n } else {\n Messages.showWarningDialog(\n \"Please select a configuration to delete\", \"No Configuration Selected\"\n )\n }\n }\n button(\"Copy\") { exportTaskConfigs() }\n button(\"Paste\") { importTaskConfigs() }\n }\n\n group(\"Planning Settings\") {\n row(\"Cognitive Mode:\") {\n cell(cognitiveModeCombo).align(Align.FILL).comment(\"Select the cognitive strategy for planning\")\n }\n row {\n cell(autoPlanPanel).align(Align.FILL)\n }\n }\n\n row {\n cell(autoFixCheckbox).align(Align.FILL)\n .comment(\"Automatically apply suggested fixes without confirmation\")\n }\n\n row(\"Temperature:\") {\n cell(temperatureSlider).align(Align.FILL)\n .comment(\"Adjust AI response creativity (higher = more creative)\")\n cell(temperatureLabel)\n }\n row(\"Default Model:\") {\n cell(globalModelCombo).align(Align.FILL)\n .comment(\"Default AI model for all tasks\")\n }\n row(\"Parsing Model:\") {\n cell(parsingModelCombo).align(Align.FILL)\n .comment(\"AI model for parsing and understanding tasks\")\n }\n row(\"Image Chat Model:\") {\n cell(imageChatModelCombo).align(Align.FILL)\n .comment(\"Multimodal AI model for image-related tasks\")\n }\n\n group(\"Task Configurations\") {\n row {\n scrollCell(taskConfigList)\n .align(Align.FILL)\n .comment(\"Double-click to edit a task configuration\")\n .resizableColumn()\n }.resizableRow()\n row {\n button(\"Add Task Config\") {\n addTaskConfig()\n }\n button(\"Edit\") {\n val selected = taskConfigList.selectedValue\n if (selected != null) {\n editTaskConfig(selected)\n } else {\n Messages.showWarningDialog(\n \"Please select a task configuration to edit\",\n \"No Selection\"\n )\n }\n }\n button(\"Delete\") {\n val selected = taskConfigList.selectedValue\n if (selected != null) {\n deleteTaskConfig(selected)\n } else {\n Messages.showWarningDialog(\n \"Please select a task configuration to delete\",\n \"No Selection\"\n )\n }\n }\n }\n }\n\n }\n }).apply {\n border = null\n viewport.border = null\n horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER\n }\n\n\n override fun doOKAction() {\n updateSettings() ?: return\n super.doOKAction()\n }\n}", + +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 053c8910..231dc1f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,10 @@ pluginName=Cognotik - Open Source Agentic Power Tools pluginRepositoryUrl=https://github.com/SimiaCryptus/Cognotik libraryGroup=com.simiacryptus -libraryVersion=2.0.32 +libraryVersion=2.0.33 # Maven Central Publishing cognotikGroup=com.cognotik -cognotikVersion=2.0.32 +cognotikVersion=2.0.33 # Signing (set these in ~/.gradle/gradle.properties or as environment variables) # signing.keyId= # signing.password= diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt index 1edf5c86..468aac0d 100644 --- a/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt +++ b/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt @@ -5,9 +5,9 @@ import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages import com.intellij.ui.components.JBList +import com.intellij.ui.components.JBScrollPane import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.panel import com.simiacryptus.cognotik.chat.model.ChatModel import com.simiacryptus.cognotik.config.AppSettingsState import com.simiacryptus.cognotik.models.AIModel @@ -97,7 +97,7 @@ class PlanConfigDialog( settings.defaultModel?.model?.modelName ?: AppSettingsState.instance.smartModel?.model?.modelName toolTipText = "Default AI model for all tasks" } -private val parsingModelCombo = + private val parsingModelCombo = ComboBox(visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()).apply { maximumSize = Dimension(CONFIG_COMBO_WIDTH, CONFIG_COMBO_HEIGHT) selectedItem = @@ -130,10 +130,11 @@ private val parsingModelCombo = // Task configuration list private val taskConfigListModel = DefaultListModel() -private val taskConfigList = JBList(taskConfigListModel).apply { + private val taskConfigList = JBList(taskConfigListModel).apply { cellRenderer = TaskConfigListCellRenderer() selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION toolTipText = "Configured tasks - double-click to edit" + visibleRowCount = 2 } @@ -222,7 +223,7 @@ private val taskConfigList = JBList(taskConfigListModel).apply { if (dialog.showAndGet()) { val selectedTaskTypes = dialog.getSelectedTaskTypes() if (selectedTaskTypes.isEmpty()) return - + // If multiple tasks selected, use default configuration without opening edit dialog if (selectedTaskTypes.size > 1) { selectedTaskTypes.forEach { taskType -> @@ -464,7 +465,7 @@ private val taskConfigList = JBList(taskConfigListModel).apply { settings.temperature = config.temperature.coerceIn(0.0, 1.0) settings.autoFix = config.autoFix settings.maxTaskHistoryChars = config.maxTaskHistoryChars -settings.maxTasksPerIteration = config.maxTasksPerIteration + settings.maxTasksPerIteration = config.maxTasksPerIteration settings.maxIterations = config.maxIterations settings.defaultModel = config.defaultModel settings.parsingModel = config.parsingModel @@ -502,7 +503,7 @@ settings.maxTasksPerIteration = config.maxTasksPerIteration } } -config.parsingModel?.model?.modelName?.let { modelName -> + config.parsingModel?.model?.modelName?.let { modelName -> visibleModelsCache.find { it.modelName == modelName }?.let { model -> settings.parsingModel = model.toApiChatModel() parsingModelCombo.selectedItem = modelName @@ -523,7 +524,8 @@ config.parsingModel?.model?.modelName?.let { modelName -> } } - override fun createCenterPanel(): JComponent = panel { + + override fun createCenterPanel(): JComponent = JBScrollPane(com.intellij.ui.dsl.builder.panel { group { row("Saved Configs:") { cell(savedConfigsCombo).align(Align.FILL) @@ -562,7 +564,6 @@ config.parsingModel?.model?.modelName?.let { modelName -> button("Copy") { exportTaskConfigs() } button("Paste") { importTaskConfigs() } } - group("Planning Settings") { row("Cognitive Mode:") { cell(cognitiveModeCombo).align(Align.FILL).comment("Select the cognitive strategy for planning") @@ -571,12 +572,10 @@ config.parsingModel?.model?.modelName?.let { modelName -> cell(autoPlanPanel).align(Align.FILL) } } - row { cell(autoFixCheckbox).align(Align.FILL) .comment("Automatically apply suggested fixes without confirmation") } - row("Temperature:") { cell(temperatureSlider).align(Align.FILL) .comment("Adjust AI response creativity (higher = more creative)") @@ -586,7 +585,7 @@ config.parsingModel?.model?.modelName?.let { modelName -> cell(globalModelCombo).align(Align.FILL) .comment("Default AI model for all tasks") } -row("Parsing Model:") { + row("Parsing Model:") { cell(parsingModelCombo).align(Align.FILL) .comment("AI model for parsing and understanding tasks") } @@ -594,7 +593,6 @@ row("Parsing Model:") { cell(imageChatModelCombo).align(Align.FILL) .comment("Multimodal AI model for image-related tasks") } - group("Task Configurations") { row { scrollCell(taskConfigList) @@ -602,7 +600,7 @@ row("Parsing Model:") { .comment("Double-click to edit a task configuration") .resizableColumn() }.resizableRow() -row { + row { button("Add Task Config") { addTaskConfig() } @@ -630,13 +628,23 @@ row { } } } - } + }).apply { + border = null + viewport.border = null + horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER } override fun doOKAction() { updateSettings() ?: return + try { + val configs = AppSettingsState.instance.savedPlanConfigs ?: mutableMapOf() + configs["Last"] = toJson(settings) + AppSettingsState.instance.savedPlanConfigs = configs + } catch (e: Exception) { + log.warn("Failed to save 'Last' configuration", e) + } super.doOKAction() } @@ -665,7 +673,7 @@ row { val model = visibleModelsCache.find { it.modelName == selectedGlobalModel } settings.defaultModel = model?.toApiChatModel() } -val selectedParsingModel = parsingModelCombo.selectedItem as? String + val selectedParsingModel = parsingModelCombo.selectedItem as? String if (selectedParsingModel != null) { val model = visibleModelsCache.find { it.modelName == selectedParsingModel } settings.parsingModel = model?.toApiChatModel() @@ -707,7 +715,7 @@ val selectedParsingModel = parsingModelCombo.selectedItem as? String // Validation patterns private val CONFIG_NAME_PATTERN = Regex("^[a-zA-Z0-9_ -]+$") - fun isVisible(chatModel: AIModel) = + fun isVisible(chatModel: AIModel) = ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.filter { it.key != null } .any { it.provider == chatModel.provider } } diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigEditDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigEditDialog.kt index 95fe224f..aaa8ecc4 100644 --- a/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigEditDialog.kt +++ b/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigEditDialog.kt @@ -5,6 +5,7 @@ import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages import com.intellij.ui.components.JBList +import com.intellij.ui.components.JBScrollPane import com.intellij.ui.components.JBTextArea import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.Align @@ -52,37 +53,48 @@ class TaskConfigEditDialog( // For SubPlanning task settings private val subTaskConfigListModel = DefaultListModel() - private val subTaskConfigList = JBList(subTaskConfigListModel) + private val subTaskConfigList = JBList(subTaskConfigListModel).apply { + this.visibleRowCount = 2 + } init { init() title = "Edit ${taskType.name} Configuration" + isResizable = true } + override fun getDimensionServiceKey(): String = "TaskConfigEditDialog" - override fun createCenterPanel(): JComponent = panel { - group("Task Configuration") { - row("Configuration Name:") { - cell(configNameField) - .align(Align.FILL) - .comment("Enter a unique name for this configuration") + + override fun createCenterPanel(): JComponent { + val dialogPanel = panel { + group("Task Configuration") { + row("Configuration Name:") { + cell(configNameField) + .align(Align.FILL) + .comment("Enter a unique name for this configuration") + } + + row("AI Model:") { + cell(modelCombo) + .align(Align.FILL) + .comment("Select the AI model to use for this task type") + } } + // Add task-specific configuration fields + createTaskSpecificFields() - row("AI Model:") { - cell(modelCombo) - .align(Align.FILL) - .comment("Select the AI model to use for this task type") + group("Task Type Information") { + row { + text(taskType.description ?: "No description available") + } } } - // Add task-specific configuration fields - createTaskSpecificFields() - group("Task Type Information") { - row { - text(taskType.description ?: "No description available") - } + return JBScrollPane(dialogPanel).apply { + preferredSize = Dimension(900, 700) + border = BorderFactory.createEmptyBorder() + horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED } - }.apply { - preferredSize = Dimension(600, 500) } private fun com.intellij.ui.dsl.builder.Panel.createTaskSpecificFields() { @@ -286,13 +298,17 @@ class TaskConfigEditDialog( ) return } - val configDialog = TaskConfigEditDialog(null, taskType, newConfig, availableModels) - if (configDialog.showAndGet()) { - val config = configDialog.getConfig() - val key = if (config.name != null) "${taskType.name}_${config.name}" else taskType.name - parentConfig.taskSettings[key] = config - subTaskConfigListModel.addElement(SubTaskConfigEntry(taskType, config, key)) + + val config = if (dialog.isQuickSelect) { + newConfig + } else { + val configDialog = TaskConfigEditDialog(null, taskType, newConfig, availableModels) + if (configDialog.showAndGet()) configDialog.getConfig() else return } + + val key = if (config.name != null) "${taskType.name}_${config.name}" else taskType.name + parentConfig.taskSettings[key] = config + subTaskConfigListModel.addElement(SubTaskConfigEntry(taskType, config, key)) } } diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt index d43eccf3..f110dd7c 100644 --- a/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt +++ b/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt @@ -31,6 +31,9 @@ class TaskTypeSelectionDialog( } private val taskTree: JTree + var isQuickSelect = false + private set + init { val root = DefaultMutableTreeNode("Task Types") @@ -99,7 +102,7 @@ class TaskTypeSelectionDialog( override fun valueChanged(e: TreeSelectionEvent?) { selectedTaskTypes.clear() - + val paths = selectionPaths if (paths != null) { paths.forEach { path -> @@ -110,7 +113,7 @@ class TaskTypeSelectionDialog( } } } - + if (selectedTaskTypes.isNotEmpty()) { updateDescription(selectedTaskTypes.toList()) } else { @@ -122,19 +125,21 @@ class TaskTypeSelectionDialog( } } }) - + // Add double-click listener to select and OK addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { - if (e.clickCount == 2 && !allowMultipleSelection) { - val path = getPathForLocation(e.x, e.y) + if (e.clickCount == 2) { + val path = taskTree.getPathForLocation(e.x, e.y) if (path != null) { val node = path.lastPathComponent as? DefaultMutableTreeNode val userObject = node?.userObject if (userObject is TaskTypeNode) { selectedTaskTypes.clear() selectedTaskTypes.add(userObject.taskType) + isQuickSelect = true doOKAction() + e.consume() } } } @@ -150,6 +155,8 @@ class TaskTypeSelectionDialog( init() title = if (allowMultipleSelection) "Select Task Types" else "Select Task Type" } + override fun getDimensionServiceKey(): String = "TaskTypeSelectionDialog" + private fun getPackageGroup(taskType: TaskType<*, *>): String { return when { @@ -174,17 +181,17 @@ class TaskTypeSelectionDialog( ) -> "Gaming" taskType.name in listOf( - "NarrativeGeneration", "NarrativeReasoning", "ArticleGeneration", - "TechnicalExplanation", "TutorialGeneration", - "BusinessProposal", "EmailCampaign", "InteractiveStory", - "ReportGeneration", "Scriptwriting", "JournalismReasoning", - "ResearchPaperGeneration", - ) -> "Writing" - - taskType.name in listOf( + "NarrativeGeneration", "NarrativeReasoning", "ArticleGeneration", + "TechnicalExplanation", "TutorialGeneration", + "BusinessProposal", "EmailCampaign", "InteractiveStory", + "ReportGeneration", "Scriptwriting", "JournalismReasoning", + "ResearchPaperGeneration", + ) -> "Writing" + + taskType.name in listOf( "Analysis", "FileModification", "FileSearch", "WriteHtml", "GeneratePresentation", "GenerateImage", - "IllustrateDocument", + "IllustrateDocument", ) -> "File Operations" taskType.name in listOf("VectorSearch", "KnowledgeIndexing") -> "Knowledge Management" @@ -203,7 +210,7 @@ class TaskTypeSelectionDialog( } return } - + if (taskTypes.size == 1) { val taskType = taskTypes[0] descriptionPane.text = buildString { @@ -290,7 +297,7 @@ class TaskTypeSelectionDialog( } fun getSelectedTaskTypes(): List> = selectedTaskTypes.toList() - + @Deprecated("Use getSelectedTaskTypes() instead", ReplaceWith("getSelectedTaskTypes().firstOrNull()")) fun getSelectedTaskType(): TaskType<*, *>? = selectedTaskTypes.firstOrNull() diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/apps/general/CmdPatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/apps/general/CmdPatchApp.kt index 6cb65e96..d206a2de 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/apps/general/CmdPatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/apps/general/CmdPatchApp.kt @@ -10,7 +10,7 @@ import java.io.File import java.nio.file.Path import java.util.concurrent.TimeUnit -val String.renderMarkdown: String get() = MarkdownUtil.renderMarkdown(this) +val String.renderMarkdown: String get() = renderMarkdown(true) fun String.renderMarkdown(tabs: Boolean = false): String = MarkdownUtil.renderMarkdown(this, tabs = tabs) class CmdPatchApp( diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/AbstractFileTask.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/AbstractFileTask.kt index 804daf02..106adcaf 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/AbstractFileTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/AbstractFileTask.kt @@ -1,6 +1,7 @@ package com.simiacryptus.cognotik.plan.tools.file import com.simiacryptus.cognotik.describe.Description +import com.simiacryptus.cognotik.input.DocumentReader import com.simiacryptus.cognotik.input.PaginatedDocumentReader import com.simiacryptus.cognotik.input.getDocumentReader import com.simiacryptus.cognotik.plan.AbstractTask @@ -55,7 +56,7 @@ abstract class AbstractFileTask( } }) }.filter { file -> - file.isFile && file.exists() + file.isFile && file.exists() && !isIgnored(file) } .distinct() .filterNotNull() @@ -63,6 +64,14 @@ abstract class AbstractFileTask( .mapNotNull { fn(it) } .joinToString("\n\n") + protected open fun isIgnored(file: File): Boolean = when(file.extension) { + /* Common Binary Files */ + "class", "jar", "exe", "dll", "bin", "img", "iso", "zip", "tar", "gz", "7z" -> true + /* Common Image and Media */ + "png", "jpg", "jpeg", "gif", "bmp", "tiff", "mp4", "mp3", "avi", "mov", "wmv", "flv", "mkv" -> true + else -> false + } + protected open fun toString(relativePath: File): CharSequence? = try { val file = root.toFile().resolve(relativePath) val content = if (executionConfig?.extractContent == true && !isTextFile(file)) { @@ -115,7 +124,14 @@ abstract class AbstractFileTask( file.getDocumentReader().use { reader -> when (reader) { is PaginatedDocumentReader -> reader.getText(0, reader.getPageCount()) - else -> reader.getText() + else -> { + val text = reader.getText() + when { + text.isAsciiPrintable() -> text + text.length > (1024 * 512) -> "" + else -> text + } + } } } } catch (e: Exception) { @@ -128,4 +144,8 @@ abstract class AbstractFileTask( } } } -} \ No newline at end of file +} + +fun String.isAsciiPrintable(): Boolean { + return this.all { it.code in 32..126 || it == '\n' || it == '\r' || it == '\t' } +} diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/FileModificationTask.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/FileModificationTask.kt index c0eec6a1..8841ddf0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/FileModificationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/FileModificationTask.kt @@ -236,7 +236,7 @@ ${ } else if (((executionConfig?.related_files ?: listOf()) + (executionConfig?.files ?: listOf())).distinct().size == 1 ) { - ((executionConfig?.related_files ?: listOf()) + (executionConfig?.files ?: listOf())).first() + ((executionConfig?.related_files ?: listOf()) + (executionConfig?.files ?: listOf())).last() } else if ((executionConfig?.files ?: listOf()).distinct().size == 1) { (executionConfig?.files ?: listOf()).first() } else { diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/GenerateImageTask.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/GenerateImageTask.kt index 62f0b5e3..bda7dbfe 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/GenerateImageTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/file/GenerateImageTask.kt @@ -172,6 +172,11 @@ GenerateImage - Create images using AI image generation models } } + override fun isIgnored(file: File) = when(file.extension) { + "png", "jpg", "jpeg" -> true + else -> super.isIgnored(file) + } + override fun acceptButtonFooter(ui: SocketManager, fn: () -> Unit): String { val acceptLink = ui.hrefLink("Accept and Save Image") { fn() diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/reasoning/NeuralNetworkLayerTask.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/reasoning/NeuralNetworkLayerTask.kt index e784ff5d..c2a45bc5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/reasoning/NeuralNetworkLayerTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/plan/tools/reasoning/NeuralNetworkLayerTask.kt @@ -73,8 +73,8 @@ class NeuralNetworkLayerTask( val include_lyapunov: Boolean = true, @Description("Whether to include Lipschitz analysis") val include_lipschitz: Boolean = true, - @Description("Target implementation languages") - val implementation_languages: List? = listOf("python", "pseudocode"), + @Description("Target implementation languages, e.g. 'tensorflow.js', 'pseudocode'") + val implementation_languages: List? = listOf("tensorflow.js"), @Description("Whether to include numerical stability analysis") val include_numerical_stability: Boolean = true, @Description("Whether to generate test cases") @@ -380,7 +380,7 @@ class NeuralNetworkLayerTask( val includeHigherOrder = executionConfig?.include_higher_order ?: true val includeLyapunov = executionConfig?.include_lyapunov ?: true val includeLipschitz = executionConfig?.include_lipschitz ?: true - val languages = executionConfig?.implementation_languages ?: listOf("python") + val languages = executionConfig?.implementation_languages ?: listOf("tensorflow.js") val includeNumerical = executionConfig?.include_numerical_stability ?: true val generateTests = executionConfig?.generate_tests ?: true val analysisDepth = executionConfig?.analysis_depth ?: "standard" @@ -1697,7 +1697,7 @@ Provide a complete implementation: 4. Required imports/dependencies Use idiomatic code for the target language. Include comments explaining the mathematics. -For Python, use NumPy. For pseudocode, be clear and mathematical. +For Python, use NumPy. For Tensorflow.js, use tfjs. For pseudocode, be clear and mathematical. """.trimIndent(), model = api, temperature = 0.3, diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/util/AddApplyFileDiffLinks.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/util/AddApplyFileDiffLinks.kt index f87eb692..5c40d9a5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/util/AddApplyFileDiffLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/util/AddApplyFileDiffLinks.kt @@ -173,7 +173,7 @@ open class AddApplyFileDiffLinks(val processor: PatchProcessor) { fun isFileResolvable(header: String?): Boolean { try { val prefiltered = prefilterFilename(normalizeFilename(header ?: "")) ?: "" - val resolvedPath = resolver(root, prefiltered) ?: return (null != defaultFile) + val resolvedPath = resolver(root, prefiltered) ?: return (true != header?.contains('.') && null != defaultFile) if (root.resolve(resolvedPath).toFile().exists()) return true if(!resolvedPath.contains('.') && null != defaultFile) return true // Allow default file for extensionless paths (likely to be a mis-parse) return false diff --git a/webui/src/main/kotlin/com/simiacryptus/cognotik/webui/chat/ChatSocketManager.kt b/webui/src/main/kotlin/com/simiacryptus/cognotik/webui/chat/ChatSocketManager.kt index 52cfea34..f22a1e5d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/cognotik/webui/chat/ChatSocketManager.kt +++ b/webui/src/main/kotlin/com/simiacryptus/cognotik/webui/chat/ChatSocketManager.kt @@ -86,7 +86,7 @@ open class ChatSocketManager( val expandedUserMessage = expandTopics(userMessage) markdownTranscript?.write("## User\n$expandedUserMessage\n\n".toByteArray()) val task = newTask() - task.echo(renderResponse(expandedUserMessage, task)) + task.echo(expandedUserMessage.renderMarkdown) synchronized(messagesLock) { chatMessages += ModelSchema.ChatMessage(ModelSchema.Role.user, expandedUserMessage.toContentList())