diff --git a/build.gradle.kts b/build.gradle.kts index 18cab052..8e6c4a68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ fun environment(key: String) = providers.environmentVariable(key).get() plugins { id("java") // Java support - id("org.jetbrains.kotlin.jvm") version "1.9.21" + id("org.jetbrains.kotlin.jvm") version "2.0.0-Beta5" id("org.jetbrains.intellij") version "1.17.2" id("org.jetbrains.changelog") version "2.2.0" id("org.jetbrains.qodana") version "2023.2.1" @@ -23,10 +23,10 @@ repositories { maven(url = "https://packages.jetbrains.team/maven/p/iuia/qa-automation-maven") } -val kotlin_version = "1.9.21" +val kotlin_version = "2.0.0-Beta5" val jetty_version = "11.0.18" val slf4j_version = "2.0.9" -val skyenet_version = "1.0.60" +val skyenet_version = "1.0.61" val remoterobot_version = "0.11.21" dependencies { diff --git a/gradle.properties b/gradle.properties index f1b2e654..673c8b11 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ pluginName=intellij-aicoder pluginRepositoryUrl=https://github.com/SimiaCryptus/intellij-aicoder -pluginVersion=1.3.9 +pluginVersion=1.3.10 jvmArgs=-Xmx8g org.gradle.jvmargs=-Xmx8g diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt index e6533ab2..5f949031 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/AutoDevAction.kt @@ -4,7 +4,7 @@ import com.github.simiacryptus.aicoder.actions.BaseAction import com.github.simiacryptus.aicoder.actions.dev.AppServer import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.util.UITools -import com.github.simiacryptus.aicoder.util.addApplyDiffLinks2 +import com.github.simiacryptus.aicoder.util.addApplyFileDiffLinks import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys import com.simiacryptus.jopenai.API @@ -19,7 +19,6 @@ import com.simiacryptus.skyenet.Acceptable import com.simiacryptus.skyenet.AgentPatterns import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.core.actors.* -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent import com.simiacryptus.skyenet.core.platform.* import com.simiacryptus.skyenet.core.platform.file.DataStorage import com.simiacryptus.skyenet.webui.application.ApplicationInterface @@ -227,7 +226,7 @@ class AutoDevAction : BaseAction() { | """.trimMargin() } - ui.socketManager.addApplyDiffLinks2( + ui.socketManager.addApplyFileDiffLinks( root = root, code = codeFiles, response = taskActor.answer(listOf( diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DiffChatAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DiffChatAction.kt index 18f5c756..03877e50 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DiffChatAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/DiffChatAction.kt @@ -95,8 +95,7 @@ class DiffChatAction : BaseAction() { } codeBuffer.set(newCode) }, task = task, ui=ui) - val html = renderMarkdown(withLinks) - return """
$html
""" + return """
${renderMarkdown(withLinks)}
""" } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt index 1dfb6521..00994a28 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/MultiDiffChatAction.kt @@ -6,7 +6,7 @@ import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.config.AppSettingsState.Companion.chatModel import com.github.simiacryptus.aicoder.util.ComputerLanguage import com.github.simiacryptus.aicoder.util.UITools -import com.github.simiacryptus.aicoder.util.addApplyDiffLinks2 +import com.github.simiacryptus.aicoder.util.addApplyFileDiffLinks import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.command.WriteCommandAction @@ -104,7 +104,7 @@ class MultiDiffChatAction : BaseAction() { storage = ApplicationServices.dataStorageFactory(DiffChatAction.root), ) { override fun renderResponse(response: String, task: SessionTask): String { - val html = addApplyDiffLinks2( + val html = addApplyFileDiffLinks( root = root, code = codeFiles, response = response, diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/TaskRunnerAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/TaskRunnerAction.kt index 442488ed..4e5ffabe 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/TaskRunnerAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/TaskRunnerAction.kt @@ -5,7 +5,7 @@ import com.github.simiacryptus.aicoder.actions.dev.AppServer import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.config.AppSettingsState.Companion.chatModel import com.github.simiacryptus.aicoder.util.UITools -import com.github.simiacryptus.aicoder.util.addApplyDiffLinks2 +import com.github.simiacryptus.aicoder.util.addApplyFileDiffLinks import com.github.simiacryptus.aicoder.util.addSaveLinks import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys.VIRTUAL_FILE_ARRAY @@ -23,7 +23,6 @@ import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.TabbedDisplay import com.simiacryptus.skyenet.apps.coding.CodingAgent import com.simiacryptus.skyenet.core.actors.* -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent import com.simiacryptus.skyenet.core.platform.ApplicationServices.clientManager import com.simiacryptus.skyenet.core.platform.ClientManager import com.simiacryptus.skyenet.core.platform.Session @@ -123,6 +122,7 @@ class TaskRunnerApp( shellCommandTaskEnabled = false, ).startProcess(userMessage = userMessage) } catch (e: Throwable) { + ui.newTask().error(ui, e) log.warn("Error", e) } } @@ -163,44 +163,44 @@ class TaskRunnerAgent( ActorTypes.TaskBreakdown to ParsedActor( resultClass = TaskBreakdownResult::class.java, prompt = """ - Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. - Detail task dependencies and relationships, and ensure the tasks are well-organized and logically ordered. - Briefly explain your rationale for the task breakdown and ordering. - - Tasks can be of the following types: - ${ + |Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. + |Detail files input and output as well as task execution dependencies. + |Keep in mind that implementation details need to be shared between the file generation tasks. + |Creating directories and initializing source control are out of scope. + | + |Tasks can be of the following types: + |${ if (!taskPlanningEnabled) "" else """ |* TaskPlanning - High-level planning and organization of tasks - identify smaller, actionable tasks based on the information available at task execution time. | ** Specify the prior tasks and the goal of the task """.trimMargin().trim() } - ${ - if (!shellCommandTaskEnabled) "" else - """ + |${ + if (!shellCommandTaskEnabled) "" else """ |* RunShellCommand - Execute shell commands and provide the output - | ** Specify the environment variables, working directory, language, and command to be executed - | ** List input files/tasks to be examined - """.trimMargin().trim() + | ** Specify the command to be executed, or describe the task to be performed + | ** List input files/tasks to be examined when writing the command + """.trimMargin().trim() } - * Inquiry - Answer questions by reading in files and providing a summary that can be discussed with and approved by the user - ** Specify the questions and the goal of the inquiry - ** List input files to be examined - * NewFile - Create one or more new files - ** For each file, specify the relative file path and the purpose of the file - ** List input files/tasks to be examined - * EditFile - Modify existing files - ** For each file, specify the relative file path and the goal of the modification - ** List input files/tasks to be examined - * Documentation - Generate documentation - ** List input files/tasks to be examined - - """.trimIndent(), + |* Inquiry - Answer questions by reading in files and providing a summary that can be discussed with and approved by the user + | ** Specify the questions and the goal of the inquiry + | ** List input files to be examined when answering the questions + |* NewFile - Create one or more new files, carefully considering how they fit into the existing project structure + | ** For each file, specify the relative file path and the purpose of the file + | ** List input files/tasks to be examined when authoring the new files + |* EditFile - Modify existing files + | ** For each file, specify the relative file path and the goal of the modification + | ** List input files/tasks to be examined when designing the modifications + |* Documentation - Generate documentation + | ** List input files/tasks to be examined + """.trimMargin(), model = model, parsingModel = parsingModel, temperature = temperature, ), ActorTypes.DocumentationGenerator to SimpleActor( + name = "DocumentationGenerator", prompt = """ Create detailed and clear documentation for the provided code, covering its purpose, functionality, inputs, outputs, and any assumptions or limitations. Use a structured and consistent format that facilitates easy understanding and navigation. @@ -211,9 +211,12 @@ class TaskRunnerAgent( temperature = temperature, ), ActorTypes.NewFileCreator to SimpleActor( + name = "NewFileCreator", prompt = """ Generate the necessary code for a new file based on the given requirements and context. Ensure the code is well-structured, follows best practices, and meets the specified functionality. + Carefully consider how the new file fits into the existing project structure and architecture. + Avoid creating files that duplicate functionality or introduce inconsistencies. Provide a clear file name suggestion based on the content and purpose of the file. Response should use one or more ``` code blocks to output file contents. @@ -240,9 +243,13 @@ class TaskRunnerAgent( temperature = temperature, ), ActorTypes.FilePatcher to SimpleActor( + name = "FilePatcher", prompt = """ Generate a patch for an existing file to modify its functionality or fix issues based on the given requirements and context. Ensure the modifications are efficient, maintain readability, and adhere to coding standards. + Carefully review the existing code and project structure to ensure the changes are consistent and do not introduce bugs. + Consider the impact of the modifications on other parts of the codebase. + Provide a summary of the changes made. Response should use one or more code patches in diff format within ```diff code blocks. @@ -260,14 +267,13 @@ class TaskRunnerAgent( + const a = 1; ``` - Consider the following task types: ${if (!taskPlanningEnabled) "" else "TaskPlanning, "}${if (!shellCommandTaskEnabled) "" else "RunShellCommand, "}Requirements, NewFile, EditFile, and Documentation. - Ensure that each identified task fits one of these categories and specify the task type for better integration with the system. Continued text """.trimIndent(), model = model, temperature = temperature, ), ActorTypes.Inquiry to SimpleActor( + name = "Inquiry", prompt = """ Create code for a new file that fulfills the specified requirements and context. Given a detailed user request, break it down into smaller, actionable tasks suitable for software development. @@ -275,7 +281,12 @@ class TaskRunnerAgent( Provide a comprehensive overview, including key concepts, relevant technologies, best practices, and any potential challenges or considerations. Ensure the information is accurate, up-to-date, and well-organized to facilitate easy understanding. - Focus on generating insights and information that support the task types available in the system (${if (!taskPlanningEnabled) "" else "TaskPlanning, "}${if (!shellCommandTaskEnabled) "" else "RunShellCommand, "}Requirements, NewFile, EditFile, Documentation). + When generating insights, consider the existing project context and focus on information that is directly relevant and applicable. + Focus on generating insights and information that support the task types available in the system (${ + if (!taskPlanningEnabled) "" else "TaskPlanning, " + }${ + if (!shellCommandTaskEnabled) "" else "RunShellCommand, " + }Requirements, NewFile, EditFile, Documentation). This will ensure that the inquiries are tailored to assist in the planning and execution of tasks within the system's framework. """.trimIndent(), model = model, @@ -283,6 +294,7 @@ class TaskRunnerAgent( ), ) + (if (!shellCommandTaskEnabled) mapOf() else mapOf( ActorTypes.RunShellCommand to CodingActor( + name = "RunShellCommand", interpreterClass = ProcessInterpreter::class, details = """ Execute the following shell command(s) and provide the output. Ensure to handle any errors or exceptions gracefully. @@ -302,7 +314,7 @@ class TaskRunnerAgent( val event: AnActionEvent, val root: Path -) : ActorSystem( +) : ActorSystem( actorMap.map { it.key.name to it.value }.toMap(), dataStorage, user, @@ -379,15 +391,11 @@ class TaskRunnerAgent( fun startProcess(userMessage: String) { val codeFiles = codeFiles - val eventStatus = if (codeFiles.size > 2) """ - |Root: ${root.toFile().absolutePath} - | + val eventStatus = if (!codeFiles.all { File(it.key).isFile } || codeFiles.size > 2) """ |Files: |${codeFiles.keys.joinToString("\n") { "* ${it}" }} """.trimMargin() else { """ - |Root: ${root.toFile().absolutePath} - | |${ virtualFiles.joinToString("\n\n") { val path = root.relativize(it.toNioPath()) @@ -429,8 +437,6 @@ class TaskRunnerAgent( api = api ) }, -// atomicRef = AtomicReference(), -// semaphore = Semaphore(0), ).call() try { @@ -677,7 +683,6 @@ class TaskRunnerAgent( ) } - /*RunShellCommand*/ TaskType.RunShellCommand -> { if (shellCommandTaskEnabled) { val semaphore = Semaphore(0) @@ -868,7 +873,7 @@ class TaskRunnerAgent( ) genState.taskResult[taskId] = codeResult renderMarkdown( - ui.socketManager.addApplyDiffLinks2( + ui.socketManager.addApplyFileDiffLinks( root = root, ui = ui, code = codeFiles, @@ -1134,6 +1139,9 @@ class TaskRunnerAgent( return input } + companion object { + private val log = LoggerFactory.getLogger(TaskRunnerAgent::class.java) + enum class ActorTypes { TaskBreakdown, DocumentationGenerator, @@ -1143,8 +1151,6 @@ class TaskRunnerAgent( RunShellCommand, } - companion object { - private val log = LoggerFactory.getLogger(TaskRunnerAgent::class.java) fun executionOrder(tasks: Map): List { val taskIds: MutableList = mutableListOf() val taskMap = tasks.toMutableMap() diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt index d7a7b267..49732a68 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/WebDevAction.kt @@ -4,7 +4,7 @@ import com.github.simiacryptus.aicoder.actions.BaseAction import com.github.simiacryptus.aicoder.actions.dev.AppServer import com.github.simiacryptus.aicoder.config.AppSettingsState import com.github.simiacryptus.aicoder.util.UITools -import com.github.simiacryptus.aicoder.util.addApplyDiffLinks2 +import com.github.simiacryptus.aicoder.util.addApplyFileDiffLinks import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.vfs.VirtualFile import com.simiacryptus.jopenai.API @@ -18,7 +18,6 @@ import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.Acceptable import com.simiacryptus.skyenet.AgentPatterns import com.simiacryptus.skyenet.core.actors.* -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent import com.simiacryptus.skyenet.core.platform.* import com.simiacryptus.skyenet.core.platform.file.DataStorage import com.simiacryptus.skyenet.webui.application.ApplicationInterface @@ -308,7 +307,7 @@ class WebDevAction : BaseAction() { fun outputFn(task: SessionTask, design: String): StringBuilder? { //val task = ui.newTask() return task.complete( - ui.socketManager.addApplyDiffLinks2( + ui.socketManager.addApplyFileDiffLinks( root = codeFiles.keys.map { File(it).toPath() }.toTypedArray().commonRoot(), code = codeFiles, response = design, diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt index 021c41e5..dc2982a2 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionSettingsRegistry.kt @@ -1,59 +1,82 @@ package com.github.simiacryptus.aicoder.config import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue import com.github.simiacryptus.aicoder.ui.EditorMenu import com.github.simiacryptus.aicoder.util.IdeaKotlinInterpreter import com.github.simiacryptus.aicoder.util.UITools import com.intellij.openapi.actionSystem.AnAction import java.io.File -import java.util.stream.Collectors -class ActionSettingsRegistry { +class ActionSettingsRegistry( + val configDir: File +) { - val actionSettings: MutableMap = HashMap() + // Removed in-memory actionSettings map private val version = 2.0080 // Increment this value to force a reload of all actions + private val objectMapper = jacksonObjectMapper() + private fun updateActionConfig(action: AnAction, code: String, language: String) { - val actionConfig = this.getActionConfig(action) + var actionConfig = this.getActionConfig(action) log.info("Updating action config for action: ${action.javaClass.name}, language: $language") + log.debug("Action Config before update: $actionConfig") actionConfig.language = language actionConfig.isDynamic = false with(action) { templatePresentation.text = actionConfig.displayText templatePresentation.description = actionConfig.displayText } + log.debug("Action Config after basic update: $actionConfig") if (!actionConfig.enabled) { + log.info("Action ${action.javaClass.name} is disabled, skipping further updates") return } else if (!actionConfig.file.exists() || actionConfig.file.readText().isBlank() || (actionConfig.version ?: 0.0) < version ) { + log.info("Writing new code to action file for ${action.javaClass.name}") actionConfig.file.writeText(code) actionConfig.version = version + saveActionConfig(actionConfig) // Save updated config to JSON file } else { handleDynamicActionConfig(action, actionConfig, code) } } + private fun saveActionConfig(actionConfig: ActionSettings) { + val configFile = File(configDir, "${actionConfig.packageName.replace('.', '/')}/${actionConfig.className}.json") + configFile.parentFile.mkdirs() + objectMapper.writeValue(configFile, actionConfig) + log.info("Action config saved to ${configFile.path}") + } + private fun handleDynamicActionConfig(action: AnAction, actionConfig: ActionSettings, code: String) { + log.debug("Entering handleDynamicActionConfig for ${action.javaClass.name}") if (actionConfig.isDynamic || (actionConfig.version ?: 0.0) >= version) { val localCode = actionConfig.file.readText().dropWhile { !it.isLetter() } + log.debug("Local code read from file for ${action.javaClass.name}") if (!localCode.equals(code)) { try { log.info("Handling dynamic action config for action: ${action.javaClass.name}") + log.debug("Dynamic code differs from existing, updating for ${action.javaClass.name}") val element = actionConfig.buildAction(localCode) actionConfig.version = version actionConfig.file.writeText(code) throw ReplaceActionException() } catch (e: Throwable) { log.info("Error loading dynamic ${action.javaClass}", e) + log.error("Exception during dynamic action config handling for ${action.javaClass.name}", e) } } } val canLoad = try { ActionSettingsRegistry::class.java.classLoader.loadClass(actionConfig.id) + log.debug("Successfully loaded class for action: ${actionConfig.id}") true } catch (e: Throwable) { + log.error("Failed to load class for action: ${actionConfig.id}", e) false } if (canLoad) { @@ -61,24 +84,29 @@ class ActionSettingsRegistry { actionConfig.file.writeText(code) actionConfig.version = version } else { + log.info("Cannot load class for action: ${actionConfig.id}, removing action.") throw RemoveActionException() } } fun edit(superChildren: Array): Array { + log.info("Starting edit process for actions") val children = superChildren.toList().toMutableList() children.toTypedArray().forEach { action -> val language = "kt" log.info("Editing action: ${action.javaClass.name}, language: $language") + log.debug("Attempting to load code for action: ${action.javaClass.name}") val code: String? = load(action.javaClass, language) if (null != code) { try { updateActionConfig(action, code, language) } catch (e: RemoveActionException) { children.remove(action) + log.info("Action removed due to RemoveActionException: ${action.javaClass.name}") if (e.newAction != null) children.add(e.newAction) } catch (e: Throwable) { UITools.error(log, "Error loading ${action.javaClass}", e) + log.error("Exception caught during action editing: ${action.javaClass.name}", e) } } } @@ -87,12 +115,15 @@ class ActionSettingsRegistry { if (!it.file.exists()) return@forEach log.info("Adding dynamic action: ${it.id}") if (!it.enabled) return@forEach + log.debug("Building dynamic action: ${it.id}") val element = it.buildAction(it.file.readText()) children.add(element) } catch (e: Throwable) { UITools.error(log, "Error loading dynamic action", e) + log.error("Exception caught during dynamic action addition: ${it.id}", e) } } + log.info("Edit process completed. Total actions: ${children.size}") return children.toTypedArray() } @@ -107,6 +138,7 @@ class ActionSettingsRegistry { ) : Exception(msg, cause) data class ActionSettings( + val configDir: File, val id: String, // Static property var enabled: Boolean = true, // User settable // Adding logging within the buildAction method to log the action building process @@ -116,7 +148,7 @@ class ActionSettingsRegistry { var language: String? = null, // Static property val packageName: String = id.substringBeforeLast('.'), val className: String = id.substringAfterLast('.'), - val file: File = File(configDir(), "actions/${packageName.replace('.', '/')}/$className.$language").apply { + val file: File = File(configDir, "${packageName.replace('.', '/')}/$className.${language ?: "kt"}").apply { parentFile.mkdirs() }, ) { @@ -128,12 +160,12 @@ class ActionSettingsRegistry { with( actionCache.getOrPut("$packageName.$newClassName") { log.info("Compiling code for new action: $newClassName") - (compile( - code.replace( - ("""(? { @@ -164,8 +196,6 @@ class ActionSettingsRegistry { other as ActionSettings if (id != other.id) return false - if (enabled != other.enabled) return false - if (displayText != other.displayText) return false if (isDynamic != other.isDynamic) return false return language == other.language } @@ -173,8 +203,6 @@ class ActionSettingsRegistry { override fun hashCode(): Int { var result = id.hashCode() - result = 31 * result + enabled.hashCode() - result = 31 * result + (displayText?.hashCode() ?: 0) result = 31 * result + isDynamic.hashCode() result = 31 * result + (language?.hashCode() ?: 0) return result @@ -183,36 +211,51 @@ class ActionSettingsRegistry { } private fun getActionConfig(action: AnAction): ActionSettings { - return actionSettings.getOrPut(action.javaClass.name) { - val actionConfig = ActionSettings(action.javaClass.name) + val configFile = File(configDir, "${action.javaClass.`package`.name.replace('.', '/')}/${action.javaClass.simpleName}.json") + return if (configFile.exists()) { + objectMapper.readValue(configFile) + } else { + val actionConfig = ActionSettings(configDir, action.javaClass.name) log.info("Creating new action config for action: ${action.javaClass.name}") actionConfig.displayText = action.templatePresentation.text + saveActionConfig(actionConfig) // Save new config to JSON file actionConfig } } @JsonIgnore fun getDynamicActions(): List { - return actionSettings.entries.stream().filter { it.value.isDynamic && it.value.enabled }.map { it.value } - .collect(Collectors.toList()) + val dynamicActionConfigs = configDir.walk().filter { it.extension == "json" }.map { objectMapper.readValue(it) } + return dynamicActionConfigs.filter { it.isDynamic && it.enabled }.toList() } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as ActionSettingsRegistry - return actionSettings == other.actionSettings + // Removed comparison based on in-memory map + return true } override fun hashCode(): Int { - return actionSettings.hashCode() + // Removed hash code based on in-memory map + return super.hashCode() } + val actionSettings: Map + get() { + val allActionSettings = mutableMapOf() + configDir.walk().filter { it.extension == "json" }.forEach { file -> + val actionSetting = objectMapper.readValue(file) + allActionSettings[actionSetting.id] = actionSetting + } + return allActionSettings.toMap() + } + companion object { private val log = org.slf4j.LoggerFactory.getLogger(ActionSettingsRegistry::class.java) - val actionCache = HashMap() + private val actionCache = HashMap() private fun load(actionPackage: String, actionName: String, language: String) = load("/sources/${language}/$actionPackage/$actionName.$language") @@ -224,7 +267,7 @@ class ActionSettingsRegistry { fun load(clazz: Class, language: String) = load(clazz.`package`.name.replace('.', '/'), clazz.simpleName, language) - fun configDir() = AppSettingsState.instance.pluginHome.apply { mkdirs() } + } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionTable.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionTable.kt index 59c9442f..91d75a9e 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionTable.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/ActionTable.kt @@ -25,13 +25,13 @@ class ActionTable( ) : JPanel(BorderLayout()) { fun read(registry: ActionSettingsRegistry) { - registry.actionSettings.clear() +// registry.actionSettings.clear() rowData.map { row -> val copy = (actionSettings.find { it.id == row[2] })!!.copy( enabled = ((row[0] as String) == "true"), displayText = row[1] as String ) - registry.actionSettings.put(copy.id, copy) +// registry.actionSettings.put(copy.id, copy) } } diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt index 46ae3ee6..bc335d59 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt @@ -40,15 +40,20 @@ data class AppSettingsState( ) : PersistentStateComponent { private var onSettingsLoadedListeners = mutableListOf<() -> Unit>() - val editorActions = ActionSettingsRegistry() - val fileActions = ActionSettingsRegistry() + val editorActions: ActionSettingsRegistry + get() = ActionSettingsRegistry(pluginHome.resolve("editorActions").apply { mkdirs() }) + val fileActions: ActionSettingsRegistry + get() = ActionSettingsRegistry(pluginHome.resolve("fileActions").apply { mkdirs() }) private val recentCommands = mutableMapOf() fun defaultSmartModel() = smartModel.chatModel() fun defaultFastModel() = fastModel.chatModel() @JsonIgnore - override fun getState() = SimpleEnvelope(JsonUtil.toJson(this)) + override fun getState(): SimpleEnvelope { + val value = JsonUtil.toJson(this) + return SimpleEnvelope(value) + } fun getRecentCommands(id: String) = recentCommands.computeIfAbsent(id) { MRUItems() } @@ -63,10 +68,10 @@ data class AppSettingsState( XmlSerializerUtil.copyBean(fromJson, this) recentCommands.clear(); recentCommands.putAll(fromJson.recentCommands) - editorActions.actionSettings.clear(); - editorActions.actionSettings.putAll(fromJson.editorActions.actionSettings) - fileActions.actionSettings.clear(); - fileActions.actionSettings.putAll(fromJson.fileActions.actionSettings) +// editorActions.actionSettings.clear(); +// editorActions.actionSettings.putAll(fromJson.editorActions.actionSettings) +// fileActions.actionSettings.clear(); +// fileActions.actionSettings.putAll(fromJson.fileActions.actionSettings) notifySettingsLoaded() } @@ -78,6 +83,57 @@ data class AppSettingsState( onSettingsLoadedListeners.forEach { it() } } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AppSettingsState + + if (temperature != other.temperature) return false + if (smartModel != other.smartModel) return false + if (fastModel != other.fastModel) return false + if (listeningPort != other.listeningPort) return false + if (listeningEndpoint != other.listeningEndpoint) return false + if (humanLanguage != other.humanLanguage) return false + if (apiThreads != other.apiThreads) return false + if (apiBase != other.apiBase) return false + if (apiKey != other.apiKey) return false + if (modalTasks != other.modalTasks) return false + if (suppressErrors != other.suppressErrors) return false + if (apiLog != other.apiLog) return false + if (devActions != other.devActions) return false + if (editRequests != other.editRequests) return false + if (pluginHome != other.pluginHome) return false + if (editorActions != other.editorActions) return false + if (fileActions != other.fileActions) return false + if (recentCommands != other.recentCommands) return false + + return true + } + + override fun hashCode(): Int { + var result = temperature.hashCode() + result = 31 * result + smartModel.hashCode() + result = 31 * result + fastModel.hashCode() + result = 31 * result + listeningPort + result = 31 * result + listeningEndpoint.hashCode() + result = 31 * result + humanLanguage.hashCode() + result = 31 * result + apiThreads + result = 31 * result + (apiBase?.hashCode() ?: 0) + result = 31 * result + (apiKey?.hashCode() ?: 0) + result = 31 * result + modalTasks.hashCode() + result = 31 * result + suppressErrors.hashCode() + result = 31 * result + apiLog.hashCode() + result = 31 * result + devActions.hashCode() + result = 31 * result + editRequests.hashCode() + result = 31 * result + pluginHome.hashCode() + result = 31 * result + editorActions.hashCode() + result = 31 * result + fileActions.hashCode() + result = 31 * result + recentCommands.hashCode() + return result + } + + companion object { var auxiliaryLog: File? = null diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt index 7d0aed9c..0951021d 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/util/UITools.kt @@ -31,7 +31,6 @@ import com.intellij.util.ui.FormBuilder import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.exceptions.ModerationException import com.simiacryptus.jopenai.models.APIProvider -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent import org.jdesktop.swingx.JXButton import org.slf4j.LoggerFactory import java.awt.* @@ -318,7 +317,7 @@ object UITools { "java.lang.String" -> if (uiVal is JTextComponent) { uiVal.text = settingsVal.toString() } else if (uiVal is ComboBox<*>) { - uiVal.item = settingsVal.toString() + (uiVal as ComboBox).item = settingsVal.toString() } "int", "java.lang.Integer" -> if (uiVal is JTextComponent) { @@ -340,7 +339,7 @@ object UITools { } else -> if (uiVal is ComboBox<*>) { - uiVal.item = settingsVal.toString() + (uiVal as ComboBox).item = settingsVal.toString() } } } catch (e: Throwable) {