diff --git a/README.md b/README.md index 1f34edd1..d96f3f8c 100644 --- a/README.md +++ b/README.md @@ -76,18 +76,18 @@ Maven: com.simiacryptus skyenet-webui - 1.1.4 + 1.1.5 ``` Gradle: ```groovy -implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.1.4' +implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.1.5' ``` ```kotlin -implementation("com.simiacryptus:skyenet:1.1.4") +implementation("com.simiacryptus:skyenet:1.1.5") ``` ### 🌟 To Use diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 96c43774..2058c0ae 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -33,7 +33,7 @@ val hsqldb_version = "2.7.2" dependencies { - implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.1.4") + implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.1.5") implementation(group = "org.hsqldb", name = "hsqldb", version = hsqldb_version) implementation("org.apache.commons:commons-text:1.11.0") diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt index 7275f62d..2496c789 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ActorSystem.kt @@ -7,11 +7,19 @@ import com.simiacryptus.skyenet.core.platform.User open class ActorSystem>( val actors: Map>, + dataStorage: StorageInterface, + user: User?, + session: Session +) : PoolSystem(dataStorage, user, session) { + + fun getActor(actor: T) = actors.get(actor.name)!! +} + +open class PoolSystem( val dataStorage: StorageInterface, val user: User?, val session: Session ) { protected val pool by lazy { ApplicationServices.clientManager.getPool(session, user) } - fun getActor(actor: T) = actors.get(actor.name)!! } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt index 22399203..528edad9 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/BaseActor.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.ChatModels diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt index e9099a2f..4a0c9a6b 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt @@ -1,9 +1,9 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel.* +import com.simiacryptus.jopenai.models.ApiModel.* import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.describe.AbbrevWhitelistTSDescriber import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.describe.TypeDescriber import com.simiacryptus.jopenai.models.ChatModels @@ -16,19 +16,24 @@ import java.util.* import javax.script.ScriptException import kotlin.reflect.KClass +private const val TT = "`"+"`"+"`" +typealias CodeInterceptor = (String) -> String + + open class CodingActor( val interpreterClass: KClass, val symbols: Map = mapOf(), - val describer: TypeDescriber = AbbrevWhitelistYamlDescriber( + val describer: TypeDescriber = AbbrevWhitelistTSDescriber( "com.simiacryptus", "com.github.simiacryptus" ), name: String? = interpreterClass.simpleName, val details: String? = null, - model: OpenAITextModel, + model: OpenAITextModel = OpenAIModels.GPT4o, val fallbackModel: ChatModels = OpenAIModels.GPT4o, temperature: Double = 0.1, - val runtimeSymbols: Map = mapOf() + val runtimeSymbols: Map = mapOf(), + var codeInterceptor: CodeInterceptor = { it } ) : BaseActor( prompt = "", name = name, @@ -67,43 +72,45 @@ open class CodingActor( get() { val formatInstructions = if (evalFormat) """Code should be structured as appropriately parameterized function(s) - |with the final line invoking the function with the appropriate request parameters.""" else "" + with the final line invoking the function with the appropriate request parameters.""" else "" return if (symbols.isNotEmpty()) { """ - |You are a coding assistant allows users actions to be enacted using $language and the script context. - |Your role is to translate natural language instructions into code as well as interpret the results and converse with the user. - |Use ``` code blocks labeled with $language where appropriate. (i.e. ```$language) - |Each response should have EXACTLY ONE code block. Do not use inline blocks. - |$formatInstructions - | - |Defined symbols include ${symbols.keys.joinToString(", ")} described below: - | - |```${this.describer.markupLanguage} - |${this.apiDescription} - |``` - | - |THESE VARIABLES ARE READ-ONLY: ${symbols.keys.joinToString(", ")} - |They are already defined for you. - | - |${details ?: ""} - |""".trimMargin().trim() - } else """ - |You are a coding assistant allowing users actions to be enacted using $language and the script context. - |Your role is to translate natural language instructions into code as well as interpret the results and converse with the user. - |Use ``` code blocks labeled with $language where appropriate. (i.e. ```$language) - |Each response should have EXACTLY ONE code block. Do not use inline blocks. - |$formatInstructions - | - |${details ?: ""} - |""".trimMargin().trim() +You are a coding assistant allows users actions to be enacted using $language and the script context. +Your role is to translate natural language instructions into code as well as interpret the results and converse with the user. +Use $TT code blocks labeled with $language where appropriate. (i.e. ${TT}$language) +Each response should have EXACTLY ONE code block. Do not use inline blocks. +$formatInstructions + +Defined symbols include ${symbols.keys.joinToString(", ")} described below: + +$TT${this.describer.markupLanguage} +${this.apiDescription} +${TT} + +THESE VARIABLES ARE READ-ONLY: ${symbols.keys.joinToString(", ")} +They are already defined for you. + +${details ?: ""} +""".trim() + } else """ +You are a coding assistant allowing users actions to be enacted using $language and the script context. +Your role is to translate natural language instructions into code as well as interpret the results and converse with the user. +Use $TT code blocks labeled with $language where appropriate. (i.e. ${TT}$language) +Each response should have EXACTLY ONE code block. Do not use inline blocks. +$formatInstructions + +${details ?: ""} +""".trim() } open val apiDescription: String get() = this.symbols.map { (name, utilityObj) -> + val describe = this.describer.describe(utilityObj.javaClass) + log.info("Describing $name (${utilityObj.javaClass}) in ${describe.length} characters") """ - |$name: - | ${this.describer.describe(utilityObj.javaClass).indent(" ")} - |""".trimMargin().trim() + $name: + ${describe.indent(" ")} + """.trimMargin().trim() }.joinToString("\n") @@ -123,7 +130,7 @@ open class CodingActor( } if (questions.codePrefix.isNotBlank()) { chatMessages = (chatMessages.dropLast(1) + listOf( - ChatMessage(Role.assistant, "Code Prefix:\n```\n${questions.codePrefix}\n```".toContentList()) + ChatMessage(Role.assistant, "Code Prefix:\n$TT\n${questions.codePrefix}\n${TT}".toContentList()) ) + chatMessages.last()).toTypedArray() } return chatMessages @@ -156,7 +163,7 @@ open class CodingActor( val respondWithCode = fixCommand(api, result.code, ex, *messages, model = model) val blocks = extractTextBlocks(respondWithCode) val renderedResponse = getRenderedResponse(blocks) - val codedInstruction = getCode(language, blocks) + val codedInstruction = codeInterceptor(getCode(language, blocks)) log.debug("Response: \n\t${renderedResponse.replace("\n", "\n\t", false)}".trimMargin()) log.debug("New Code: \n\t${codedInstruction.replace("\n", "\n\t", false)}".trimMargin()) result = CodeResultImpl( @@ -175,7 +182,7 @@ open class CodingActor( log.debug("Running $code") OutputInterceptor.clearGlobalOutput() val result = try { - interpreter.run((prefix + "\n" + code).sortCode()) + interpreter.run((prefix + "\n" + codeInterceptor(code)).sortCode()) } catch (e: Exception) { when { e is FailedToImplementException -> throw e @@ -247,14 +254,14 @@ open class CodingActor( try { val codeBlocks = extractTextBlocks(chat(api, request, model)) val renderedResponse = getRenderedResponse(codeBlocks) - val codedInstruction = getCode(language, codeBlocks) + val codedInstruction = codeInterceptor(getCode(language, codeBlocks)) log.debug("Response: \n\t${renderedResponse.replace("\n", "\n\t", false)}".trimMargin()) log.debug("New Code: \n\t${codedInstruction.replace("\n", "\n\t", false)}".trimMargin()) var workingCode = codedInstruction var workingRenderedResponse = renderedResponse for (fixAttempt in 0..input.fixIterations) { try { - val validate = interpreter.validate((input.codePrefix + "\n" + workingCode).sortCode()) + val validate = interpreter.validate((input.codePrefix + "\n" + codeInterceptor(workingCode)).sortCode()) if (validate != null) throw validate log.debug("Validation succeeded") _status = CodeResult.Status.Success @@ -264,12 +271,12 @@ open class CodingActor( throw if (ex is FailedToImplementException) ex else FailedToImplementException( cause = ex, message = """ - |**ERROR** - | - |```text - |${ex.stackTraceToString()} - |``` - |""".trimMargin().trim(), +**ERROR** + | +${TT}text +${ex.stackTraceToString()} +${TT} +""".trim(), language = language, code = workingCode, prefix = input.codePrefix @@ -279,7 +286,7 @@ open class CodingActor( val respondWithCode = fixCommand(api, workingCode, ex, *messages, model = model) val codeBlocks = extractTextBlocks(respondWithCode) workingRenderedResponse = getRenderedResponse(codeBlocks) - workingCode = getCode(language, codeBlocks) + workingCode = codeInterceptor(getCode(language, codeBlocks)) log.debug( "Response: \n\t${ workingRenderedResponse.replace( @@ -324,22 +331,22 @@ open class CodingActor( ChatMessage( Role.assistant, """ - |```${language.lowercase()} - |${previousCode.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }} - |``` - |""".trimMargin().trim().toContentList() +$TT${language.lowercase()} +${previousCode.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }} +${TT} +""".trim().toContentList() ), ChatMessage( Role.system, """ - |The previous code failed with the following error: - | - |``` - |${error.message?.trim() ?: "".let { /*escapeHtml4*/(it)/*.indent(" ")*/ }} - |``` - | - |Correct the code and try again. - |""".trimMargin().trim().toContentList() +The previous code failed with the following error: + +$TT +${error.message?.trim() ?: "".let { /*escapeHtml4*/(it)/*.indent(" ")*/ }} +${TT} + +Correct the code and try again. +""".trim().toContentList() ) ) ) @@ -361,7 +368,8 @@ open class CodingActor( model = model, fallbackModel = fallbackModel, temperature = temperature, - runtimeSymbols = runtimeSymbols + runtimeSymbols = runtimeSymbols, + codeInterceptor = codeInterceptor ) companion object { @@ -370,7 +378,7 @@ open class CodingActor( fun String.indent(indent: String = " ") = this.replace("\n", "\n$indent") fun extractTextBlocks(response: String): List> { - val codeBlockRegex = Regex("(?s)```(.*?)\\n(.*?)```") + val codeBlockRegex = Regex("(?s)$TT(.*?)\\n(.*?)${TT}") val languageRegex = Regex("([a-zA-Z0-9-_]+)") val result = mutableListOf>() @@ -407,9 +415,9 @@ open class CodingActor( fun getRenderedResponse(respondWithCode: List>, defaultLanguage: String = "") = respondWithCode.joinToString("\n") { when (it.first) { - "code" -> "```$defaultLanguage\n${it.second.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }}\n```" + "code" -> "$TT$defaultLanguage\n${it.second.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }}\n${TT}" "text" -> it.second.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }.toString() - else -> "```${it.first}\n${it.second.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }}\n```" + else -> "$TT${it.first}\n${it.second.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }}\n${TT}" } } @@ -426,7 +434,7 @@ open class CodingActor( fun String.sortCode(bodyWrapper: (String) -> String = { it }): String { val (imports, otherCode) = this.split("\n").partition { it.trim().startsWith("import ") } - return imports.distinct().sorted().joinToString("\n") + "\n\n" + bodyWrapper(otherCode.joinToString("\n")) + return imports.map { it.trim() }.distinct().sorted().joinToString("\n") + "\n\n" + bodyWrapper(otherCode.joinToString("\n")) } fun String.camelCase(locale: Locale = Locale.getDefault()): String { @@ -481,11 +489,11 @@ open class CodingActor( fun errorMessage(ex: ScriptException, code: String) = try { """ - |```text + |${TT}text |${ex.message ?: ""} at line ${ex.lineNumber} column ${ex.columnNumber} | ${if (ex.lineNumber > 0) code.split("\n")[ex.lineNumber - 1] else ""} | ${if (ex.columnNumber > 0) " ".repeat(ex.columnNumber - 1) + "^" else ""} - |``` + |${TT} """.trimMargin().trim() } catch (_: Exception) { ex.message ?: "" @@ -499,4 +507,4 @@ open class CodingActor( val code: String? = null, val prefix: String? = null, ) : RuntimeException(message, cause) -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageActor.kt index 16ae1262..7b4f920f 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageActor.kt @@ -1,10 +1,9 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.ApiModel.ChatMessage -import com.simiacryptus.jopenai.ApiModel.ImageGenerationRequest -import com.simiacryptus.jopenai.GPT4Tokenizer +import com.simiacryptus.jopenai.models.ApiModel +import com.simiacryptus.jopenai.models.ApiModel.ChatMessage +import com.simiacryptus.jopenai.models.ApiModel.ImageGenerationRequest import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.ImageModels @@ -66,7 +65,7 @@ open class ImageActor( override fun respond(input: List, api: API, vararg messages: ChatMessage): ImageResponse { var text = response(*messages, api = api).choices.first().message?.content ?: throw RuntimeException("No response") - while (imageModel.maxPrompt <= text.length) { + while (imageModel.maxPrompt <= text.length && null != openAI) { text = response( *listOf( messages.toList(), @@ -79,7 +78,7 @@ open class ImageActor( api = api ).choices.first().message?.content ?: throw RuntimeException("No response") } - return ImageResponseImpl(text, api = api) + return ImageResponseImpl(text, api = this.openAI!!) } override fun withModel(model: ChatModels): ImageActor = ImageActor( @@ -92,9 +91,11 @@ open class ImageActor( height = height, ) + var openAI: OpenAIClient? = null + fun setImageAPI(openAI: OpenAIClient): ImageActor { + this.openAI = openAI + return this + } + } -interface ImageResponse { - val text: String - val image: BufferedImage -} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageResponse.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageResponse.kt new file mode 100644 index 00000000..19703f0c --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ImageResponse.kt @@ -0,0 +1,8 @@ +package com.simiacryptus.skyenet.core.actors + +import java.awt.image.BufferedImage + +interface ImageResponse { + val text: String + val image: BufferedImage +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt index 602319f8..944e69a8 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedActor.kt @@ -1,16 +1,17 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.describe.TypeDescriber +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAIModels import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.skyenet.core.util.MultiExeption +import com.simiacryptus.util.JsonUtil +import org.slf4j.LoggerFactory import java.util.function.Function open class ParsedActor( @@ -27,6 +28,7 @@ open class ParsedActor( ) { override val includeMethods: Boolean get() = false }, + var parserPrompt: String? = null, ) : BaseActor, ParsedResponse>( prompt = prompt, name = name, @@ -55,7 +57,7 @@ open class ParsedActor( ParsedResponse(resultClass!!) { override val text = response(*messages, api = api).choices.first().message?.content ?: throw RuntimeException("No response") - private val _obj: T by lazy { getParser(api).apply(text) } + private val _obj: T by lazy { getParser(api, parserPrompt).apply(text) } override val obj get() = _obj } @@ -83,7 +85,10 @@ open class ParsedActor( ApiModel.ChatRequest( messages = listOf( ApiModel.ChatMessage(role = ApiModel.Role.system, content = prompt.toContentList()), - ApiModel.ChatMessage(role = ApiModel.Role.user, content = "The user message to parse:\n\n$input".toContentList()), + ApiModel.ChatMessage( + role = ApiModel.Role.user, + content = "The user message to parse:\n\n$input".toContentList() + ), ), temperature = temperature, model = parsingModel.modelName, @@ -106,7 +111,14 @@ open class ParsedActor( if (endIndex > 7) { contentUnwrapped = contentUnwrapped.substring(7, endIndex) } else { - throw RuntimeException("Failed to parse response: ${contentUnwrapped.replace("\n", "\n ")}") + throw RuntimeException( + "Failed to parse response: ${ + contentUnwrapped.replace( + "\n", + "\n " + ) + }" + ) } } @@ -143,6 +155,8 @@ open class ParsedActor( ) companion object { - private val log = org.slf4j.LoggerFactory.getLogger(ParsedActor::class.java) + private val log = LoggerFactory.getLogger(ParsedActor::class.java) } + + } \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedResponse.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedResponse.kt index 54b4ec29..d943132a 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedResponse.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/ParsedResponse.kt @@ -4,4 +4,21 @@ abstract class ParsedResponse(val clazz: Class) { abstract val text: String abstract val obj: T override fun toString() = text + open fun map(cls: Class, fn: (T) -> V): ParsedResponse = MappedResponse(cls, this.clazz, fn, this) +} + +class MappedResponse( + clazz: Class, + private val cls: Class, + private val fn: (F) -> T, + private val inner: ParsedResponse +) : ParsedResponse(clazz) { + override val text: String + get() = inner.text + override val obj: T + get() = fn(inner.obj) + + override fun map(cls: Class, fn: (T) -> V): ParsedResponse { + return MappedResponse(cls, this.clazz, fn, this) + } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SimpleActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SimpleActor.kt index ed81625d..86899cbb 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SimpleActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SimpleActor.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SpeechResponse.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SpeechResponse.kt new file mode 100644 index 00000000..b555568d --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/SpeechResponse.kt @@ -0,0 +1,5 @@ +package com.simiacryptus.skyenet.core.actors + +interface SpeechResponse { + val mp3data: ByteArray? +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/TextToSpeechActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/TextToSpeechActor.kt index f8504482..97bce168 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/TextToSpeechActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/TextToSpeechActor.kt @@ -1,9 +1,8 @@ package com.simiacryptus.skyenet.core.actors import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.ApiModel.ChatMessage -import com.simiacryptus.jopenai.GPT4Tokenizer +import com.simiacryptus.jopenai.models.ApiModel +import com.simiacryptus.jopenai.models.ApiModel.ChatMessage import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.AudioModels import com.simiacryptus.jopenai.models.ChatModels @@ -20,6 +19,12 @@ open class TextToSpeechActor( name = name, model = models, ) { + var openAI: OpenAIClient? = null + fun setOpenAI(openAI: OpenAIClient): TextToSpeechActor { + this.openAI = openAI + return this + } + override fun chatMessages(questions: List) = questions.map { ChatMessage( role = ApiModel.Role.user, @@ -50,14 +55,11 @@ open class TextToSpeechActor( override fun respond(input: List, api: API, vararg messages: ChatMessage) = SpeechResponseImpl( messages.joinToString("\n") { it.content?.joinToString("\n") { it.text ?: "" } ?: "" }, - api = api + api = this.openAI ?: throw RuntimeException("OpenAI client not set") ) - override fun withModel(model: ChatModels) = this -} - -interface SpeechResponse { - val mp3data: ByteArray? + override fun withModel(model: ChatModels) = TextToSpeechActor(name, audioModel, voice, speed, model) + .also { it.openAI = this.openAI } } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt index 85597c16..9f80a256 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt @@ -3,7 +3,7 @@ package com.simiacryptus.skyenet.core.platform import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.google.common.util.concurrent.AtomicDouble -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.APIProvider import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAIModel diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt index 606aaa01..af1418d5 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ClientManager.kt @@ -4,6 +4,7 @@ import com.google.common.util.concurrent.ListeningScheduledExecutorService import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.ThreadFactoryBuilder import com.simiacryptus.jopenai.ChatClient +import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.APIProvider import com.simiacryptus.jopenai.util.ClientUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices.dataStorageFactory @@ -17,19 +18,28 @@ open class ClientManager { private data class SessionKey(val session: Session, val user: User?) - private val clientCache = mutableMapOf() - private val poolCache = mutableMapOf() - private val scheduledPoolCache = mutableMapOf() - fun getClient( + private val chatCache = mutableMapOf() + fun getChatClient( session: Session, user: User?, ): ChatClient { log.debug("Fetching client for session: {}, user: {}", session, user) val key = SessionKey(session, user) - return clientCache.getOrPut(key) { createClient(session, user)!! } + return chatCache.getOrPut(key) { createChatClient(session, user)!! } + } + + private val openAICache = mutableMapOf() + fun getOpenAIClient( + session: Session, + user: User?, + ): OpenAIClient { + log.debug("Fetching client for session: {}, user: {}", session, user) + val key = SessionKey(session, user) + return openAICache.getOrPut(key) { createOpenAIClient(session, user)!! } } + private val poolCache = mutableMapOf() protected open fun createPool(session: Session, user: User?) = ThreadPoolExecutor( 0, Integer.MAX_VALUE, @@ -38,7 +48,7 @@ open class ClientManager { RecordingThreadFactory(session, user) ) - /*createScheduledPool*/ + private val scheduledPoolCache = mutableMapOf() protected open fun createScheduledPool(session: Session, user: User?, dataStorage: StorageInterface?) = MoreExecutors.listeningDecorator(ScheduledThreadPoolExecutor(1)) @@ -80,7 +90,7 @@ open class ClientManager { } } - protected open fun createClient( + protected open fun createChatClient( session: Session, user: User?, ): ChatClient? { @@ -136,6 +146,62 @@ open class ClientManager { })!! } + protected open fun createOpenAIClient( + session: Session, + user: User?, + ): OpenAIClient? { + log.debug("Creating client for session: {}, user: {}", session, user) + val sessionDir = dataStorageFactory(dataStorageRoot).getDataDir(user, session).apply { mkdirs() } + if (user != null) { + val userSettings = userSettingsManager.getUserSettings(user) + val userApi = + if (userSettings.apiKeys.isNotEmpty()) { + /* + MonitoredClient( + key = userSettings.apiKeys, + apiBase = userSettings.apiBase, + logfile = sessionDir.resolve("openai.log"), + session = session, + user = user, + workPool = getPool(session, user), + )*/ + OpenAIClient( + key = userSettings.apiKeys, + apiBase = userSettings.apiBase, + workPool = getPool(session, user), + ).apply { + this.session = session + this.user = user + logStreams += sessionDir.resolve("openai.log").outputStream().buffered() + } + } else null + if (userApi != null) return userApi + } + val canUseGlobalKey = ApplicationServices.authorizationManager.isAuthorized( + null, user, OperationType.GlobalKey + ) + if (!canUseGlobalKey) throw RuntimeException("No API key") + return (if (ClientUtil.keyMap.isNotEmpty()) { + /*MonitoredClient( + key = ClientUtil.keyMap.mapKeys { APIProvider.valueOf(it.key) }, + logfile = sessionDir.resolve("openai.log"), + session = session, + user = user, + workPool = getPool(session, user), + )*/ + OpenAIClient( + key = ClientUtil.keyMap.mapKeys { APIProvider.valueOf(it.key) }, + workPool = getPool(session, user), + ).apply { + this.session = session + this.user = user + logStreams += sessionDir.resolve("openai.log").outputStream().buffered() + } + } else { + null + })!! + } + companion object { private val log = LoggerFactory.getLogger(ClientManager::class.java) } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt index 043bbaed..3ec0ac13 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/HSQLUsageManager.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.core.platform import com.google.common.util.concurrent.AtomicDouble -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAIModel import org.slf4j.Logger diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt index f9f9960f..f97f4a88 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/DataStorage.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.core.platform.file -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.StorageInterface import com.simiacryptus.skyenet.core.platform.StorageInterface.Companion.validateSessionId diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt index 708ccf01..ebd0f306 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.core.platform.file import com.simiacryptus.jopenai.models.* -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.UsageInterface import com.simiacryptus.skyenet.core.platform.UsageInterface.* @@ -49,21 +49,21 @@ open class UsageManager(val root: File) : UsageInterface { Session(sessionId), User(email = user), modelEnum, - com.simiacryptus.jopenai.ApiModel.Usage(prompt_tokens = value.toLong()) + ApiModel.Usage(prompt_tokens = value.toLong()) ) "output" -> incrementUsage( Session(sessionId), User(email = user), modelEnum, - com.simiacryptus.jopenai.ApiModel.Usage(completion_tokens = value.toLong()) + ApiModel.Usage(completion_tokens = value.toLong()) ) "cost" -> incrementUsage( session = Session(sessionId = sessionId), user = User(email = user), model = modelEnum, - tokens = com.simiacryptus.jopenai.ApiModel.Usage(cost = value.toDouble()) + tokens = ApiModel.Usage(cost = value.toDouble()) ) else -> throw RuntimeException("Unknown direction $direction") @@ -135,7 +135,7 @@ open class UsageManager(val root: File) : UsageInterface { session: Session, apiKey: String?, model: OpenAIModel, - tokens: com.simiacryptus.jopenai.ApiModel.Usage + tokens: ApiModel.Usage ) { usagePerSession.computeIfAbsent(session) { UsageCounters() } .tokensPerModel.computeIfAbsent(UsageKey(session, apiKey, model)) { UsageValues() } @@ -158,7 +158,7 @@ open class UsageManager(val root: File) : UsageInterface { } } - override fun getUserUsageSummary(apiKey: String): Map { + override fun getUserUsageSummary(apiKey: String): Map { return sessionsByUser[apiKey]?.flatMap { sessionId -> val usage = usagePerSession[sessionId] usage?.tokensPerModel?.entries?.map { (model, counter) -> @@ -166,7 +166,7 @@ open class UsageManager(val root: File) : UsageInterface { } ?: emptyList() }?.groupBy { it.first }?.mapValues { it.value.map { it.second }.reduce { a, b -> - com.simiacryptus.jopenai.ApiModel.Usage( + ApiModel.Usage( prompt_tokens = a.prompt_tokens + b.prompt_tokens, completion_tokens = a.completion_tokens + b.completion_tokens, cost = (a.cost ?: 0.0) + (b.cost ?: 0.0) @@ -175,12 +175,12 @@ open class UsageManager(val root: File) : UsageInterface { } ?: emptyMap() } - override fun getSessionUsageSummary(session: Session): Map = + override fun getSessionUsageSummary(session: Session): Map = usagePerSession[session]?.tokensPerModel?.entries?.map { (model, counter) -> model.model to counter.toUsage() }?.groupBy { it.first }?.mapValues { it.value.map { it.second }.reduce { a, b -> - com.simiacryptus.jopenai.ApiModel.Usage( + ApiModel.Usage( prompt_tokens = a.prompt_tokens + b.prompt_tokens, completion_tokens = a.completion_tokens + b.completion_tokens, cost = (a.cost ?: 0.0) + (b.cost ?: 0.0) diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UserSettingsManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UserSettingsManager.kt index 7ef0dc51..de50a35f 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UserSettingsManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UserSettingsManager.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.core.platform.file -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.ApplicationServicesConfig.dataStorageRoot import com.simiacryptus.skyenet.core.platform.User diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/StorageInterfaceTest.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/StorageInterfaceTest.kt index 30bf2640..383c5c99 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/StorageInterfaceTest.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/StorageInterfaceTest.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.core.platform.test -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.StorageInterface import com.simiacryptus.skyenet.core.platform.User diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/UsageTest.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/UsageTest.kt index 3b31304a..f10016e5 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/UsageTest.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/test/UsageTest.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.core.platform.test -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.OpenAIModels import com.simiacryptus.skyenet.core.platform.StorageInterface diff --git a/core/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/CommonRoot.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/CommonRoot.kt similarity index 94% rename from core/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/CommonRoot.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/core/util/CommonRoot.kt index a727309f..c2ef214c 100644 --- a/core/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/CommonRoot.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/CommonRoot.kt @@ -1,4 +1,4 @@ -package com.github.simiacryptus.aicoder.actions.generic +package com.simiacryptus.skyenet.core.util import java.io.File import java.nio.file.Path diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/FunctionWrapper.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/FunctionWrapper.kt index b2167484..dcd4c29b 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/FunctionWrapper.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/FunctionWrapper.kt @@ -6,7 +6,7 @@ import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.models.EmbeddingModels import com.simiacryptus.jopenai.models.ImageModels import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import java.awt.image.BufferedImage import java.io.Closeable import java.io.File diff --git a/core/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/GetModuleRootForFile.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/GetModuleRootForFile.kt similarity index 83% rename from core/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/GetModuleRootForFile.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/core/util/GetModuleRootForFile.kt index e239392e..7bf3b458 100644 --- a/core/src/main/kotlin/com/github/simiacryptus/aicoder/actions/generic/GetModuleRootForFile.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/GetModuleRootForFile.kt @@ -1,4 +1,4 @@ -package com.github.simiacryptus.aicoder.actions.generic +package com.simiacryptus.skyenet.core.util import java.io.File diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/MultiExeption.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/MultiExeption.kt similarity index 62% rename from core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/MultiExeption.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/core/util/MultiExeption.kt index 02c61be5..ece5ae42 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/MultiExeption.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/util/MultiExeption.kt @@ -1,6 +1,4 @@ -package com.simiacryptus.skyenet.core.actors - -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent +package com.simiacryptus.skyenet.core.util class MultiExeption(exceptions: Collection) : RuntimeException( exceptions.joinToString("\n\n") { "```text\n${/*escapeHtml4*/(it.stackTraceToString())/*.indent(" ")*/}\n```" } diff --git a/docs/path_structure.md b/docs/path_structure.md new file mode 100644 index 00000000..77253e37 --- /dev/null +++ b/docs/path_structure.md @@ -0,0 +1,54 @@ +Based on the provided code, here's a document detailing the servlet path layout for the application: + +# Servlet Path Layout + +The application uses a hierarchical structure for its servlet paths, with different functionalities mapped to specific URLs. Below is a breakdown of the main servlet paths and their purposes: + +## Root Level Paths + +1. `/` - Welcome page (WelcomeServlet) +2. `/api` - API endpoint (WelcomeServlet) +3. `/logout` - Logout functionality (LogoutServlet) +4. `/proxy` - Proxy HTTP requests (ProxyHttpServlet) +5. `/userInfo` - User information (UserInfoServlet) +6. `/userSettings` - User settings (UserSettingsServlet) +7. `/usage` - Usage information (UsageServlet) +8. `/apiKeys` - API key management (ApiKeyServlet) +9. `/stats` - Server statistics (StatisticsServlet) + +## Authentication + +* `/oauth2callback` - OAuth2 callback URL for Google authentication + +## Application-Specific Paths + +For each child web application defined in `childWebApps`, a new path is created. These paths are dynamically generated based on the `ApplicationServer` instances. + +## Common Paths for Each Application + +Within each application-specific path, the following common servlets are typically available: + +1. `/` - Default servlet +2. `/ws` - WebSocket endpoint +3. `/newSession` - Create a new session +4. `/appInfo` - Application information +5. `/userInfo` - User information (may overlap with root level) +6. `/usage` - Usage information (may overlap with root level) +7. `/fileIndex/*` - File indexing +8. `/fileZip` - File compression +9. `/sessions` - Session listing +10. `/settings` - Session settings +11. `/threads` - Session threads +12. `/share` - Session sharing +13. `/delete` - Delete session +14. `/cancel` - Cancel threads + +## Notes + +* The exact paths may vary depending on the specific implementation of each `ApplicationServer` instance. +* Some paths (like `/userInfo` and `/usage`) appear both at the root level and within application-specific contexts. The application-specific versions may provide more tailored information. +* The application uses filters for Cross-Origin Resource Sharing (CORS) and authentication/authorization checks. +* WebSocket support is configured for real-time communication. +* The server is set up to handle both HTTP and HTTPS connections, with the latter being used when running in server mode. + +This layout provides a comprehensive structure for handling various aspects of the application, from user management and authentication to application-specific functionalities and session handling. diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/planning_agent.md b/docs/planning_agent.md similarity index 100% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/planning_agent.md rename to docs/planning_agent.md diff --git a/docs/planning_agent_bespoke.html b/docs/planning_agent_bespoke.html new file mode 100644 index 00000000..2c7ca62f --- /dev/null +++ b/docs/planning_agent_bespoke.html @@ -0,0 +1,223 @@ + + + + + + Plan Feature User and Developer Guide + + + + + +
+
+

Plan Feature User and Developer Guide

+
+ +
+

Introduction

+

The Plan feature is a sophisticated system designed to break down high-level user requests into manageable, actionable tasks. It leverages OpenAI's capabilities to automate task planning, execution, and management, ensuring efficient workflow and seamless integration with existing systems.

+
+ +
+

Features Overview

+
    +
  • Dynamic Task Breakdown
  • +
  • Task Execution
  • +
  • Dependency Management
  • +
  • Asynchronous Processing
  • +
  • Customizable Settings
  • +
  • Extensible Architecture
  • +
+
+ +
+

User Guide

+

Accessing the Plan Feature

+

Navigate to the application interface where task planning and management functionalities are exposed.

+
+ +
+

Creating and Managing Plans

+
    +
  1. Initiate a New Plan
  2. +
  3. Task Breakdown
  4. +
  5. Review Generated Tasks
  6. +
  7. Execute the Plan
  8. +
+
+ +
+

Understanding Task Dependencies

+
    +
  • Visual Representation using Mermaid
  • +
  • Interpreting the Graph
  • +
+
+ +
+

Viewing Execution Logs

+
    +
  • Accessing Logs through UI
  • +
  • Log Files stored in .logs directory
  • +
+
+ +
+

Developer Guide

+

Architecture Overview

+
    +
  • TaskType
  • +
  • PlanSettings
  • +
  • PlanCoordinator
  • +
  • PlanUtil
  • +
  • PlanningTask
  • +
  • AbstractTask
  • +
+
+ +
+

Task Types

+
    +
  1. CommandAutoFixTask
  2. +
  3. InquiryTask
  4. +
  5. FileModificationTask
  6. +
  7. RunShellCommandTask
  8. +
  9. RefactorTask
  10. +
  11. SecurityAuditTask
  12. +
  13. CodeOptimizationTask
  14. +
  15. CodeReviewTask
  16. +
  17. DocumentationTask
  18. +
  19. TestGenerationTask
  20. +
+
+ +
+

Adding New Task Types

+

+val NewTaskType = TaskType("NewTaskType")
+
+class NewTaskTypeTask(settings: PlanSettings, task: PlanningTask.PlanTask) : AbstractTask(settings, task) {
+    override fun promptSegment(): String {
+        return "Description for NewTaskType"
+    }
+
+    override fun run(
+        agent: PlanCoordinator,
+        taskId: String,
+        userMessage: String,
+        plan: TaskBreakdownInterface,
+        planProcessingState: PlanProcessingState,
+        task: SessionTask,
+        api: API
+    ) {
+        // Implementation for task execution
+    }
+}
+        
+
+ +
+

Configuring Plan Settings

+
    +
  • Default and Parsing Models
  • +
  • Command Environment
  • +
  • Temperature and Budget
  • +
  • Task-Specific Settings
  • +
  • Auto-Fix and Blocking Behavior
  • +
+
+ +
+

Extending PlanCoordinator

+
    +
  • Initialization
  • +
  • Task Execution
  • +
  • Logging and Error Handling
  • +
+
+ +
+

Utilizing PlanUtil

+
    +
  • Diagram Generation
  • +
  • Rendering Plans
  • +
  • Plan Filtering
  • +
+
+ +
+

Handling Asynchronous Task Execution

+
    +
  • Concurrency Management
  • +
  • Task Dependencies
  • +
  • Error Propagation
  • +
+
+ +
+

Customizing Task Execution

+
    +
  1. Overriding the `run` Method
  2. +
  3. Integrating with External Systems
  4. +
  5. Enhancing the Execution Flow
  6. +
+
+ +
+

Best Practices

+
    +
  • Modular Design
  • +
  • Robust Error Handling
  • +
  • Efficient Dependency Management
  • +
  • Logging and Monitoring
  • +
  • Security Considerations
  • +
+
+ +
+

Troubleshooting

+
    +
  1. Circular Dependency Detected
  2. +
  3. Unknown Task Type Error
  4. +
  5. API Errors
  6. +
  7. File Reading Errors
  8. +
  9. Task Execution Failures
  10. +
+
+ +
+

Frequently Asked Questions (FAQs)

+

Q1: How do I enable or disable specific task types?

+

Q2: Can I add custom commands for shell tasks?

+

Q3: How are task dependencies managed?

+

Q4: Is it possible to log API interactions for auditing purposes?

+

Q5: How can I visualize the task execution flow?

+
+
+ + + + + + + + + + + diff --git a/docs/planning_agent_deck.html b/docs/planning_agent_deck.html new file mode 100644 index 00000000..1bcbc3d4 --- /dev/null +++ b/docs/planning_agent_deck.html @@ -0,0 +1,221 @@ ++ + + + + + Plan Feature User and Developer Guide + + + + + + + + + +
+

Plan Feature User and Developer Guide

+
+ +
+

Introduction

+

The Plan feature is a sophisticated system designed to break down high-level user requests into manageable, actionable tasks. It leverages OpenAI's capabilities to automate task planning, execution, and management, ensuring efficient workflow and seamless integration with existing systems.

+
+ +
+

Features Overview

+
    +
  • Dynamic Task Breakdown
  • +
  • Task Execution
  • +
  • Dependency Management
  • +
  • Asynchronous Processing
  • +
  • Customizable Settings
  • +
  • Extensible Architecture
  • +
+
+ +
+

User Guide

+

Accessing the Plan Feature

+

Navigate to the application interface where task planning and management functionalities are exposed.

+
+ +
+

Creating and Managing Plans

+
    +
  1. Initiate a New Plan
  2. +
  3. Task Breakdown
  4. +
  5. Review Generated Tasks
  6. +
  7. Execute the Plan
  8. +
+
+ +
+

Understanding Task Dependencies

+
    +
  • Visual Representation using Mermaid
  • +
  • Interpreting the Graph
  • +
+
+ +
+

Viewing Execution Logs

+
    +
  • Accessing Logs through UI
  • +
  • Log Files stored in .logs directory
  • +
+
+ +
+

Developer Guide

+

Architecture Overview

+
    +
  • TaskType
  • +
  • PlanSettings
  • +
  • PlanCoordinator
  • +
  • PlanUtil
  • +
  • PlanningTask
  • +
  • AbstractTask
  • +
+
+ +
+

Task Types

+
    +
  1. CommandAutoFixTask
  2. +
  3. InquiryTask
  4. +
  5. FileModificationTask
  6. +
  7. RunShellCommandTask
  8. +
  9. RefactorTask
  10. +
+
+ +
+

Task Types (continued)

+
    +
  1. SecurityAuditTask
  2. +
  3. CodeOptimizationTask
  4. +
  5. CodeReviewTask
  6. +
  7. DocumentationTask
  8. +
  9. TestGenerationTask
  10. +
+
+ +
+

Adding New Task Types

+
    +
  1. Define the Task Type
  2. +
  3. Create the Task Class
  4. +
  5. Register the Task Type
  6. +
  7. Update PlanSettings
  8. +
+
+ +
+

Configuring Plan Settings

+
    +
  • Default and Parsing Models
  • +
  • Command Environment
  • +
  • Temperature and Budget
  • +
  • Task-Specific Settings
  • +
  • Auto-Fix and Blocking Behavior
  • +
+
+ +
+

Extending PlanCoordinator

+
    +
  • Initialization
  • +
  • Task Execution
  • +
  • Logging and Error Handling
  • +
+
+ +
+

Utilizing PlanUtil

+
    +
  • Diagram Generation
  • +
  • Rendering Plans
  • +
  • Plan Filtering
  • +
+
+ +
+

Handling Asynchronous Task Execution

+
    +
  • Concurrency Management
  • +
  • Task Dependencies
  • +
  • Error Propagation
  • +
+
+ +
+

Customizing Task Execution

+
    +
  1. Overriding the run Method
  2. +
  3. Integrating with External Systems
  4. +
  5. Enhancing the Execution Flow
  6. +
+
+ +
+

Best Practices

+
    +
  • Modular Design
  • +
  • Robust Error Handling
  • +
  • Efficient Dependency Management
  • +
  • Logging and Monitoring
  • +
  • Security Considerations
  • +
+
+ +
+

Troubleshooting

+
    +
  1. Circular Dependency Detected
  2. +
  3. Unknown Task Type Error
  4. +
  5. API Errors
  6. +
  7. File Reading Errors
  8. +
  9. Task Execution Failures
  10. +
+
+ +
+

Frequently Asked Questions (FAQs)

+
    +
  • How do I enable or disable specific task types?
  • +
  • Can I add custom commands for shell tasks?
  • +
  • How are task dependencies managed?
  • +
  • Is it possible to log API interactions for auditing purposes?
  • +
  • How can I visualize the task execution flow?
  • +
+
+ + + + + + + + + + diff --git a/docs/planning_agent_impress.html b/docs/planning_agent_impress.html new file mode 100644 index 00000000..540fc96b --- /dev/null +++ b/docs/planning_agent_impress.html @@ -0,0 +1,154 @@ + + + + + + Plan Feature User and Developer Guide + + + + +
+
+

Plan Feature User and Developer Guide

+

Welcome to the comprehensive guide for the Plan feature, designed to assist both end-users and developers.

+
+ +
+

Introduction

+

The Plan feature is a sophisticated system designed to break down high-level user requests into manageable, actionable tasks.

+
+ +
+

Features Overview

+
    +
  • Dynamic Task Breakdown
  • +
  • Task Execution
  • +
  • Dependency Management
  • +
  • Asynchronous Processing
  • +
  • Customizable Settings
  • +
  • Extensible Architecture
  • +
+
+ +
+

User Guide

+

Accessing the Plan Feature

+

Navigate to the application interface where task planning and management functionalities are exposed.

+
+ +
+

Creating and Managing Plans

+
    +
  1. Initiate a New Plan
  2. +
  3. Task Breakdown
  4. +
  5. Review Generated Tasks
  6. +
  7. Execute the Plan
  8. +
+
+ +
+

Understanding Task Dependencies

+

The Plan feature utilizes Mermaid to render task dependency graphs.

+
+ +
+

Viewing Execution Logs

+

Execution logs are automatically generated and can be accessed through the UI.

+
+ +
+

Developer Guide

+

Architecture Overview

+

The Plan feature is built around several core components: TaskType, PlanSettings, PlanCoordinator, PlanUtil, PlanningTask, and AbstractTask.

+
+ +
+

Task Types

+

Various task types are defined, including CommandAutoFixTask, InquiryTask, FileModificationTask, and more.

+
+ +
+

Adding New Task Types

+

To introduce a new task type:

+
    +
  1. Define the Task Type
  2. +
  3. Create the Task Class
  4. +
  5. Register the Task Type
  6. +
  7. Update PlanSettings
  8. +
+
+ +
+

Configuring Plan Settings

+

PlanSettings.kt allows for extensive configuration of how tasks are planned and executed.

+
+ +
+

Extending PlanCoordinator

+

PlanCoordinator.kt is pivotal in managing the lifecycle of plans and their tasks.

+
+ +
+

Utilizing PlanUtil

+

PlanUtil.kt provides utility functions essential for task management and visualization.

+
+ +
+

Handling Asynchronous Task Execution

+

The system leverages a ThreadPoolExecutor to manage task execution asynchronously.

+
+ +
+

Customizing Task Execution

+

Developers can customize how individual tasks execute by overriding methods and integrating with external systems.

+
+ +
+

Best Practices

+
    +
  • Modular Design
  • +
  • Robust Error Handling
  • +
  • Efficient Dependency Management
  • +
  • Logging and Monitoring
  • +
  • Security Considerations
  • +
+
+ +
+

Troubleshooting

+

Common issues and their solutions are provided, including circular dependencies, unknown task types, API errors, file reading errors, and task execution failures.

+
+ +
+

Frequently Asked Questions (FAQs)

+

Answers to common questions about enabling/disabling task types, adding custom commands, managing dependencies, logging API interactions, and visualizing task execution flow.

+
+
+ + + + + diff --git a/docs/planning_agent_reveal.html b/docs/planning_agent_reveal.html new file mode 100644 index 00000000..7bf55048 --- /dev/null +++ b/docs/planning_agent_reveal.html @@ -0,0 +1,153 @@ ++ + + + + + + Plan Feature User and Developer Guide + + + + + + + + + +
+
+
+

Plan Feature User and Developer Guide

+
+ +
+

Introduction

+

The Plan feature is a sophisticated system designed to break down high-level user requests into manageable, actionable tasks. It leverages OpenAI's capabilities to automate task planning, execution, and management, ensuring efficient workflow and seamless integration with existing systems.

+
+ +
+

Features Overview

+
    +
  • Dynamic Task Breakdown
  • +
  • Task Execution
  • +
  • Dependency Management
  • +
  • Asynchronous Processing
  • +
  • Customizable Settings
  • +
  • Extensible Architecture
  • +
+
+ +
+

User Guide

+
+

Accessing the Plan Feature

+

Navigate to the application interface where task planning and management functionalities are exposed.

+
+
+

Creating and Managing Plans

+
    +
  1. Initiate a New Plan
  2. +
  3. Task Breakdown
  4. +
  5. Review Generated Tasks
  6. +
  7. Execute the Plan
  8. +
+
+
+

Understanding Task Dependencies

+

The Plan feature utilizes Mermaid to render task dependency graphs.

+
+
+

Viewing Execution Logs

+

Execution logs are automatically generated and can be accessed through the UI.

+
+
+ +
+

Developer Guide

+
+

Architecture Overview

+
    +
  • TaskType
  • +
  • PlanSettings
  • +
  • PlanCoordinator
  • +
  • PlanUtil
  • +
  • PlanningTask
  • +
  • AbstractTask
  • +
+
+
+

Task Types

+
    +
  1. CommandAutoFixTask
  2. +
  3. InquiryTask
  4. +
  5. FileModificationTask
  6. +
  7. RunShellCommandTask
  8. +
  9. RefactorTask
  10. +
  11. SecurityAuditTask
  12. +
  13. CodeOptimizationTask
  14. +
  15. CodeReviewTask
  16. +
  17. DocumentationTask
  18. +
  19. TestGenerationTask
  20. +
+
+
+

Adding New Task Types

+
    +
  1. Define the Task Type
  2. +
  3. Create the Task Class
  4. +
  5. Register the Task Type
  6. +
  7. Update PlanSettings
  8. +
+
+
+ +
+

Best Practices

+
    +
  • Modular Design
  • +
  • Robust Error Handling
  • +
  • Efficient Dependency Management
  • +
  • Logging and Monitoring
  • +
  • Security Considerations
  • +
+
+ +
+

Troubleshooting

+
    +
  1. Circular Dependency Detected
  2. +
  3. Unknown Task Type Error
  4. +
  5. API Errors
  6. +
  7. File Reading Errors
  8. +
  9. Task Execution Failures
  10. +
+
+ +
+

Frequently Asked Questions (FAQs)

+

Q1: How do I enable or disable specific task types?

+

Q2: Can I add custom commands for shell tasks?

+

Q3: How are task dependencies managed?

+

Q4: Is it possible to log API interactions for auditing purposes?

+

Q5: How can I visualize the task execution flow?

+
+
+
+ + + + + + + + diff --git a/docs/project_info.md b/docs/prompts/project_info.md similarity index 100% rename from docs/project_info.md rename to docs/prompts/project_info.md diff --git a/docs/project_info_template.md b/docs/prompts/project_info_template.md similarity index 100% rename from docs/project_info_template.md rename to docs/prompts/project_info_template.md diff --git a/docs/project_info_template2.md b/docs/prompts/project_info_template2.md similarity index 100% rename from docs/project_info_template2.md rename to docs/prompts/project_info_template2.md diff --git a/docs/tabbed_ui.md b/docs/tabbed_ui.md deleted file mode 100644 index d1ad3f37..00000000 --- a/docs/tabbed_ui.md +++ /dev/null @@ -1,190 +0,0 @@ -# Tabbed UI Documentation - -## Overview - -The tabbed UI components in the codebase are specifically designed to facilitate the creation, management, and seamless -integration of tabbed interfaces within web applications. Ideal for applications requiring dynamic content switching -without page reloads, these components support various content types and are driven by user interactions or backend -processes. They are particularly useful in dashboards, multi-step forms, and settings panels. - -## Components - -### TabbedDisplay - -`TabbedDisplay` is a base class for creating tabbed displays. It manages a list of tabs, each associated with a label -and content. It provides methods to render tabs, manage tab content, and handle user interactions to switch between -tabs. - -#### Key Methods and Properties - -### Retryable - -`Retryable` extends `TabbedDisplay` to add functionality for retrying operations. It is particularly useful for -operations that might fail and need a retry mechanism, encapsulated within a tab. - -#### Key Methods - -### AgentPatterns - -`AgentPatterns` contains utility methods for displaying content in a tabbed format without directly managing the tab -state. - -#### Key Functions - -a tabbed interface, optionally splitting content into separate tasks for performance. - -### MarkdownUtil - -`MarkdownUtil` provides utilities for rendering Markdown content into HTML, including special handling for Mermaid -diagrams and tabbed displays of Markdown versus rendered HTML. - -#### Key Methods - -Markdown into HTML, with support for Mermaid diagrams and an optional tabbed interface. - -## Usage Examples - -### Example 1: Basic Tabbed Display - -```kotlin -val tabbedDisplay = TabbedDisplay(sessionTask) -tabbedDisplay.set("Tab 1", "Content for Tab 1") -tabbedDisplay.set("Tab 2", "Content for Tab 2") -tabbedDisplay.update() -``` - -This example demonstrates how to create a simple tabbed interface with two tabs. - -Detailed examples of using these components can be added here, including code snippets and explanations of how to -integrate the tabbed UI into a web application. - -## Conclusion - -The tabbed UI components are versatile tools for building interactive and dynamic web interfaces. By understanding and -utilizing these components, developers can enhance the user experience of their applications. - -### Tabs - -The `TabbedDisplay` class in `TabbedDisplay.kt` implements a tabbed UI component that allows displaying multiple pieces -of content in a tabbed interface. - -#### High Level Design - -- The `TabbedDisplay` class maintains a list of tab name and content `StringBuilder` pairs. -- It provides methods to get/set tab content by name, find tabs by name, and update the rendered HTML. -- The `render()` method generates the HTML for the tabbed interface, including tab buttons and content divs. -- An instance keeps a reference to the `container` `StringBuilder` it is rendered into, allowing it to update itself. - -#### Usage - -To use the `TabbedDisplay` class: - -1. Create an instance, passing the `SessionTask` it is associated with -2. Add tabs using the `set(name: String, content: String)` method. This will create the tab if it doesn't exist or - update its content if it does. -3. Retrieve tab content using the `get(i: String)` method -4. Call `update()` after modifying tabs to re-render the component HTML - -The tabbed content will automatically be displayed in the associated `SessionTask`. - -### Retry - -The `Retryable` class in `Retryable.kt` extends `TabbedDisplay` to add a "retry" button that re-runs a block of code and -adds the output as a new tab. - -#### High Level Design - -`Retryable` overrides the `renderTabButtons()` method to add a recycle â™» button after the tab buttons. This button, when -clicked, triggers the retry mechanism. - -- When clicked, the retry callback: - 1. Adds a new tab with a "Retrying..." placeholder and calls `update()` - 2. Runs the `process` lambda passed to the constructor, passing a `StringBuilder` for output - 3. Replaces the placeholder content with the final output -- This allows easily re-running a block of code and capturing the new output in a new tab - -#### Usage - -To use `Retryable`: - -1. Create an instance, passing the `ApplicationInterface`, `SessionTask` and retry process lambda -2. The retry button will automatically be shown and will run the `process` lambda when clicked -3. The `process` lambda should return the `String` content to display in the new tab - -### Example 2: Using Retryable - - ```kotlin - val applicationInterface = getApplicationInterface() // Assume this returns an ApplicationInterface instance -val sessionTask = getSessionTask() // Assume this returns a SessionTask instance -val retryable = Retryable(applicationInterface, sessionTask) { stringBuilder -> - try { - // Code that might fail and needs retrying - "Operation successful" - } catch (e: Exception) { - stringBuilder.append("Error encountered: ${e.message}") - "Retry failed" - } -} -retryable.update() - ``` - -This example demonstrates how to use `Retryable` to add retry functionality. The lambda function provided to `Retryable` -is executed when the retry button is clicked. If the operation is successful, it returns a success message; otherwise, -it logs the error and returns a failure message. -By using `Retryable`, you can add retry functionality to a tabbed display with just a few lines of code. - -### Acceptable - -The `Acceptable` class is designed to handle user interactions that require acceptance or feedback before proceeding. It -extends the functionality of `TabbedDisplay` by integrating user input and decision-making processes directly into the -tabbed interface. - -#### High Level Design - -`Acceptable` manages a sequence of user interactions within a tabbed display, where each tab can represent a stage in a -decision-making process. It uses a combination of user prompts, text inputs, and acceptance links to gather and process -user feedback. - -- The class initializes with a user message and a function to process the initial response. -- It dynamically adds tabs based on user interactions and updates the display accordingly. -- A feedback mechanism allows users to revise their responses, which the system processes to potentially alter the - subsequent flow or decisions. - -#### Key Methods - -- `main()`: Orchestrates the initial display and subsequent updates based on user interactions. -- `feedbackForm()`: Generates the HTML form for user feedback within the tab. -- `acceptLink()`: Provides a link for the user to confirm their decision, moving the process to the next stage. - -#### Usage - -To use `Acceptable`: - -1. Instantiate `Acceptable` with necessary parameters like the session task, user message, initial response processing - function, and UI interface. -2. Use the `call()` method to start the interaction process and wait for the user's final acceptance. -3. The class handles user inputs and updates the tabbed display dynamically, reflecting the stages of user interaction - and decision-making. - -### Example 3: Using Acceptable - -```kotlin -val sessionTask = getSessionTask() // Assume this returns a SessionTask instance -val userMessage = "Please review the information and accept to proceed." -val acceptable = Acceptable( - task = sessionTask, - userMessage = userMessage, - initialResponse = { msg -> processInitialResponse(msg) }, - outputFn = { response -> response.toString() }, - ui = getApplicationInterface(), // Assume this returns an ApplicationInterface instance - reviseResponse = { history -> reviseUserResponse(history) }, - heading = "User Acceptance Required" -) -acceptable.call() -``` - -This example demonstrates how to use `Acceptable` to manage a user acceptance process within a tabbed interface. The -user is prompted to review information and provide feedback or accept to proceed, with each stage managed as a separate -tab. - -Let me know if you have any other questions! \ No newline at end of file diff --git a/docs/discussable.md b/docs/ui/discussable.md similarity index 100% rename from docs/discussable.md rename to docs/ui/discussable.md diff --git a/docs/task_ui.md b/docs/ui/task_ui.md similarity index 100% rename from docs/task_ui.md rename to docs/ui/task_ui.md diff --git a/docs/ui_protocol.md b/docs/ui/ui_protocol.md similarity index 99% rename from docs/ui_protocol.md rename to docs/ui/ui_protocol.md index 582a7792..4bb1bb4a 100644 --- a/docs/ui_protocol.md +++ b/docs/ui/ui_protocol.md @@ -122,7 +122,6 @@ val retryable = Retryable(appInterface, task) { content -> // Process content "Processed content" } -retryable.update() ``` ### 6. AgentPatterns diff --git a/gradle.properties b/gradle.properties index f0b5132b..a05675d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.2.3 +libraryVersion = 1.2.4 gradleVersion = 7.6.1 kotlin.daemon.jvmargs=-Xmx2g diff --git a/settings.gradle.kts b/settings.gradle.kts index b55c8051..409d77e6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,4 +8,4 @@ include("kotlin") //include("kotlin-hack-test") include("webui") -//includeBuild("../jo-penai/") \ No newline at end of file +//includeBuild("../jo-penai/") diff --git a/webui/build.gradle.kts b/webui/build.gradle.kts index 2503434e..5fafc433 100644 --- a/webui/build.gradle.kts +++ b/webui/build.gradle.kts @@ -36,7 +36,7 @@ val jackson_version = "2.17.2" dependencies { - implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.1.4") { + implementation(group = "com.simiacryptus", name = "jo-penai", version = "1.1.5") { exclude(group = "org.slf4j") } diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt index 1898b6ae..6899cb05 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt @@ -5,7 +5,7 @@ import com.simiacryptus.skyenet.set import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.session.SocketManagerBase -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown fun SocketManagerBase.addApplyDiffLinks( code: () -> String, diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt index 217e2937..1b547196 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt @@ -9,7 +9,7 @@ import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.set import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SocketManagerBase -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import java.io.File import java.nio.file.Path import kotlin.io.path.readText @@ -33,7 +33,7 @@ fun SocketManagerBase.addApplyFileDiffLinks( // Single diff block without the closing ``` due to LLM limitations... add it back and recurse return addApplyFileDiffLinks( root, - response + "\n```", + response + "\n```\n", handle, ui, api @@ -96,20 +96,21 @@ private fun SocketManagerBase.renderNewFile( filepath.toFile().writeText(codeValue, Charsets.UTF_8) handle(mapOf(File(filename).toPath() to codeValue)) return """ - |```${codeLang} - |${codeValue} - |``` - | - |
Automatically Saved ${filepath}
- |""".trimMargin() +```${codeLang} +${codeValue} +``` + +
Automatically Saved ${filepath}
+ +""" } catch (e: Throwable) { return """ - |```${codeLang} - |${codeValue} - |``` - | - |
Error Auto-Saving ${filename}: ${e.message}
- |""".trimMargin() +```${codeLang} +${codeValue} +``` + +
Error Auto-Saving ${filename}: ${e.message}
+""" } } else { val commandTask = ui.newTask(false) @@ -127,12 +128,12 @@ private fun SocketManagerBase.renderNewFile( } })!! return """ - |```${codeLang} - |${codeValue} - |``` - | - |${commandTask.placeholder} - """.trimMargin() +```${codeLang} +${codeValue} +``` + +${commandTask.placeholder} +""" } } @@ -207,22 +208,22 @@ private fun SocketManagerBase.renderDiffBlock( val echoDiff = try { IterativePatchUtil.generatePatch(prevCode, newCode.newCode) } catch (e: Throwable) { - renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) + renderMarkdown("```\n${e.stackTraceToString()}\n```\n", ui = ui) } if (echoDiff.isNotBlank() && newCode.isValid && shouldAutoApply(filepath ?: root.resolve(filename))) { try { filepath.toFile().writeText(newCode.newCode, Charsets.UTF_8) handle(mapOf(relativize to newCode.newCode)) - return "```diff\n$diffVal\n```" + """
Diff Automatically Applied to ${filepath}
""" + return "```diff\n$diffVal\n```\n" + """
Diff Automatically Applied to ${filepath}
""" } catch (e: Throwable) { log.error("Error auto-applying diff", e) - return "```diff\n$diffVal\n```" + """
Error Auto-Applying Diff to ${filepath}: ${e.message}
""" + return "```diff\n$diffVal\n```\n" + """
Error Auto-Applying Diff to ${filepath}: ${e.message}
""" } } val diffTask = ui.newTask(root = false) - diffTask.complete(renderMarkdown("```diff\n$diffVal\n```", ui = ui)) + diffTask.complete(renderMarkdown("```diff\n$diffVal\n```\n", ui = ui)) // Create tasks for displaying code and patch information val prevCodeTask = ui.newTask(root = false) @@ -266,13 +267,13 @@ private fun SocketManagerBase.renderDiffBlock( var originalCode = prevCode // For reverting changes // Create "Apply Diff" button - var apply1 = hrefLink("Apply Diff", classname = "href-link cmd-button") { + val apply1 = hrefLink("Apply Diff", classname = "href-link cmd-button") { try { originalCode = load(filepath) newCode = patch(originalCode, diffVal) - filepath.toFile()?.writeText(newCode.newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") + filepath.toFile().writeText(newCode.newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") handle(mapOf(relativize to newCode.newCode)) - hrefLink.set("""
Diff Applied
""" + revert) + hrefLink.set("
Diff Applied
$revert") applydiffTask.complete() } catch (e: Throwable) { hrefLink.append("""
Error: ${e.message}
""") @@ -291,41 +292,41 @@ private fun SocketManagerBase.renderDiffBlock( val patchFixer = SimpleActor( prompt = """ - |You are a helpful AI that helps people with coding. - | - |Response should use one or more code patches in diff format within ```diff code blocks. - |Each diff should be preceded by a header that identifies the file being modified. - |The diff format should use + for line additions, - for line deletions. - |The diff should include 2 lines of context before and after every change. - | - |Example: - | - |Here are the patches: - | - |### src/utils/exampleUtils.js - |```diff - | // Utility functions for example feature - | const b = 2; - | function exampleFunction() { - |- return b + 1; - |+ return b + 2; - | } - |``` - | - |### tests/exampleUtils.test.js - |```diff - | // Unit tests for exampleUtils - | const assert = require('assert'); - | const { exampleFunction } = require('../src/utils/exampleUtils'); - | - | describe('exampleFunction', () => { - |- it('should return 3', () => { - |+ it('should return 4', () => { - | assert.equal(exampleFunction(), 3); - | }); - | }); - |``` - """.trimMargin(), +You are a helpful AI that helps people with coding. + +Response should use one or more code patches in diff format within ```diff code blocks. +Each diff should be preceded by a header that identifies the file being modified. +The diff format should use + for line additions, - for line deletions. +The diff should include 2 lines of context before and after every change. + +Example: + +Here are the patches: + +### src/utils/exampleUtils.js +```diff + // Utility functions for example feature + const b = 2; + function exampleFunction() { +- return b + 1; ++ return b + 2; + } +``` + +### tests/exampleUtils.test.js +```diff + // Unit tests for exampleUtils + const assert = require('assert'); + const { exampleFunction } = require('../src/utils/exampleUtils'); + + describe('exampleFunction', () => { +- it('should return 3', () => { ++ it('should return 4', () => { + assert.equal(exampleFunction(), 3); + }); + }); +``` +""", model = OpenAIModels.GPT4o, temperature = 0.3 ) @@ -333,29 +334,29 @@ private fun SocketManagerBase.renderDiffBlock( val echoDiff = try { IterativePatchUtil.generatePatch(prevCode, newCode.newCode) } catch (e: Throwable) { - renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) + renderMarkdown("```\n${e.stackTraceToString()}\n```\n", ui = ui) } var answer = patchFixer.answer( listOf( """ - |Code: - |```${filename.split('.').lastOrNull() ?: ""} - |$prevCode - |``` - | - |Patch: - |```diff - |$diffVal - |``` - | - |Effective Patch: - |```diff - | $echoDiff - |``` - | - |Please provide a fix for the diff above in the form of a diff patch. - """.trimMargin() +Code: +```${filename.split('.').lastOrNull() ?: ""} +$prevCode +``` + +Patch: +```diff +$diffVal +``` + +Effective Patch: +```diff +$echoDiff +``` + +Please provide a fix for the diff above in the form of a diff patch. +""" ), api as OpenAIClient ) answer = ui.socketManager?.addApplyFileDiffLinks(root, answer, handle, ui, api) ?: answer @@ -401,23 +402,40 @@ private fun SocketManagerBase.renderDiffBlock( hrefLink = applydiffTask.complete(apply1 + "\n" + apply2)!! } + val lang = filename.split('.').lastOrNull() ?: "" newCodeTaskSB?.set( renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${newCode}\n```", + """# $filename + +```$lang +${newCode} +``` +""", ui = ui, tabs = false ) ) newCodeTask.complete("") prevCodeTaskSB?.set( renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${prevCode}\n```", + """# $filename + +```$lang +${prevCode} +``` +""", ui = ui, tabs = false ) ) prevCodeTask.complete("") patchTaskSB?.set( renderMarkdown( - "# $filename\n\n```diff\n ${echoDiff}\n```", + """ +# $filename + +```diff +${echoDiff} +``` +""", ui = ui, tabs = false ) @@ -430,25 +448,49 @@ private fun SocketManagerBase.renderDiffBlock( val echoDiff2 = try { IterativePatchUtil.generatePatch(prevCode, newCode2) } catch (e: Throwable) { - renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) + renderMarkdown( + """ + +``` +${e.stackTraceToString()} +``` +""", ui = ui) } newCode2TaskSB?.set( renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${newCode2}\n```", + """ +# $filename + +```${filename.split('.').lastOrNull() ?: ""} +${newCode2} +``` +""", ui = ui, tabs = false ) ) newCode2Task.complete("") prevCode2TaskSB?.set( renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${prevCode}\n```", + """ +# $filename + +```${filename.split('.').lastOrNull() ?: ""} +${prevCode} +``` +""", ui = ui, tabs = false ) ) prevCode2Task.complete("") patch2TaskSB?.set( renderMarkdown( - "# $filename\n\n```diff\n ${echoDiff2}\n```", + """ +# $filename + +```diff + ${echoDiff2} +``` +""", ui = ui, tabs = false ) @@ -471,9 +513,7 @@ private fun SocketManagerBase.renderDiffBlock( val newValue = if (newCode.isValid) { mainTabs + "\n" + applydiffTask.placeholder } else { - mainTabs + """ -
Warning: The patch is not valid. Please fix the patch before applying.
- """.trimIndent() + applydiffTask.placeholder + mainTabs + """
Warning: The patch is not valid. Please fix the patch before applying.
""" + applydiffTask.placeholder } return newValue } @@ -506,7 +546,7 @@ private fun load( filepath: Path? ) = try { if (true != filepath?.toFile()?.exists()) { - log.warn("""File not found: $filepath""".trimMargin()) + log.warn("File not found: $filepath") "" } else { filepath.readText(Charsets.UTF_8) diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt index 0ce984be..bd532da5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddShellExecutionLinks.kt @@ -2,7 +2,7 @@ package com.simiacryptus.diff import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SocketManagerBase - import com.simiacryptus.skyenet.webui.util.MarkdownUtil + import com.simiacryptus.skyenet.util.MarkdownUtil import java.util.* import java.io.BufferedReader import java.io.InputStreamReader @@ -37,13 +37,13 @@ import java.io.InputStreamReader } } """ - ```shell - $shellCode - ``` -
- $executeButton - ${executionTask.placeholder} -
- """.trimIndent() +```shell +$shellCode +``` +
+ $executeButton + ${executionTask.placeholder} +
+""" } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/AgentPatterns.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/AgentPatterns.kt index e7b98f29..c3605cf6 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/AgentPatterns.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/AgentPatterns.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import java.util.UUID +import java.util.* object AgentPatterns { @@ -21,9 +21,9 @@ object AgentPatterns { displayMapInTabs(tasks.mapValues { it.value.placeholder }, ui = ui, split = false) } else { """ - |
- |
- |${ +
+
+${ map.keys.joinToString("\n") { key -> """""" - }/*.indent(" ")*/ + } } - |
- |${ +
+${ map.entries.withIndex().joinToString("\n") { (idx, t) -> val (key, value) = t """ - |
" active" else -> "" } }" data-tab="$key"> - |${value/*.indent(" ")*/} - |
- """.trimMargin() - }/*.indent(" ")*/ +${value/*.indent(" ")*/} +
+""" + } } - |
- """.trimMargin() + +""" } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt index 5774f1c3..fa8fbdb4 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt @@ -1,9 +1,9 @@ package com.simiacryptus.skyenet -import com.simiacryptus.jopenai.ApiModel.Role +import com.simiacryptus.jopenai.models.ApiModel.Role import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import java.util.concurrent.Callable import java.util.concurrent.Semaphore import java.util.concurrent.atomic.AtomicBoolean @@ -43,7 +43,7 @@ ${ } } - """.trimMargin() +""" } private val acceptGuard = AtomicBoolean(false) @@ -84,11 +84,11 @@ ${ feedbackSB.clear() feedbackSB.append( """ - |
- |${acceptLink(tabIndex, tabContent, design, feedbackSB, feedbackTask = this)} - |
- |${textInput(design, tabContent, history, task, feedbackSB, feedbackTask = this)} - """.trimMargin() +
+${acceptLink(tabIndex, tabContent, design, feedbackSB, feedbackTask = this)} +
+${textInput(design, tabContent, history, task, feedbackSB, feedbackTask = this)} +""" ) complete() } @@ -215,9 +215,9 @@ ${ return atomicRef.get() } catch (e: Exception) { log.warn(""" - |Error in Discussable - |${e.message} - """.trimIndent(), e) +Error in Discussable +${e.message} +""", e) task.error(ui, e) return null as T } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/Retryable.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/Retryable.kt index ff2d167b..0985d65b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/Retryable.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/Retryable.kt @@ -20,13 +20,13 @@ open class Retryable( } override fun renderTabButtons(): String = """ -
${ +
${ tabs.withIndex().joinToString("\n") { (index, _) -> val tabId = "$index" """""" } } - ${ +${ ui.hrefLink("â™»") { val idx = tabs.size val label = label(idx) @@ -38,7 +38,7 @@ open class Retryable( set(label, newResult) } } -
- """.trimIndent() +
+""" } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt index 822b6007..c27c7221 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt @@ -15,14 +15,14 @@ open class TabbedDisplay( val size: Int get() = tabs.size open fun render() = if(tabs.isEmpty()) "" else """ -
- ${renderTabButtons()} - ${ +
+${renderTabButtons()} +${ tabs.toTypedArray().withIndex().joinToString("\n") { (idx, t) -> renderContentTab(t, idx) } } -
- """.trimIndent() +
+""" val container: StringBuilder by lazy { log.debug("Initializing container with rendered content") @@ -30,7 +30,7 @@ open class TabbedDisplay( } open fun renderTabButtons() = """ -
${ +
${ tabs.toTypedArray().withIndex().joinToString("\n") { (idx, pair) -> if (idx == selectedTab) { """""" @@ -39,15 +39,15 @@ open class TabbedDisplay( } } }
- """.trimIndent() +""" open fun renderContentTab(t: Pair, idx: Int) = """ -
"active" else -> "" } - }" data-tab="$idx">${t.second}
""".trimIndent() + }" data-tab="$idx">${t.second}
""" operator fun get(i: String) = tabs.toMap()[i] diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt index 66982b14..0b5f6cd2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt @@ -1,10 +1,8 @@ package com.simiacryptus.skyenet.apps.coding import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.OpenAIClient -import com.simiacryptus.jopenai.models.ChatModels +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.jopenai.proxy.ValidatedObject import com.simiacryptus.skyenet.Retryable @@ -19,7 +17,7 @@ import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.interpreter.Interpreter import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.TimeUnit diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt index 53b4e71b..63cc314e 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ShellToolAgent.kt @@ -1,13 +1,13 @@ package com.simiacryptus.skyenet.apps.coding import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.describe.TypeDescriber import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.core.actors.CodingActor.CodeResult import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent @@ -19,8 +19,8 @@ import com.simiacryptus.skyenet.interpreter.Interpreter import com.simiacryptus.skyenet.kotlin.KotlinInterpreter import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown -import com.simiacryptus.skyenet.webui.util.OpenAPI +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.OpenAPI import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt index 25f1885d..24ca2d96 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/DocumentParserApp.kt @@ -4,7 +4,7 @@ import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.AnthropicModels import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.TabbedDisplay import com.simiacryptus.skyenet.apps.general.parsers.DefaultParsingModel import com.simiacryptus.skyenet.apps.general.parsers.PDFReader @@ -17,7 +17,7 @@ import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.application.ApplicationSocketManager import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.session.SocketManager -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.skyenet.util.MarkdownUtil import org.intellij.lang.annotations.Language import java.awt.image.BufferedImage import java.io.File diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt index 41b7ebdf..a2c5fcad 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt @@ -5,7 +5,7 @@ import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.models.OpenAITextModel -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.AgentPatterns import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.core.actors.ParsedActor @@ -17,7 +17,7 @@ import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.application.ApplicationSocketManager import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.session.SocketManager -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.skyenet.util.MarkdownUtil import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt index eb526d79..188b33fa 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanAheadApp.kt @@ -3,7 +3,7 @@ package com.simiacryptus.skyenet.apps.general import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.OpenAITextModel -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.apps.plan.PlanCoordinator import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.Companion.initialPlan import com.simiacryptus.skyenet.apps.plan.PlanSettings diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt index 55c633b6..5cfa1363 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PlanChatApp.kt @@ -4,15 +4,16 @@ import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.models.OpenAITextModel import com.simiacryptus.skyenet.apps.plan.* +import com.simiacryptus.skyenet.apps.plan.InquiryTask.InquiryTaskData import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import com.simiacryptus.skyenet.webui.util.MarkdownUtil import org.slf4j.LoggerFactory import java.io.File import java.util.* -class PlanChatApp( +open class PlanChatApp( applicationName: String = "Task Planning Chat v1.0", path: String = "/taskChat", rootFile: File? = null, @@ -134,19 +135,16 @@ class PlanChatApp( } - private fun addRespondToChatTask(plan: PlanningTask.TaskBreakdownInterface): PlanningTask.TaskBreakdownInterface { - val tasksByID = plan.tasksByID?.toMutableMap() ?: mutableMapOf() + protected open fun addRespondToChatTask(plan: Map): Map { + val tasksByID = plan?.toMutableMap() ?: mutableMapOf() val respondTaskId = "respond_to_chat" - tasksByID[respondTaskId] = PlanningTask.PlanTask( + tasksByID[respondTaskId] = InquiryTaskData( task_description = "Respond to the user's chat message based on the executed plan", - task_type = TaskType.Inquiry, task_dependencies = tasksByID.keys.toList() ) - return PlanningTask.TaskBreakdownResult( - tasksByID = tasksByID, - ) + return tasksByID } companion object { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/StressTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/StressTestApp.kt index e7d2f249..0344a4f9 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/StressTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/StressTestApp.kt @@ -7,7 +7,7 @@ import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.skyenet.util.MarkdownUtil import org.slf4j.LoggerFactory import kotlin.random.Random diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt index 34a9bf23..2d542868 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt @@ -2,8 +2,8 @@ package com.simiacryptus.skyenet.apps.general import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.ApiModel.Role +import com.simiacryptus.jopenai.models.ApiModel +import com.simiacryptus.jopenai.models.ApiModel.Role import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.models.ChatModels @@ -11,19 +11,18 @@ import com.simiacryptus.jopenai.models.ImageModels import com.simiacryptus.jopenai.models.OpenAIModels import com.simiacryptus.jopenai.proxy.ValidatedObject import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.AgentPatterns import com.simiacryptus.skyenet.Discussable import com.simiacryptus.skyenet.TabbedDisplay import com.simiacryptus.skyenet.core.actors.* -import com.simiacryptus.skyenet.core.platform.ClientManager import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.StorageInterface import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.intellij.lang.annotations.Language import java.io.ByteArrayOutputStream import java.io.File diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt index 87d7e3b9..2a3b874d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/parsers/DocumentRecord.kt @@ -1,9 +1,9 @@ package com.simiacryptus.skyenet.apps.general.parsers -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.EmbeddingModels -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import java.io.* import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt index d98c1236..8fa9653f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt @@ -2,20 +2,18 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.apps.general.CommandPatchApp import com.simiacryptus.skyenet.apps.general.PatchApp -import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanTask -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.webui.session.SessionTask +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.io.File -abstract class AbstractAnalysisTask( +abstract class AbstractAnalysisTask( planSettings: PlanSettings, - planTask: PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: T? +) : AbstractTask(planSettings, planTask) { abstract val actorName: String abstract val actorPrompt: String @@ -24,7 +22,8 @@ abstract class AbstractAnalysisTask( SimpleActor( name = actorName, prompt = actorPrompt, - model = planSettings.getTaskSettings(planTask.task_type!!).model ?: planSettings.defaultModel, + model = planSettings.getTaskSettings(TaskType.valueOf(planTask?.task_type!!)).model + ?: planSettings.defaultModel, temperature = planSettings.temperature, ) } @@ -33,7 +32,7 @@ abstract class AbstractAnalysisTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API @@ -65,7 +64,8 @@ abstract class AbstractAnalysisTask( autoFix = agent.planSettings.autoFix ), api = api as ChatClient, - model = agent.planSettings.getTaskSettings(planTask.task_type!!).model ?: agent.planSettings.defaultModel, + model = agent.planSettings.getTaskSettings(TaskType.valueOf(planTask?.task_type!!)).model + ?: agent.planSettings.defaultModel, files = agent.files, command = analysisResult ).run( diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt index 6ce67415..b4d05829 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt @@ -2,8 +2,6 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.FileValidationUtils import com.simiacryptus.jopenai.API -import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanTask -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.set import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask @@ -13,15 +11,16 @@ import java.nio.file.Files import java.nio.file.Path import kotlin.streams.asSequence -abstract class AbstractTask( +abstract class AbstractTask( val planSettings: PlanSettings, - val planTask: PlanTask + val planTask: T? ) { var state: TaskState? = TaskState.Pending protected val codeFiles = mutableMapOf() protected open val root: Path - get() = File(planSettings.workingDir).toPath() + get() = planSettings.workingDir?.let { File(it).toPath() } + ?: throw IllegalStateException("Working directory not set") enum class TaskState { Pending, @@ -30,7 +29,7 @@ abstract class AbstractTask( } protected fun getPriorCode(planProcessingState: PlanProcessingState) = - planTask.task_dependencies?.joinToString("\n\n\n") { dependency -> + planTask?.task_dependencies?.joinToString("\n\n\n") { dependency -> """ |# $dependency | @@ -38,7 +37,8 @@ abstract class AbstractTask( """.trimMargin() } ?: "" - protected fun getInputFileCode(): String = ((planTask.input_files ?: listOf()) + (planTask.output_files ?: listOf())) + protected fun getInputFileCode(): String = + ((planTask?.input_files ?: listOf()) + (planTask?.output_files ?: listOf())) .flatMap { pattern: String -> val matcher = FileSystems.getDefault().getPathMatcher("glob:$pattern") Files.walk(root).asSequence() @@ -90,7 +90,7 @@ abstract class AbstractTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt index 6255688d..c14e8f0e 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt @@ -1,14 +1,37 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.CodeOptimizationTask.CodeOptimizationTaskData + import org.slf4j.LoggerFactory class CodeOptimizationTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractAnalysisTask(planSettings, planTask) { + planTask: CodeOptimizationTaskData? +) : AbstractAnalysisTask(planSettings, planTask) { + + class CodeOptimizationTaskData( + @Description("Files to be optimized") + val filesToOptimize: List? = null, + @Description("Specific areas of focus for the optimization") + val optimizationFocus: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.Optimization.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override val actorName = "CodeOptimization" override val actorPrompt = """ - |Analyze the provided code and suggest optimizations to improve code quality. Focus exclusively on: + Analyze the provided code and suggest optimizations to improve code quality. Focus exclusively on: |1. Code structure and organization |2. Readability improvements |3. Maintainability enhancements @@ -32,6 +55,7 @@ class CodeOptimizationTask( """.trimMargin() } + override fun getAnalysisInstruction(): String { return "Optimize the following code for better readability and maintainability" } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt index 9941d5a5..65f5c4f9 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt @@ -1,32 +1,58 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.CodeReviewTask.CodeReviewTaskData import org.slf4j.LoggerFactory class CodeReviewTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractAnalysisTask(planSettings, planTask) { + planTask: CodeReviewTaskData? +) : AbstractAnalysisTask(planSettings, planTask) { + class CodeReviewTaskData( + @Description("List of files to be reviewed") + val filesToReview: List? = null, + @Description("Specific areas of focus for the review (optional)") + val focusAreas: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.CodeReview.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override val actorName: String = "CodeReview" override val actorPrompt: String = """ Perform a comprehensive code review for the provided code files. Analyze the code for: 1. Code quality and readability 2. Potential bugs or errors - |3. Performance issues - |4. Security vulnerabilities - |5. Adherence to best practices and coding standards - |6. Suggestions for improvements or optimizations + 3. Performance issues + 4. Security vulnerabilities + 5. Adherence to best practices and coding standards + 6. Suggestions for improvements or optimizations | Provide a detailed review with specific examples and recommendations for each issue found. Format the response as a markdown document with appropriate headings and code snippets. """.trimIndent() - override fun getAnalysisInstruction(): String = "Review the following code" + override fun getAnalysisInstruction(): String { + val filesToReview = planTask?.filesToReview?.joinToString(", ") ?: "all provided files" + val focusAreas = planTask?.focusAreas?.joinToString(", ") + return "Review the following code files: $filesToReview" + + if (focusAreas != null) ". Focus on these areas: $focusAreas" else "" + } override fun promptSegment(): String { return """ - |CodeReview - Perform an automated code review and provide suggestions for improvements - | ** Specify the files to be reviewed - | ** Optionally provide specific areas of focus for the review + CodeReview - Perform an automated code review and provide suggestions for improvements + ** Specify the files to be reviewed + ** Optionally provide specific areas of focus for the review """.trimMargin() } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt index ee28e22e..5f88c199 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt @@ -2,29 +2,49 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient +import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.skyenet.Retryable import com.simiacryptus.skyenet.apps.general.CmdPatchApp import com.simiacryptus.skyenet.apps.general.PatchApp -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface +import com.simiacryptus.skyenet.apps.plan.CommandAutoFixTask.CommandAutoFixTaskData +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil import org.slf4j.LoggerFactory import java.io.File import java.util.concurrent.Semaphore class CommandAutoFixTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: CommandAutoFixTaskData? +) : AbstractTask(planSettings, planTask) { + class CommandAutoFixTaskData( + @Description("The command to be executed") + val command: List? = null, + @Description("The working directory for the command execution") + val workingDir: String? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.CommandAutoFix.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override fun promptSegment(): String { return """ - |CommandAutoFix - Run a command and automatically fix any issues that arise - | ** Specify the command to be executed and any additional instructions - | ** Specify the working directory relative to the root directory - | ** Provide the command arguments in the 'commandArguments' field - | ** List input files/tasks to be examined when fixing issues - | ** Available commands: - |${planSettings.commandAutoFixCommands?.joinToString("\n") { " * ${File(it).name}" }} +CommandAutoFix - Run a command and automatically fix any issues that arise + ** Specify the command to be executed and any additional instructions + ** Specify the working directory relative to the root directory + ** Provide the command and its arguments in the 'command' field + ** List input files/tasks to be examined when fixing issues + ** Available commands: + ${planSettings.commandAutoFixCommands?.joinToString("\n") { " * ${File(it).name}" }} """.trimMargin() } @@ -32,7 +52,7 @@ class CommandAutoFixTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API @@ -43,7 +63,7 @@ class CommandAutoFixTask( } Retryable(agent.ui, task = task) { val task = agent.ui.newTask(false).apply { it.append(placeholder) } - val alias = this.planTask.execution_task?.command?.first() + val alias = this.planTask?.command?.first() val commandAutoFixCommands = agent.planSettings.commandAutoFixCommands val cmds = commandAutoFixCommands ?.map { File(it) }?.associateBy { it.name } @@ -53,7 +73,7 @@ class CommandAutoFixTask( if (executable == null) { throw IllegalArgumentException("Command not found: $alias") } - val workingDirectory = (this.planTask.execution_task?.workingDir + val workingDirectory = (this.planTask?.workingDir ?.let { agent.root.toFile().resolve(it) } ?: agent.root.toFile()) .apply { mkdirs() } val outputResult = CmdPatchApp( @@ -61,7 +81,7 @@ class CommandAutoFixTask( session = agent.session, settings = PatchApp.Settings( executable = executable, - arguments = this.planTask.execution_task?.command?.drop(1)?.joinToString(" ") ?: "", + arguments = this.planTask?.command?.drop(1)?.joinToString(" ") ?: "", workingDirectory = workingDirectory, exitCodeOption = "nonzero", additionalInstructions = "", @@ -69,7 +89,8 @@ class CommandAutoFixTask( ), api = api as ChatClient, files = agent.files, - model = agent.planSettings.getTaskSettings(planTask.task_type!!).model ?: agent.planSettings.defaultModel, + model = agent.planSettings.getTaskSettings(TaskType.valueOf(planTask?.task_type!!)).model + ?: agent.planSettings.defaultModel, ).run( ui = agent.ui, task = task diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt index 03417778..f3c4bf76 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DisabledTaskException.kt @@ -1,3 +1,3 @@ package com.simiacryptus.skyenet.apps.plan -class DisabledTaskException(taskType: TaskType) : Exception("Task type $taskType is disabled") \ No newline at end of file +class DisabledTaskException(taskType: TaskType<*>) : Exception("Task type $taskType is disabled") \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt index 6d659edc..cf02797f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt @@ -1,19 +1,37 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface +import com.simiacryptus.skyenet.apps.plan.DocumentationTask.DocumentationTaskData import com.simiacryptus.skyenet.core.actors.SimpleActor +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.util.concurrent.Semaphore class DocumentationTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: DocumentationTaskData? +) : AbstractTask(planSettings, planTask) { + class DocumentationTaskData( + @Description("List of files or tasks to be documented") + val items_to_document: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.Documentation.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override fun promptSegment(): String { return """ Documentation - Generate documentation @@ -30,7 +48,7 @@ class DocumentationTask( Include code examples where applicable, and explain the rationale behind key design decisions and algorithm choices. Document any known issues or areas for improvement, providing guidance for future developers on how to extend or maintain the code. """.trimMargin(), - model = planSettings.getTaskSettings(planTask.task_type!!).model ?: planSettings.defaultModel, + model = planSettings.getTaskSettings(TaskType.Documentation).model ?: planSettings.defaultModel, temperature = planSettings.temperature, ) } @@ -39,7 +57,7 @@ class DocumentationTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API @@ -49,12 +67,14 @@ class DocumentationTask( semaphore.release() } val process = { sb: StringBuilder -> + val itemsToDocument = planTask?.items_to_document ?: emptyList() val docResult = documentationGeneratorActor.answer( listOf( userMessage, JsonUtil.toJson(plan), getPriorCode(planProcessingState), getInputFileCode(), + "Items to document: ${itemsToDocument.joinToString(", ")}" ).filter { it.isNotBlank() }, api ) planProcessingState.taskResult[taskId] = docResult diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt index 2256fd89..3cf4f665 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt @@ -2,19 +2,39 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface +import com.simiacryptus.skyenet.apps.plan.FileModificationTask.FileModificationTaskData import com.simiacryptus.skyenet.core.actors.SimpleActor +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.util.concurrent.Semaphore class FileModificationTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: FileModificationTaskData? +) : AbstractTask(planSettings, planTask) { + class FileModificationTaskData( + @Description("List of input files to be examined when designing the modifications") + input_files: List? = null, + @Description("List of output files to be modified or created") + output_files: List? = null, + @Description("Specific modifications to be made to the files") + val modifications: Map? = null, + task_description: String? = null, + task_dependencies: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.FileModification.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + val fileModificationActor by lazy { SimpleActor( name = "FileModification", @@ -62,7 +82,7 @@ class FileModificationTask( |} ${TRIPLE_TILDE} """.trimMargin(), - model = planSettings.getTaskSettings(planTask.task_type!!).model ?: planSettings.defaultModel, + model = planSettings.getTaskSettings(TaskType.FileModification).model ?: planSettings.defaultModel, temperature = planSettings.temperature, ) } @@ -79,12 +99,12 @@ class FileModificationTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API ) { - if(((planTask.input_files ?: listOf()) + (planTask.output_files ?: listOf())).isEmpty()) { + if (((planTask?.input_files ?: listOf()) + (planTask?.output_files ?: listOf())).isEmpty()) { task.complete("No input files specified") return } @@ -97,7 +117,7 @@ class FileModificationTask( JsonUtil.toJson(plan), getPriorCode(planProcessingState), getInputFileCode(), - this.planTask.task_description ?: "", + this.planTask?.task_description ?: "", ).filter { it.isNotBlank() }, api ) planProcessingState.taskResult[taskId] = codeResult diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt index 8292cf18..33b644c1 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/ForeachTask.kt @@ -1,16 +1,36 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.ForeachTask.ForeachTaskData import com.simiacryptus.skyenet.apps.plan.PlanUtil.diagram import com.simiacryptus.skyenet.apps.plan.PlanUtil.executionOrder -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.webui.session.SessionTask import org.slf4j.LoggerFactory class ForeachTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: ForeachTaskData? +) : AbstractTask(planSettings, planTask) { + + class ForeachTaskData( + @Description("A list of items over which the ForEach task will iterate. (Only applicable for ForeachTask tasks) Can be used to process outputs from previous tasks.") + val foreach_items: List? = null, + @Description("A map of sub-task IDs to PlanTask objects to be executed for each item. (Only applicable for ForeachTask tasks) Allows for complex task dependencies and information flow within iterations.") + val foreach_subplan: Map? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null, + ) : PlanTaskBase( + task_type = TaskType.ForeachTask.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) override fun promptSegment(): String { return """ @@ -24,21 +44,23 @@ ForeachTask - Execute a task for each item in a list agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API ) { - val items = planTask.foreach_task?.foreach_items ?: throw RuntimeException("No items specified for ForeachTask") - val subTasks = planTask.foreach_task?.foreach_subplan ?: throw RuntimeException("No subTasks specified for ForeachTask") + val items = + planTask?.foreach_items ?: throw RuntimeException("No items specified for ForeachTask") + val subTasks = planTask.foreach_subplan ?: throw RuntimeException("No subTasks specified for ForeachTask") val subPlanTask = agent.ui.newTask(false) task.add(subPlanTask.placeholder) items.forEachIndexed { index, item -> val itemSubTasks = subTasks.mapValues { (_, subTaskPlan) -> - subTaskPlan.copy(task_description = "${subTaskPlan.task_description} - Item $index: $item") + subTaskPlan.task_description = "${subTaskPlan.task_description} - Item $index: $item" + subTaskPlan } - val itemPlanProcessingState = PlanProcessingState(itemSubTasks.toMutableMap()) + val itemPlanProcessingState = PlanProcessingState(itemSubTasks) agent.executePlan( task = subPlanTask, diagramBuffer = subPlanTask.add(diagram(agent.ui, itemPlanProcessingState.subTasks)), @@ -48,9 +70,7 @@ ForeachTask - Execute a task for each item in a list taskIdProcessingQueue = executionOrder(itemSubTasks).toMutableList(), pool = agent.pool, userMessage = "$userMessage\nProcessing item $index: $item", - plan = object : TaskBreakdownInterface { - override val tasksByID = itemSubTasks - }, + plan = itemSubTasks, api = api ) } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt index fdc634c6..a81a7c4b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt @@ -1,39 +1,59 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.Discussable -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface import com.simiacryptus.skyenet.core.actors.SimpleActor +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.util.concurrent.Semaphore import java.util.concurrent.atomic.AtomicReference class InquiryTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: InquiryTaskData? +) : AbstractTask(planSettings, planTask) { + class InquiryTaskData( + @Description("The specific questions or topics to be addressed in the inquiry") + val inquiry_questions: List? = null, + @Description("The goal or purpose of the inquiry") + val inquiry_goal: String? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.Inquiry.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + private val inquiryActor by lazy { 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. - Compile comprehensive information and insights on the specified topic. - 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. + 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. + Compile comprehensive information and insights on the specified topic. + 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. - 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 (${ + 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 (${ planSettings.taskSettings.filter { it.value.enabled }.keys.joinToString(", ") }). - This will ensure that the inquiries are tailored to assist in the planning and execution of tasks within the system's framework. + This will ensure that the inquiries are tailored to assist in the planning and execution of tasks within the system's framework. """.trimMargin(), - model = planSettings.getTaskSettings(planTask.task_type!!).model ?: planSettings.defaultModel, + model = planSettings.getTaskSettings(TaskType.valueOf(planTask?.task_type!!)).model + ?: planSettings.defaultModel, temperature = planSettings.temperature, ) } @@ -50,7 +70,7 @@ class InquiryTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API @@ -67,7 +87,13 @@ class InquiryTask( val inquiryResult = if (planSettings.allowBlocking) Discussable( task = task, - userMessage = { "Expand ${this.planTask.task_description ?: ""}\n${JsonUtil.toJson(data = this)}" }, + userMessage = { + "Expand ${this.planTask?.task_description ?: ""}\nQuestions: ${ + planTask?.inquiry_questions?.joinToString( + "\n" + ) + }\nGoal: ${planTask?.inquiry_goal}\n${JsonUtil.toJson(data = this)}" + }, heading = "", initialResponse = { it: String -> inquiryActor.answer(toInput(it), api = api) }, outputFn = { design: String -> @@ -75,7 +101,9 @@ class InquiryTask( }, ui = agent.ui, reviseResponse = { userMessages: List> -> - val inStr = "Expand ${this.planTask.task_description ?: ""}\n${JsonUtil.toJson(data = this)}" + val inStr = "Expand ${this.planTask?.task_description ?: ""}\nQuestions: ${ + planTask?.inquiry_questions?.joinToString("\n") + }\nGoal: ${planTask?.inquiry_goal}\n${JsonUtil.toJson(data = this)}" val messages = userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } .toTypedArray() inquiryActor.respond( @@ -87,7 +115,13 @@ class InquiryTask( atomicRef = AtomicReference(), semaphore = Semaphore(0), ).call() else inquiryActor.answer( - toInput("Expand ${this.planTask.task_description ?: ""}\n${JsonUtil.toJson(data = this)}"), + toInput( + "Expand ${this.planTask?.task_description ?: ""}\nQuestions: ${ + planTask?.inquiry_questions?.joinToString( + "\n" + ) + }\nGoal: ${planTask?.inquiry_goal}\n${JsonUtil.toJson(data = this)}" + ), api = api ).apply { task.add(MarkdownUtil.renderMarkdown(this, ui = agent.ui)) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt index ee599a45..47be71b8 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt @@ -1,12 +1,33 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.PerformanceAnalysisTask.PerformanceAnalysisTaskData import org.slf4j.LoggerFactory class PerformanceAnalysisTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractAnalysisTask(planSettings, planTask) { + planTask: PerformanceAnalysisTaskData? +) : AbstractAnalysisTask(planSettings, planTask) { + class PerformanceAnalysisTaskData( + @Description("Files to be analyzed for performance issues") + val files_to_analyze: List? = null, + @Description("Specific areas of focus for the analysis (e.g., time complexity, memory usage, I/O operations)") + val analysis_focus: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null, + ) : PlanTaskBase( + task_type = TaskType.PerformanceAnalysis.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override val actorName = "PerformanceAnalysis" override val actorPrompt = """ Analyze the provided code for performance issues and bottlenecks. Focus exclusively on: @@ -32,6 +53,10 @@ PerformanceAnalysis - Analyze code for performance issues and suggest improvemen ** Optionally provide specific areas of focus for the analysis (e.g., time complexity, memory usage, I/O operations) """.trimMargin() } + fun getFiles(): List { + return planTask?.files_to_analyze ?: emptyList() + } + override fun getAnalysisInstruction(): String { return "Analyze the following code for performance issues and provide a detailed report" diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt index 56ea7090..b7a220e0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt @@ -3,16 +3,14 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.FileValidationUtils import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.skyenet.Discussable import com.simiacryptus.skyenet.TabbedDisplay import com.simiacryptus.skyenet.apps.plan.PlanUtil.buildMermaidGraph import com.simiacryptus.skyenet.apps.plan.PlanUtil.filterPlan import com.simiacryptus.skyenet.apps.plan.PlanUtil.getAllDependencies import com.simiacryptus.skyenet.apps.plan.PlanUtil.render -import com.simiacryptus.skyenet.apps.plan.PlanningTask.* import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.getImpl import com.simiacryptus.skyenet.core.actors.ParsedResponse import com.simiacryptus.skyenet.core.platform.ApplicationServices @@ -20,9 +18,10 @@ import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.StorageInterface import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.set +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path @@ -66,7 +65,8 @@ class PlanCoordinator( taskBreakdownWithPrompt = JsonUtil.fromJson(jsonInput, PlanUtil.TaskBreakdownWithPrompt::class.java) taskBreakdownWithPrompt.plan } - task.add(MarkdownUtil.renderMarkdown( + task.add( + MarkdownUtil.renderMarkdown( """ |## Executing TaskBreakdownWithPrompt |Prompt: ${taskBreakdownWithPrompt.prompt} @@ -75,14 +75,14 @@ class PlanCoordinator( |${taskBreakdownWithPrompt.planText} |``` """.trimMargin(), ui = ui)) - executePlan(plan, task, taskBreakdownWithPrompt.prompt, api) + executePlan(plan ?: emptyMap(), task, taskBreakdownWithPrompt.prompt, api) } catch (e: Exception) { task.error(ui, e) } } fun executePlan( - plan: TaskBreakdownInterface, + plan: Map, task: SessionTask, userMessage: String, api: API @@ -121,20 +121,22 @@ class PlanCoordinator( return planProcessingState } - private fun newState(plan: TaskBreakdownInterface) = - PlanProcessingState((filterPlan { plan }.tasksByID?.entries?.toTypedArray>() - ?.associate { it.key to it.value } ?: mapOf()).toMutableMap()) + private fun newState(plan: Map) = + PlanProcessingState( + subTasks = (filterPlan { plan }?.entries?.toTypedArray>() + ?.associate { it.key to it.value } ?: mapOf()).toMutableMap() + ) fun executePlan( task: SessionTask, diagramBuffer: StringBuilder?, - subTasks: Map, + subTasks: Map, diagramTask: SessionTask, planProcessingState: PlanProcessingState, taskIdProcessingQueue: MutableList, pool: ThreadPoolExecutor, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, api: API ) { val sessionTask = ui.newTask(false).apply { task.add(placeholder) } @@ -193,7 +195,6 @@ class PlanCoordinator( val subTask = planProcessingState.subTasks[taskId] ?: throw RuntimeException("Task not found: $taskId") planProcessingState.taskFutures[taskId] = pool.submit { subTask.state = AbstractTask.TaskState.Pending - //taskTabs.update() log.debug("Awaiting dependencies: ${subTask.task_dependencies?.joinToString(", ") ?: ""}") subTask.task_dependencies ?.associate { it to planProcessingState.taskFutures[it] } @@ -205,7 +206,7 @@ class PlanCoordinator( } } subTask.state = AbstractTask.TaskState.InProgress - //taskTabs.update() + taskTabs.update() log.debug("Running task: ${System.identityHashCode(subTask)} ${subTask.task_description}") val task1 = planProcessingState.uitaskMap.get(taskId) ?: ui.newTask(false).apply { taskTabs[taskId] = placeholder @@ -308,14 +309,20 @@ class PlanCoordinator( ) }, outputFn = { - render( - withPrompt = PlanUtil.TaskBreakdownWithPrompt( - prompt = userMessage, - plan = it.obj as TaskBreakdownResult, - planText = it.text - ), - ui = ui - ) + try { + render( + withPrompt = PlanUtil.TaskBreakdownWithPrompt( + prompt = userMessage, + plan = it.obj, + planText = it.text + ), + ui = ui + ) + } catch (e: Throwable) { + log.warn("Error rendering task breakdown", e) + task.error(ui, e) + e.message ?: e.javaClass.simpleName + } }, ui = ui, reviseResponse = { userMessages: List> -> @@ -327,7 +334,7 @@ class PlanCoordinator( ).call().let { PlanUtil.TaskBreakdownWithPrompt( prompt = userMessage, - plan = filterPlan { it.obj } as TaskBreakdownResult, + plan = filterPlan { it.obj } ?: emptyMap(), planText = it.text ) } @@ -338,7 +345,7 @@ class PlanCoordinator( ).let { PlanUtil.TaskBreakdownWithPrompt( prompt = userMessage, - plan = filterPlan { it.obj } as TaskBreakdownResult, + plan = filterPlan { it.obj } ?: emptyMap(), planText = it.text ) } @@ -348,13 +355,13 @@ class PlanCoordinator( api: API, planSettings: PlanSettings, inStrings: List - ): ParsedResponse { + ): ParsedResponse> { val planningActor = planSettings.planningActor() return planningActor.respond( messages = planningActor.chatMessages(inStrings), input = inStrings, api = api - ) as ParsedResponse + ).map(Map::class.java) { it.tasksByID ?: emptyMap() } as ParsedResponse> } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt index 800c4735..2888b31d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanProcessingState.kt @@ -4,8 +4,8 @@ import com.simiacryptus.skyenet.webui.session.SessionTask import java.util.concurrent.Future data class PlanProcessingState( - val subTasks: Map, - val tasksByDescription: MutableMap = subTasks.entries.toTypedArray() + val subTasks: Map, + val tasksByDescription: MutableMap = subTasks.entries.toTypedArray() .associate { it.value.task_description to it.value }.toMutableMap(), val taskIdProcessingQueue: MutableList = PlanUtil.executionOrder(subTasks).toMutableList(), val taskResult: MutableMap = mutableMapOf(), diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt index 79d2dbc6..54447a77 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanSettings.kt @@ -2,9 +2,13 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.describe.AbbrevWhitelistYamlDescriber import com.simiacryptus.jopenai.models.OpenAITextModel +import com.simiacryptus.skyenet.apps.plan.CommandAutoFixTask.CommandAutoFixTaskData +import com.simiacryptus.skyenet.apps.plan.FileModificationTask.FileModificationTaskData import com.simiacryptus.skyenet.apps.plan.PlanUtil.isWindows +import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanningTaskData import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownResult import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.getAvailableTaskTypes +import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.getImpl import com.simiacryptus.skyenet.core.actors.ParsedActor data class TaskSettings( @@ -12,21 +16,20 @@ data class TaskSettings( var model: OpenAITextModel? = null ) - open class PlanSettings( var defaultModel: OpenAITextModel, var parsingModel: OpenAITextModel, val command: List = listOf(if (isWindows) "powershell" else "bash"), var temperature: Double = 0.2, val budget: Double = 2.0, - val taskSettings: MutableMap = TaskType.values().associateWith { taskType -> + val taskSettings: MutableMap = TaskType.values().associateWith { taskType -> TaskSettings( when (taskType) { TaskType.FileModification, TaskType.Inquiry -> true else -> false } ) - }.toMutableMap(), + }.mapKeys { it.key.name }.toMutableMap(), var autoFix: Boolean = false, var allowBlocking: Boolean = true, var commandAutoFixCommands: List? = listOf(), @@ -35,11 +38,11 @@ open class PlanSettings( val language: String? = if (isWindows) "powershell" else "bash", ) { - fun getTaskSettings(taskType: TaskType): TaskSettings = - taskSettings[taskType] ?: TaskSettings() + fun getTaskSettings(taskType: TaskType<*>): TaskSettings = + taskSettings[taskType.name] ?: TaskSettings() - fun setTaskSettings(taskType: TaskType, settings: TaskSettings) { - taskSettings[taskType] = settings + fun setTaskSettings(taskType: TaskType<*>, settings: TaskSettings) { + taskSettings[taskType.name] = settings } fun copy( @@ -48,7 +51,7 @@ open class PlanSettings( command: List = this.command, temperature: Double = this.temperature, budget: Double = this.budget, - taskSettings: MutableMap = this.taskSettings, + taskSettings: MutableMap = this.taskSettings, autoFix: Boolean = this.autoFix, allowBlocking: Boolean = this.allowBlocking, commandAutoFixCommands: List? = this.commandAutoFixCommands, @@ -70,40 +73,9 @@ open class PlanSettings( language = language, ) - var exampleInstance = TaskBreakdownResult( - tasksByID = mapOf( - "1" to PlanningTask.PlanTask( - task_description = "Task 1", - task_type = TaskType.CommandAutoFix, - task_dependencies = listOf(), - execution_task = PlanningTask.ExecutionTask( - command = listOf("npx", "create-react-app", ".", "--template", "typescript"), - workingDir = ".", - ) - ), - "2" to PlanningTask.PlanTask( - task_description = "Task 2", - task_type = TaskType.FileModification, - task_dependencies = listOf("1"), - input_files = listOf("input2.txt"), - output_files = listOf("output2.txt"), - ), - "3" to PlanningTask.PlanTask( - task_description = "Task 3", - task_type = TaskType.TaskPlanning, - task_dependencies = listOf("2"), - input_files = listOf("input3.txt"), - ) - ), - ) - - open fun planningActor(): ParsedActor { + fun planningActor(): ParsedActor { val planTaskSettings = this.getTaskSettings(TaskType.TaskPlanning) - return ParsedActor( - name = "TaskBreakdown", - resultClass = TaskBreakdownResult::class.java, - exampleInstance = exampleInstance, - prompt = """ + val prompt = """ |Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. | |For each task: @@ -113,15 +85,35 @@ open class PlanSettings( |* Specify important interface and integration details (each task will run independently off of a copy of this plan) | |Tasks can be of the following types: - |${getAvailableTaskTypes(this).joinToString("\n") { "* ${it.promptSegment()}" }} + |${ + getAvailableTaskTypes(this).joinToString("\n") { taskType -> + "* ${getImpl(this, taskType).promptSegment()}" + } + } | |Creating directories and initializing source control are out of scope. |${if (planTaskSettings.enabled) "Do not start your plan with a plan to plan!\n" else ""} - """.trimMargin(), + """.trimMargin() + val describer = describer() + val parserPrompt = """ +Task Subtype Schema: + +${getAvailableTaskTypes(this).joinToString("\n\n") { taskType -> """ +${taskType.name}: + ${describer.describe(taskType.taskDataClass).replace("\n", "\n ")} +""".trim() + }} + """.trimIndent() + return ParsedActor( + name = "TaskBreakdown", + resultClass = TaskBreakdownResult::class.java, + exampleInstance = exampleInstance, + prompt = prompt, model = planTaskSettings.model ?: this.defaultModel, parsingModel = this.parsingModel, temperature = this.temperature, - describer = describer(), + describer = describer, + parserPrompt = parserPrompt ) } @@ -138,4 +130,28 @@ open class PlanSettings( } } } + + companion object { + var exampleInstance = TaskBreakdownResult( + tasksByID = mapOf( + "1" to CommandAutoFixTaskData( + task_description = "Task 1", + task_dependencies = listOf(), + command = listOf("npx", "create-react-app", ".", "--template", "typescript"), + workingDir = "." + ), + "2" to FileModificationTaskData( + task_description = "Task 2", + task_dependencies = listOf("1"), + input_files = listOf("input2.txt"), + output_files = listOf("output2.txt"), + ), + "3" to PlanningTaskData( + task_description = "Task 3", + task_dependencies = listOf("2"), + input_files = listOf("input3.txt"), + ) + ), + ) + } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt index f88861c5..7b4be204 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanUtil.kt @@ -1,12 +1,10 @@ package com.simiacryptus.skyenet.apps.plan -import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.AgentPatterns import com.simiacryptus.skyenet.apps.plan.AbstractTask.TaskState -import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanTask -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface +import com.simiacryptus.skyenet.util.MarkdownUtil import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.util.JsonUtil import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -14,7 +12,7 @@ object PlanUtil { fun diagram( ui: ApplicationInterface, - taskMap: Map + taskMap: Map ) = MarkdownUtil.renderMarkdown( """ |## Sub-Plan Task Dependency Graph @@ -27,7 +25,7 @@ object PlanUtil { data class TaskBreakdownWithPrompt( val prompt: String, - val plan: PlanningTask.TaskBreakdownResult, + val plan: Map, val planText: String ) @@ -45,20 +43,20 @@ object PlanUtil { "```mermaid\n" + buildMermaidGraph( (filterPlan { withPrompt.plan - }.tasksByID ?: emptyMap()).toMutableMap() + } ?: emptyMap()).toMutableMap() ) + "\n```\n", ui = ui ) ) ) - fun executionOrder(tasks: Map): List { + fun executionOrder(tasks: Map): List { val taskIds: MutableList = mutableListOf() - val taskMap = tasks.mapValues { it.value.copy(task_dependencies = it.value.task_dependencies?.filter { entry -> - entry in tasks.keys - }) }.toMutableMap() + val taskMap = tasks.toMutableMap() while (taskMap.isNotEmpty()) { val nextTasks = - taskMap.filter { (_, task) -> task.task_dependencies?.all { taskIds.contains(it) } ?: true } + taskMap.filter { (_, task) -> task.task_dependencies?.filter { entry -> + entry in tasks.keys + }?.all { taskIds.contains(it) } ?: true } if (nextTasks.isEmpty()) { throw RuntimeException("Circular dependency detected in task breakdown") } @@ -86,7 +84,7 @@ object PlanUtil { private val mermaidGraphCache = ConcurrentHashMap() private val mermaidExceptionCache = ConcurrentHashMap() - fun buildMermaidGraph(subTasks: Map): String { + fun buildMermaidGraph(subTasks: Map): String { // Generate a unique key based on the subTasks map val cacheKey = JsonUtil.toJson(subTasks) // Return cached result if available @@ -96,7 +94,7 @@ object PlanUtil { val graphBuilder = StringBuilder("graph TD;\n") subTasks.forEach { (taskId, task) -> val sanitizedTaskId = sanitizeForMermaid(taskId) - val taskType = task.task_type?.name ?: "Unknown" + val taskType = task.task_type ?: "Unknown" val escapedDescription = escapeMermaidCharacters(task.task_description ?: "") val style = when (task.state) { TaskState.Completed -> ":::completed" @@ -126,11 +124,11 @@ object PlanUtil { } } - fun filterPlan(retries: Int = 3, fn: () -> TaskBreakdownInterface): TaskBreakdownInterface { - val obj = fn() - var tasksByID = obj.tasksByID?.filter { (k, v) -> + fun filterPlan(retries: Int = 3, fn: () -> Map?): Map? { + val obj = fn() ?: emptyMap() + var tasksByID = obj?.filter { (k, v) -> when { - v.task_type == TaskType.TaskPlanning && v.task_dependencies.isNullOrEmpty() -> + v.task_type == TaskType.TaskPlanning.name && v.task_dependencies.isNullOrEmpty() -> if (retries <= 0) { log.warn("TaskPlanning task $k has no dependencies: " + JsonUtil.toJson(obj)) true @@ -141,12 +139,10 @@ object PlanUtil { else -> true } } ?: emptyMap() - tasksByID = tasksByID.map { - it.key to it.value.copy( - task_dependencies = it.value.task_dependencies?.filter { it in tasksByID.keys }, - state = TaskState.Pending - ) - }.toMap() + tasksByID.forEach { + it.value.task_dependencies = it.value.task_dependencies?.filter { it in tasksByID.keys } + it.value.state = TaskState.Pending + } try { executionOrder(tasksByID) } catch (e: RuntimeException) { @@ -158,16 +154,16 @@ object PlanUtil { return filterPlan(retries - 1, fn) } } - return if (tasksByID.size == obj.tasksByID?.size) { + return if (tasksByID.size == obj?.size) { obj } else filterPlan { - PlanningTask.TaskBreakdownResult(tasksByID) + tasksByID } } fun getAllDependencies( - subPlanTask: PlanTask, - subTasks: Map, + subPlanTask: PlanTaskBase, + subTasks: Map, visited: MutableSet ): List { val dependencies = subPlanTask.task_dependencies?.toMutableList() ?: mutableListOf() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt index 62ce097c..f17d50a2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt @@ -1,67 +1,45 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList -import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.Discussable import com.simiacryptus.skyenet.apps.plan.PlanUtil.diagram import com.simiacryptus.skyenet.apps.plan.PlanUtil.executionOrder import com.simiacryptus.skyenet.apps.plan.PlanUtil.filterPlan import com.simiacryptus.skyenet.apps.plan.PlanUtil.render +import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanningTaskData import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.session.SessionTask +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory class PlanningTask( planSettings: PlanSettings, - planTask: PlanTask -) : AbstractTask(planSettings, planTask) { - - interface TaskBreakdownInterface { - val tasksByID: Map? - } + planTask: PlanningTaskData? +) : AbstractTask(planSettings, planTask) { + class PlanningTaskData( + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = TaskState.Pending, + ) : PlanTaskBase( + task_type = TaskType.TaskPlanning.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) data class TaskBreakdownResult( @Description("A map where each task ID is associated with its corresponding PlanTask object. Crucial for defining task relationships and information flow.") - override val tasksByID: Map? = null, - ) : TaskBreakdownInterface - - data class PlanTask( - @Description("A detailed description of the specific task to be performed, including its role in the overall plan and its dependencies on other tasks.") - val task_description: String? = null, - @Description("An enumeration indicating the type of task to be executed.") - val task_type: TaskType? = null, - @Description("A list of IDs of tasks that must be completed before this task can be executed. This defines upstream dependencies ensuring proper task order and information flow.") - var task_dependencies: List? = null, - @Description("A list of file paths specifying the input files required by this task. These may be outputs from dependent tasks, facilitating data transfer between tasks.") - val input_files: List? = null, - @Description("A list of file paths specifying the output files generated by this task. These may serve as inputs for subsequent tasks, enabling information sharing.") - val output_files: List? = null, - @Description("The current execution state of the task. Important for coordinating task execution and managing dependencies.") - var state: TaskState? = TaskState.Pending, - @Description("Only applicable in Foreach tasks - details specific to the Foreach task.") - val foreach_task: ForEachTask? = null, - @Description("Only applicable in CommandAutoFix tasks - details specific to the CommandAutoFix task.") - val execution_task: ExecutionTask? = null, + val tasksByID: Map? = null, ) - data class ForEachTask( - @Description("A list of items over which the ForEach task will iterate. (Only applicable for ForeachTask tasks) Can be used to process outputs from previous tasks.") - val foreach_items: List? = null, - @Description("A map of sub-task IDs to PlanTask objects to be executed for each item. (Only applicable for ForeachTask tasks) Allows for complex task dependencies and information flow within iterations.") - val foreach_subplan: Map? = null, - ) - - data class ExecutionTask( - @Description("The command line for the task (Only applicable in CommandAutoFix tasks).") - val command: List? = null, - @Description("The working directory relative to the root directory (e.g., \".\" or \"./subdir\") (Only applicable for CommandAutoFix and RunShellCommand tasks)") - val workingDir: String? = null, - ) - - private val taskBreakdownActor by lazy { planSettings.planningActor() } override fun promptSegment(): String { return """ @@ -81,29 +59,29 @@ class PlanningTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API ) { - @Suppress("NAME_SHADOWING") val task = agent.ui.newTask(false).apply { task.add(placeholder) } + val newTask = agent.ui.newTask(false).apply { add(placeholder) } fun toInput(s: String) = listOf( userMessage, - JsonUtil.toJson(plan), + JsonUtil.toJson(plan.entries.associate { it.key to it.value }), getPriorCode(planProcessingState), getInputFileCode(), s ).filter { it.isNotBlank() } val subPlan = if (planSettings.allowBlocking && !planSettings.autoFix) { - createSubPlanDiscussable(agent, task, userMessage, ::toInput, api).call().obj + createSubPlanDiscussable(newTask, userMessage, ::toInput, api, agent.ui).call().obj } else { - val design = taskBreakdownActor.answer( - toInput("Expand ${planTask.task_description ?: ""}"), + val design = planSettings.planningActor().answer( + toInput("Expand ${planTask?.task_description ?: ""}"), api = api ) render( withPrompt = PlanUtil.TaskBreakdownWithPrompt( - plan = filterPlan { design.obj } as TaskBreakdownResult, + plan = filterPlan { design.obj.tasksByID } ?: emptyMap(), planText = design.text, prompt = userMessage ), @@ -111,52 +89,51 @@ class PlanningTask( ) design.obj } - executeSubTasks(agent, userMessage, filterPlan { subPlan }, task, api) + executeSubTasks(agent, userMessage, filterPlan { subPlan.tasksByID } ?: emptyMap(), task, api) } private fun createSubPlanDiscussable( - agent: PlanCoordinator, task: SessionTask, userMessage: String, toInput: (String) -> List, - api: API - ): Discussable> { - return Discussable( - task = task, - userMessage = { "Expand ${planTask.task_description ?: ""}" }, - heading = "", - initialResponse = { it: String -> taskBreakdownActor.answer(toInput(it), api = api) }, - outputFn = { design: ParsedResponse -> - render( - withPrompt = PlanUtil.TaskBreakdownWithPrompt( - plan = filterPlan { design.obj } as TaskBreakdownResult, - planText = design.text, - prompt = userMessage - ), - ui = agent.ui - ) - }, - ui = agent.ui, - reviseResponse = { userMessages: List> -> - taskBreakdownActor.respond( - messages = userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } - .toTypedArray(), - input = toInput("Expand ${planTask.task_description ?: ""}\n${JsonUtil.toJson(this)}"), - api = api - ) - }, - ) - } + api: API, + ui: ApplicationInterface + ) = Discussable( + task = task, + userMessage = { "Expand ${planTask?.task_description ?: ""}" }, + heading = "", + initialResponse = { it: String -> planSettings.planningActor().answer(toInput(it), api = api) }, + outputFn = { design: ParsedResponse -> + render( + withPrompt = PlanUtil.TaskBreakdownWithPrompt( + plan = filterPlan { design.obj.tasksByID } ?: emptyMap(), + planText = design.text, + prompt = userMessage + ), + ui = ui + ) + }, + ui = ui, + reviseResponse = { userMessages: List> -> + planSettings.planningActor().respond( + messages = userMessages.map { ApiModel.ChatMessage(it.second, it.first.toContentList()) } + .toTypedArray(), + input = toInput("Expand ${planTask?.task_description ?: ""}\n${JsonUtil.toJson(this)}"), + api = api + ) + }, + ) + private fun executeSubTasks( agent: PlanCoordinator, userMessage: String, - subPlan: TaskBreakdownInterface, + subPlan: Map, parentTask: SessionTask, api: API ) { val subPlanTask = agent.ui.newTask(false) parentTask.add(subPlanTask.placeholder) - val subTasks = subPlan.tasksByID ?: emptyMap() + val subTasks = subPlan ?: emptyMap() val planProcessingState = PlanProcessingState(subTasks.toMutableMap()) agent.executePlan( task = subPlanTask, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt index f437cd6d..eb7cd1e0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt @@ -1,11 +1,32 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.RefactorTask.RefactorTaskData import org.slf4j.LoggerFactory class RefactorTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractAnalysisTask(planSettings, planTask) { + planTask: RefactorTaskData? +) : AbstractAnalysisTask(planSettings, planTask) { + class RefactorTaskData( + @Description("List of files to be refactored") + val filesToRefactor: List? = null, + @Description("Specific areas of focus for the refactoring (e.g., modularity, design patterns, naming conventions)") + val refactoringFocus: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.RefactorTask.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override val actorName: String = "Refactor" override val actorPrompt: String = """ Analyze the provided code and suggest refactoring to improve code structure, readability, and maintainability. Focus on: @@ -35,7 +56,6 @@ RefactorTask - Analyze and refactor existing code to improve structure, readabil """.trimMargin() } - companion object { private val log = LoggerFactory.getLogger(RefactorTask::class.java) } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt index ddb33191..f629653d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt @@ -1,13 +1,14 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.skyenet.apps.coding.CodingAgent -import com.simiacryptus.skyenet.apps.plan.PlanningTask.TaskBreakdownInterface +import com.simiacryptus.skyenet.apps.plan.RunShellCommandTask.RunShellCommandTaskData import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.interpreter.ProcessInterpreter import com.simiacryptus.skyenet.webui.session.SessionTask +import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory import java.io.File import java.util.concurrent.Semaphore @@ -15,24 +16,46 @@ import kotlin.reflect.KClass class RunShellCommandTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractTask(planSettings, planTask) { + planTask: RunShellCommandTaskData? +) : AbstractTask(planSettings, planTask) { + + class RunShellCommandTaskData( + @Description("The shell command to be executed") + val command: String? = null, + @Description("The working directory for the command execution") + val workingDir: String? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.RunShellCommand.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + val shellCommandActor by lazy { 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. - | - |Note: This task is for running simple and safe commands. Avoid executing commands that can cause harm to the system or compromise security. - """.trimMargin(), +Execute the following shell command(s) and provide the output. Ensure to handle any errors or exceptions gracefully. +Note: This task is for running simple and safe commands. Avoid executing commands that can cause harm to the system or compromise security. + """.trimMargin(), symbols = mapOf( "env" to (planSettings.env ?: emptyMap()), - "workingDir" to (planTask.execution_task?.workingDir?.let { File(it).absolutePath } ?: File(planSettings.workingDir).absolutePath), + "workingDir" to (planTask?.workingDir?.let { File(it).absolutePath } ?: File( + planSettings.workingDir + ).absolutePath), "language" to (planSettings.language ?: "bash"), - "command" to planSettings.command, + "command" to (planTask?.command ?: planSettings.command), ), - model = planSettings.getTaskSettings(planTask.task_type!!).model ?: planSettings.defaultModel, + model = planSettings.getTaskSettings(TaskType.valueOf(planTask?.task_type!!)).model + ?: planSettings.defaultModel, temperature = planSettings.temperature, ) } @@ -50,7 +73,7 @@ class RunShellCommandTask( agent: PlanCoordinator, taskId: String, userMessage: String, - plan: TaskBreakdownInterface, + plan: Map, planProcessingState: PlanProcessingState, task: SessionTask, api: API diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt index 8e84b592..a5a4b34f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt @@ -1,11 +1,34 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.SecurityAuditTask.SecurityAuditTaskData import org.slf4j.LoggerFactory class SecurityAuditTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractAnalysisTask(planSettings, planTask) { + planTask: SecurityAuditTaskData? +) : AbstractAnalysisTask(planSettings, planTask) { + + + class SecurityAuditTaskData( + @Description("List of files to be audited") + val filesToAudit: List? = null, + @Description("Specific areas of focus for the security audit") + val focusAreas: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.SecurityAudit.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override val actorName: String = "SecurityAudit" override val actorPrompt: String = """ Perform a comprehensive security audit for the provided code files. Analyze the code for: diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt index b027207f..2561fd28 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt @@ -1,30 +1,56 @@ package com.simiacryptus.skyenet.apps.plan +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver +import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.AbstractTask.TaskState +import com.simiacryptus.skyenet.apps.plan.CodeOptimizationTask.CodeOptimizationTaskData +import com.simiacryptus.skyenet.apps.plan.CodeReviewTask.CodeReviewTaskData +import com.simiacryptus.skyenet.apps.plan.CommandAutoFixTask.CommandAutoFixTaskData +import com.simiacryptus.skyenet.apps.plan.DocumentationTask.DocumentationTaskData +import com.simiacryptus.skyenet.apps.plan.FileModificationTask.FileModificationTaskData +import com.simiacryptus.skyenet.apps.plan.ForeachTask.ForeachTaskData +import com.simiacryptus.skyenet.apps.plan.InquiryTask.InquiryTaskData +import com.simiacryptus.skyenet.apps.plan.PerformanceAnalysisTask.PerformanceAnalysisTaskData +import com.simiacryptus.skyenet.apps.plan.PlanningTask.PlanningTaskData +import com.simiacryptus.skyenet.apps.plan.RefactorTask.RefactorTaskData +import com.simiacryptus.skyenet.apps.plan.RunShellCommandTask.RunShellCommandTaskData +import com.simiacryptus.skyenet.apps.plan.SecurityAuditTask.SecurityAuditTaskData +import com.simiacryptus.skyenet.apps.plan.TaskType.Companion.valueOf +import com.simiacryptus.skyenet.apps.plan.TestGenerationTask.TestGenerationTaskData import com.simiacryptus.util.DynamicEnum import com.simiacryptus.util.DynamicEnumDeserializer import com.simiacryptus.util.DynamicEnumSerializer @JsonDeserialize(using = TaskTypeDeserializer::class) @JsonSerialize(using = TaskTypeSerializer::class) -class TaskType(name: String) : DynamicEnum(name) { +class TaskType( + name: String, + val taskDataClass: Class +) : DynamicEnum>(name) { companion object { - private val taskConstructors = mutableMapOf AbstractTask>() - - val TaskPlanning = TaskType("TaskPlanning") - val Inquiry = TaskType("Inquiry") - val FileModification = TaskType("FileModification") - val Documentation = TaskType("Documentation") - val RunShellCommand = TaskType("RunShellCommand") - val CommandAutoFix = TaskType("CommandAutoFix") - val CodeReview = TaskType("CodeReview") - val TestGeneration = TaskType("TestGeneration") - val Optimization = TaskType("Optimization") - val SecurityAudit = TaskType("SecurityAudit") - val PerformanceAnalysis = TaskType("PerformanceAnalysis") - val RefactorTask = TaskType("RefactorTask") - val ForeachTask = TaskType("ForeachTask") + + private val taskConstructors = + mutableMapOf, (PlanSettings, PlanTaskBase?) -> AbstractTask>() + + val TaskPlanning = TaskType("TaskPlanning", PlanningTaskData::class.java) + val Inquiry = TaskType("Inquiry", InquiryTaskData::class.java) + val FileModification = TaskType("FileModification", FileModificationTaskData::class.java) + val Documentation = TaskType("Documentation", DocumentationTaskData::class.java) + val CodeReview = TaskType("CodeReview", CodeReviewTaskData::class.java) + val TestGeneration = TaskType("TestGeneration", TestGenerationTaskData::class.java) + val Optimization = TaskType("Optimization", CodeOptimizationTaskData::class.java) + val SecurityAudit = TaskType("SecurityAudit", SecurityAuditTaskData::class.java) + val PerformanceAnalysis = TaskType("PerformanceAnalysis", PerformanceAnalysisTaskData::class.java) + val RefactorTask = TaskType("RefactorTask", RefactorTaskData::class.java) + val RunShellCommand = TaskType("RunShellCommand", RunShellCommandTaskData::class.java) + val CommandAutoFix = TaskType("CommandAutoFix", CommandAutoFixTaskData::class.java) + val ForeachTask = TaskType("ForeachTask", ForeachTaskData::class.java) init { registerConstructor(CommandAutoFix) { settings, task -> CommandAutoFixTask(settings, task) } @@ -42,17 +68,31 @@ class TaskType(name: String) : DynamicEnum(name) { registerConstructor(TaskPlanning) { settings, task -> PlanningTask(settings, task) } } - private fun registerConstructor( - taskType: TaskType, - constructor: (PlanSettings, PlanningTask.PlanTask) -> AbstractTask + private fun registerConstructor( + taskType: TaskType, + constructor: (PlanSettings, T?) -> AbstractTask ) { - taskConstructors[taskType] = constructor + taskConstructors[taskType] = { settings : PlanSettings, task : PlanTaskBase? -> + constructor(settings, task as T?) + } register(taskType) } fun values() = values(TaskType::class.java) - fun getImpl(planSettings: PlanSettings, planTask: PlanningTask.PlanTask): AbstractTask { - val taskType = planTask.task_type ?: throw RuntimeException("Task type is null") + fun getImpl( + planSettings: PlanSettings, + planTask: PlanTaskBase? + ): AbstractTask { + val taskType = planTask?.task_type?.let { valueOf(it) } + ?: throw RuntimeException("Task type not specified") + return getImpl(planSettings, taskType, planTask) + } + + fun getImpl( + planSettings: PlanSettings, + taskType: TaskType<*>, + planTask: PlanTaskBase? = null + ): AbstractTask { if (!planSettings.getTaskSettings(taskType).enabled) { throw DisabledTaskException(taskType) } @@ -63,12 +103,67 @@ class TaskType(name: String) : DynamicEnum(name) { fun getAvailableTaskTypes(planSettings: PlanSettings) = values().filter { planSettings.getTaskSettings(it).enabled - }.map { getImpl(planSettings, PlanningTask.PlanTask(task_type = it)) } + } - fun valueOf(name: String): TaskType = valueOf(TaskType::class.java, name) - private fun register(taskType: TaskType) = register(TaskType::class.java, taskType) + fun valueOf(name: String): TaskType<*> = valueOf(TaskType::class.java, name) + private fun register(taskType: TaskType<*>) = register(TaskType::class.java, taskType) } } -class TaskTypeSerializer : DynamicEnumSerializer(TaskType::class.java) -class TaskTypeDeserializer : DynamicEnumDeserializer(TaskType::class.java) \ No newline at end of file +@JsonTypeIdResolver(PlanTaskTypeIdResolver::class) +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "task_type") +//@JsonDeserialize(using = PlanTaskBaseDeserializer::class) +abstract class PlanTaskBase( + @Description("An enumeration indicating the type of task to be executed. Must be a single value from the TaskType enum.") + val task_type: String? = null, + @Description("A detailed description of the specific task to be performed, including its role in the overall plan and its dependencies on other tasks.") + var task_description: String? = null, + @Description("A list of IDs of tasks that must be completed before this task can be executed. This defines upstream dependencies ensuring proper task order and information flow.") + var task_dependencies: List? = null, + @Description("A list of file paths specifying the input files required by this task. These may be outputs from dependent tasks, facilitating data transfer between tasks.") + val input_files: List? = null, + @Description("A list of file paths specifying the output files generated by this task. These may serve as inputs for subsequent tasks, enabling information sharing.") + val output_files: List? = null, + @Description("The current execution state of the task. Important for coordinating task execution and managing dependencies.") + var state: TaskState? = null +) + +class PlanTaskTypeIdResolver : TypeIdResolverBase() { + override fun idFromValue(value: Any) = when (value) { + is PlanTaskBase -> if (value.task_type != null) { + value.task_type + } else { + throw IllegalArgumentException("Unknown task type") + } + + else -> throw IllegalArgumentException("Unexpected value type: ${value.javaClass}") + } + + override fun idFromValueAndType(value: Any, suggestedType: Class<*>): String { + return idFromValue(value) + } + + override fun typeFromId(context: DatabindContext, id: String): JavaType { + val taskType = TaskType.valueOf(id.replace(" ", "")) + val subType = context.constructType(taskType.taskDataClass) + return subType + } + + override fun getMechanism(): JsonTypeInfo.Id { + return JsonTypeInfo.Id.CUSTOM + } +} + +class TaskTypeSerializer : DynamicEnumSerializer>(TaskType::class.java) +class TaskTypeDeserializer : DynamicEnumDeserializer>(TaskType::class.java) +class PlanTaskBaseDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PlanTaskBase { + val node = ctxt.readTree(p) + val taskType = node.get("task_type")?.asText() + if (taskType == null) { + throw JsonMappingException(p, "Missing task_type") + } + val baseTaskType = valueOf(taskType) + return ctxt.readValue(node.traverse(p.codec), baseTaskType.taskDataClass) + } +} diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt index 957e18c6..88b69e11 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt @@ -1,14 +1,36 @@ package com.simiacryptus.skyenet.apps.plan +import com.simiacryptus.jopenai.describe.Description +import com.simiacryptus.skyenet.apps.plan.TestGenerationTask.TestGenerationTaskData import org.slf4j.LoggerFactory class TestGenerationTask( planSettings: PlanSettings, - planTask: PlanningTask.PlanTask -) : AbstractAnalysisTask(planSettings, planTask) { + planTask: TestGenerationTaskData? +) : AbstractAnalysisTask(planSettings, planTask) { + + class TestGenerationTaskData( + @Description("List of files for which tests should be generated") + val filesToTest: List? = null, + @Description("List of input files or tasks to be examined when generating tests") + val inputReferences: List? = null, + task_description: String? = null, + task_dependencies: List? = null, + input_files: List? = null, + output_files: List? = null, + state: TaskState? = null + ) : PlanTaskBase( + task_type = TaskType.TestGeneration.name, + task_description = task_description, + task_dependencies = task_dependencies, + input_files = input_files, + output_files = output_files, + state = state + ) + override val actorName: String = "TestGeneration" override val actorPrompt: String = """ - |Generate comprehensive unit tests for the provided code files. The tests should: + Generate comprehensive unit tests for the provided code files. The tests should: |1. Cover all public methods and functions |2. Include both positive and negative test cases |3. Test edge cases and boundary conditions @@ -57,9 +79,13 @@ class TestGenerationTask( override fun promptSegment(): String { return """ - |TestGeneration - Generate unit tests for the specified code files - | ** Specify the files for which tests should be generated - | ** List input files/tasks to be examined when generating tests + TestGeneration - Generate unit tests for the specified code files + ** Specify the files for which tests should be generated using the 'filesToTest' field + ** List input files/tasks to be examined when generating tests using the 'inputReferences' field + ** The task will generate test files for each specified file in 'filesToTest' + ** Test files will be created in a 'test' directory parallel to the source files + ** Specify the files for which tests should be generated + ** List input files/tasks to be examined when generating tests """.trimMargin() } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/EncryptFiles.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EncryptFiles.kt similarity index 94% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/EncryptFiles.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/util/EncryptFiles.kt index 10561d55..f133daf2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/EncryptFiles.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EncryptFiles.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.webui.util +package com.simiacryptus.skyenet.util import com.simiacryptus.skyenet.core.platform.ApplicationServices import java.io.File diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtil.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/MarkdownUtil.kt similarity index 99% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtil.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/util/MarkdownUtil.kt index 0ef79b33..2f8990a1 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtil.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/MarkdownUtil.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.webui.util +package com.simiacryptus.skyenet.util import com.simiacryptus.skyenet.AgentPatterns.displayMapInTabs import com.simiacryptus.skyenet.webui.application.ApplicationInterface diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/OpenAPI.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/OpenAPI.kt similarity index 98% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/OpenAPI.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/util/OpenAPI.kt index f7a3e99a..ba0c27c2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/OpenAPI.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/OpenAPI.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.webui.util +package com.simiacryptus.skyenet.util import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/Selenium2S3.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/Selenium2S3.kt similarity index 99% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/Selenium2S3.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/util/Selenium2S3.kt index 4623d8e9..d8d576f1 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/Selenium2S3.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/Selenium2S3.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.webui.util +package com.simiacryptus.skyenet.util import com.simiacryptus.skyenet.core.platform.ApplicationServices.cloud import com.simiacryptus.skyenet.core.util.Selenium @@ -34,7 +34,7 @@ open class Selenium2S3( var loadImages: Boolean = false open val driver: WebDriver by lazy { chromeDriver(loadImages = loadImages).apply { - Companion.setCookies( + setCookies( this, cookies ) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/TensorflowProjector.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/TensorflowProjector.kt similarity index 93% rename from webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/TensorflowProjector.kt rename to webui/src/main/kotlin/com/simiacryptus/skyenet/util/TensorflowProjector.kt index 53ed506d..c38df47b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/util/TensorflowProjector.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/TensorflowProjector.kt @@ -1,9 +1,11 @@ -package com.simiacryptus.skyenet.webui.util +package com.simiacryptus.skyenet.util import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.models.ApiModel +import com.simiacryptus.jopenai.models.ApiModel.EmbeddingRequest import com.simiacryptus.jopenai.models.EmbeddingModels -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices.cloud import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.StorageInterface @@ -21,7 +23,7 @@ class TensorflowProjector( private fun toVectorMap(vararg words: String): Map { val vectors = words.map { word -> word to (api as OpenAIClient).createEmbedding( - com.simiacryptus.jopenai.ApiModel.EmbeddingRequest( + EmbeddingRequest( model = EmbeddingModels.AdaEmbedding.modelName, input = word, ) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt index 8c666d35..8381541f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationDirectory.kt @@ -2,13 +2,13 @@ package com.simiacryptus.skyenet.webui.application import com.simiacryptus.jopenai.util.ClientUtil -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.OutputInterceptor import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.ApplicationServicesConfig.isLocked import com.simiacryptus.skyenet.webui.chat.ChatServer import com.simiacryptus.skyenet.webui.servlet.* -import com.simiacryptus.skyenet.webui.util.Selenium2S3 +import com.simiacryptus.skyenet.util.Selenium2S3 import jakarta.servlet.DispatcherType import jakarta.servlet.MultipartConfigElement import jakarta.servlet.Servlet @@ -57,7 +57,6 @@ abstract class ApplicationDirectory( open val proxyHttpServlet: HttpServlet = ProxyHttpServlet() open val apiKeyServlet: HttpServlet = ApiKeyServlet() open val welcomeServlet: HttpServlet = WelcomeServlet(this) -// abstract val toolServlet: ToolServlet? open fun authenticatedWebsite(): OAuthBase? = OAuthGoogle( redirectUri = "$domainName/oauth2callback", @@ -134,6 +133,9 @@ abstract class ApplicationDirectory( newWebAppContext("/", welcomeResources, "welcome", welcomeServlet).let { authenticatedWebsite()?.configure(it, false) ?: it }, + newWebAppContext("/api", welcomeServlet).let { + authenticatedWebsite()?.configure(it, false) ?: it + }, ).toTypedArray() + childWebApps.map { newWebAppContext(it.path, it.server) } @@ -184,7 +186,7 @@ abstract class ApplicationDirectory( protected open fun newWebAppContext(path: String, server: ChatServer): WebAppContext { val baseResource = server.baseResource ?: throw IllegalStateException("No base resource") - val webAppContext = newWebAppContext(path, baseResource, resourceBase = "applicaton") + val webAppContext = newWebAppContext(path, baseResource, resourceBase = "application") server.configure(webAppContext) log.info("WebAppContext configured for path: $path with ChatServer") return webAppContext diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt index d2fc7c83..ad3adff5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.webui.application import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.* import com.simiacryptus.skyenet.core.platform.ApplicationServices.authenticationManager import com.simiacryptus.skyenet.core.platform.ApplicationServices.authorizationManager diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationSocketManager.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationSocketManager.kt index 70ea0552..14f8a898 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationSocketManager.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationSocketManager.kt @@ -25,7 +25,7 @@ abstract class ApplicationSocketManager( user = socket.user, userMessage = userMessage, socketManager = this, - api = ApplicationServices.clientManager.getClient( + api = ApplicationServices.clientManager.getChatClient( session, socket.user ) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt index 5fc5e474..43a7097d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt @@ -1,8 +1,7 @@ package com.simiacryptus.skyenet.webui.chat -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.jopenai.util.ClientUtil.toContentList import com.simiacryptus.skyenet.Retryable @@ -12,7 +11,7 @@ import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.session.SocketManagerBase -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.skyenet.util.MarkdownUtil open class ChatSocketManager( session: Session, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ApiKeyServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ApiKeyServlet.kt index 0063c0e8..423fdd3f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ApiKeyServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ApiKeyServlet.kt @@ -1,9 +1,9 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.APIProvider import com.simiacryptus.jopenai.models.OpenAIModel -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.ApplicationServices.userSettingsManager import com.simiacryptus.skyenet.core.platform.ApplicationServicesConfig.dataStorageRoot diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/AppInfoServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/AppInfoServlet.kt index 57e6453c..bc919931 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/AppInfoServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/AppInfoServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/DeleteSessionServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/DeleteSessionServlet.kt index 7e180b37..54d143eb 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/DeleteSessionServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/DeleteSessionServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.ApplicationServices.authorizationManager import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt index 34530260..9629499a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ProxyHttpServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionSettingsServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionSettingsServlet.kt index 0e925605..104b6f6b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionSettingsServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionSettingsServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices.authenticationManager import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.webui.application.ApplicationServer diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt index beaa618c..953cc5e3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.ApplicationServices.authenticationManager import com.simiacryptus.skyenet.core.platform.ApplicationServices.authorizationManager @@ -13,7 +13,7 @@ import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.core.util.Selenium import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.application.ApplicationServer.Companion.getCookie -import com.simiacryptus.skyenet.webui.util.Selenium2S3 +import com.simiacryptus.skyenet.util.Selenium2S3 import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UsageServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UsageServlet.kt index 2abe563a..2206464d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UsageServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UsageServlet.kt @@ -1,5 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.models.OpenAIModel import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.Session @@ -29,7 +30,7 @@ class UsageServlet : HttpServlet() { private fun serve( resp: HttpServletResponse, - usage: Map + usage: Map ) { val totalPromptTokens = usage.values.sumOf { it.prompt_tokens } val totalCompletionTokens = usage.values.sumOf { it.completion_tokens } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserInfoServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserInfoServlet.kt index 67395ea8..f9d120ad 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserInfoServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserInfoServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.webui.servlet -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationServer.Companion.getCookie diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserSettingsServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserSettingsServlet.kt index 9266f362..34373d0e 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserSettingsServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/UserSettingsServlet.kt @@ -2,7 +2,7 @@ package com.simiacryptus.skyenet.webui.servlet import com.simiacryptus.jopenai.models.APIProvider import com.simiacryptus.jopenai.models.ChatModels -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.UserSettingsInterface.UserSettings import com.simiacryptus.skyenet.webui.application.ApplicationServer.Companion.getCookie diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/WelcomeServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/WelcomeServlet.kt index dc6bb26f..abbefee1 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/WelcomeServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/WelcomeServlet.kt @@ -1,32 +1,86 @@ package com.simiacryptus.skyenet.webui.servlet +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.simiacryptus.skyenet.core.platform.ApplicationServices import com.simiacryptus.skyenet.core.platform.ApplicationServices.authorizationManager import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType -import com.simiacryptus.skyenet.core.platform.StorageInterface -import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationDirectory -import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.application.ApplicationServer.Companion.getCookie -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.intellij.lang.annotations.Language +import org.slf4j.LoggerFactory import java.nio.file.NoSuchFileException open class WelcomeServlet(private val parent: ApplicationDirectory) : HttpServlet() { override fun doGet(req: HttpServletRequest?, resp: HttpServletResponse?) { - val user = ApplicationServices.authenticationManager.getUser(req!!.getCookie()) - val requestURI = req.requestURI ?: "/" - resp?.contentType = when (requestURI) { - "/" -> "text/html" - else -> ApplicationServer.getMimeType(requestURI) + val path = req?.servletPath ?: "/" + when { + path == "/" || path == "/index.html" -> serveStaticPage(resp) + path == "/user" -> serveUserInfo(req!!, resp!!) + path == "/apps" -> serveAppList(req!!, resp) + else -> serveResource(req, resp, path) + } + } + + override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) { + val requestURI = req?.requestURI ?: "/" + when { + requestURI.startsWith("/userSettings") -> parent.userSettingsServlet.service(req!!, resp!!) + else -> resp?.sendError(404) + } + } + + + private fun serveStaticPage(resp: HttpServletResponse?) { + resp?.contentType = "text/html" + val inputStream = this::class.java.getResourceAsStream("/welcome/welcome.html") + inputStream?.copyTo(resp?.outputStream!!) + } + + private fun serveUserInfo(req: HttpServletRequest, resp: HttpServletResponse) { + val user = ApplicationServices.authenticationManager.getUser(req.getCookie()) + val mapper = jacksonObjectMapper() + resp.contentType = "application/json" + mapper.writeValue(resp.outputStream, user) + } + + private fun serveAppList(req: HttpServletRequest, resp: HttpServletResponse?) { + val user = ApplicationServices.authenticationManager.getUser(req.getCookie()) + val authorizedApps = parent.childWebApps.filter { + authorizationManager.isAuthorized(it.server.javaClass, user, OperationType.Read) + }.map { + val canRead = authorizationManager.isAuthorized(it.server.javaClass, user, OperationType.Read) + val canWrite = authorizationManager.isAuthorized(it.server.javaClass, user, OperationType.Write) + val canWritePublic = authorizationManager.isAuthorized(it.server.javaClass, user, OperationType.Public) + + mapOf( + "path" to it.path, + "thumbnail" to it.thumbnail, + "applicationName" to it.server.applicationName, + "javaClass" to it.server.javaClass, + "canRead" to canRead, + "canWrite" to canWrite, + "canWritePublic" to canWritePublic, + ) } + val mapper = jacksonObjectMapper() + mapper.enable(SerializationFeature.INDENT_OUTPUT) + resp?.contentType = "application/json" + lateinit var valueAsString: String + try { + valueAsString = mapper.writeValueAsString(authorizedApps) + resp?.outputStream?.write(valueAsString.toByteArray()) + } catch (e: Exception) { + log.error("Error serving app list: $valueAsString", e) + } + } + + private fun serveResource(req: HttpServletRequest?, resp: HttpServletResponse?, requestURI: String) { when { - requestURI == "/" -> resp?.writer?.write(homepage(user).trimIndent()) - requestURI == "/index.html" -> resp?.writer?.write(homepage(user).trimIndent()) requestURI.startsWith("/userInfo") -> { parent.userInfoServlet.service(req, resp!!) } @@ -40,152 +94,16 @@ open class WelcomeServlet(private val parent: ApplicationDirectory) : } } - override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) { - val requestURI = req?.requestURI ?: "/" - when { - requestURI.startsWith("/userSettings") -> parent.userSettingsServlet.service(req!!, resp!!) - else -> resp?.sendError(404) - } - } - @Language("Markdown") protected open val welcomeMarkdown = """""".trimIndent() @Language("Markdown") protected open val postAppMarkdown = """""".trimIndent() - @Language("HTML") - protected open fun homepage(user: User?) = """ - - - - - - SimiaCryptus Skyenet Apps - - - - - - - - - - - - - - -
- - -
- -
- -
- - ${renderMarkdown(welcomeMarkdown, tabs = false)} - - - ${parent.childWebApps.joinToString("\n") { app -> appRow(app, user) }} -
- - ${renderMarkdown(postAppMarkdown, tabs = false)} - - - - - - - """.trimIndent() - - protected open fun appRow( - app: ApplicationDirectory.ChildWebApp, - user: User? - ) = when { - !authorizationManager.isAuthorized(app.server.javaClass, user, OperationType.Read) -> "" - else -> """ - - - ${if(!app.thumbnail.isNullOrBlank()) """${app.server.applicationName}""" else ""} - ${app.server.applicationName} - - - List Sessions - - - ${ - when { - !authorizationManager.isAuthorized(app.server.javaClass, user, OperationType.Public) -> "" - else -> - """New Public Session""" - } - } - - - ${ - when { - !authorizationManager.isAuthorized(app.server.javaClass, user, OperationType.Write) -> "" - else -> - """New Private Session""" - } - } - - - """.trimIndent() + companion object { + val log = LoggerFactory.getLogger(WelcomeServlet::class.java) } - private fun imageElement(app: ApplicationDirectory.ChildWebApp) = """ - ${app.server.applicationName} - """.trimIndent() } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt index 2baeed13..b9851bda 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SessionTask.kt @@ -5,7 +5,7 @@ import com.simiacryptus.jopenai.proxy.ValidatedObject import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.core.platform.StorageInterface.Companion.long64 import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.awt.image.BufferedImage import java.io.File diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt index 7d152de5..0070719c 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/session/SocketManagerBase.kt @@ -4,7 +4,7 @@ import com.simiacryptus.skyenet.core.platform.* import com.simiacryptus.skyenet.core.platform.ApplicationServices.clientManager import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType import com.simiacryptus.skyenet.webui.chat.ChatSocket -import com.simiacryptus.skyenet.webui.util.MarkdownUtil +import com.simiacryptus.skyenet.util.MarkdownUtil import org.slf4j.LoggerFactory import java.io.File import java.net.URLDecoder diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt index 13559b6b..13b2efe3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt @@ -1,7 +1,7 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.ChatClient import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent @@ -12,7 +12,7 @@ import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.util.* diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/FilePatchTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/FilePatchTestApp.kt index b2f55d6f..47bc4954 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/FilePatchTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/FilePatchTestApp.kt @@ -2,7 +2,7 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel +import com.simiacryptus.jopenai.models.ApiModel import com.simiacryptus.jopenai.OpenAIClient import com.simiacryptus.skyenet.core.actors.CodingActor import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent @@ -15,7 +15,7 @@ import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer import com.simiacryptus.skyenet.webui.application.ApplicationSocketManager import com.simiacryptus.skyenet.webui.session.SocketManager -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.awt.Desktop import java.nio.file.Files diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt index ff39b1ae..f306835f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ImageActorTestApp.kt @@ -8,7 +8,7 @@ import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory open class ImageActorTestApp( diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt index 7ac00185..e39a15ea 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/ParsedActorTestApp.kt @@ -2,14 +2,14 @@ package com.simiacryptus.skyenet.webui.test import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ChatClient -import com.simiacryptus.jopenai.util.JsonUtil +import com.simiacryptus.util.JsonUtil import com.simiacryptus.skyenet.core.actors.ParsedActor import com.simiacryptus.skyenet.core.platform.ClientManager import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory open class ParsedActorTestApp( diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt index 1a072111..af95ece3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/SimpleActorTestApp.kt @@ -8,7 +8,7 @@ import com.simiacryptus.skyenet.core.platform.Session import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.webui.application.ApplicationInterface import com.simiacryptus.skyenet.webui.application.ApplicationServer -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory open class SimpleActorTestApp( diff --git a/webui/src/main/resources/application/uiHandlers.js b/webui/src/main/resources/application/uiHandlers.js index 076c1647..81e9396c 100644 --- a/webui/src/main/resources/application/uiHandlers.js +++ b/webui/src/main/resources/application/uiHandlers.js @@ -1,4 +1,5 @@ import {closeModal, findAncestor, getSessionId, showModal, toggleVerbose} from './functions.js'; +import {queueMessage} from "./chat.js"; function isCtrlOrCmd(event) { return event.ctrlKey || (navigator.platform.toLowerCase().indexOf('mac') !== -1 && event.metaKey); @@ -93,4 +94,8 @@ function handleFilesClick(event) { const sessionId = getSessionId(); const url = "fileIndex/" + sessionId + "/"; window.open(url, "_blank"); +} + +function handleMessageAction(messageId, action) { + queueMessage('!' + messageId + ',' + action); } \ No newline at end of file diff --git a/webui/src/main/resources/welcome/about.js b/webui/src/main/resources/welcome/about.js index af218555..7bbdce39 100644 --- a/webui/src/main/resources/welcome/about.js +++ b/webui/src/main/resources/welcome/about.js @@ -1,3 +1,5 @@ +import {showModal} from './functions.js'; + document.addEventListener('DOMContentLoaded', () => { const privacyLink = document.getElementById('privacy'); const tosLink = document.getElementById('tos'); diff --git a/webui/src/main/resources/welcome/functions.js b/webui/src/main/resources/welcome/functions.js index 3924bf8c..610ce334 100644 --- a/webui/src/main/resources/welcome/functions.js +++ b/webui/src/main/resources/welcome/functions.js @@ -15,14 +15,14 @@ async function fetchData(endpoint) { } -function showModal(endpoint) { +export function showModal(endpoint) { fetchData(endpoint).then(r => { const modal = document.getElementById('modal'); if (modal) modal.style.display = 'block'; }); } -function closeModal() { +export function closeModal() { const modal = document.getElementById('modal'); if (modal) modal.style.display = 'none'; } diff --git a/webui/src/main/resources/welcome/main.js b/webui/src/main/resources/welcome/main.js index 8ee9d88d..a037881a 100644 --- a/webui/src/main/resources/welcome/main.js +++ b/webui/src/main/resources/welcome/main.js @@ -1,17 +1,9 @@ -let messageMap = {}; -let singleInput = false; -let stickyInput = false; +import {closeModal, showModal} from "./functions.js"; + let loadImages = "true"; -let showMenubar = true; document.addEventListener('DOMContentLoaded', () => { if (typeof mermaid !== 'undefined') mermaid.run(); - applyToAllSvg(); - - // Set a timer to periodically apply svgPanZoom to all SVG elements - setInterval(() => { - applyToAllSvg(); - }, 5000); // Adjust the interval as needed // Restore the selected tabs from localStorage before adding event listeners document.querySelectorAll('.tabs-container').forEach(tabsContainer => { @@ -68,96 +60,12 @@ document.addEventListener('DOMContentLoaded', () => { const userUsageLink = document.getElementById('user-usage'); const logoutLink = document.getElementById('logout'); - const form = document.getElementById('main-input'); - const messageInput = document.getElementById('chat-input'); - window.addEventListener('click', (event) => { if (event.target === document.getElementById('modal')) { closeModal(); } }); - if (form) form.addEventListener('submit', (event) => { - event.preventDefault(); - send(messageInput.value); - messageInput.value = ''; - }); - - if (messageInput) { - messageInput.addEventListener('keydown', (event) => { - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - form.dispatchEvent(new Event('submit')); - } - }); - let originalScrollHeight = messageInput.scrollHeight; - messageInput.style.height = (messageInput.scrollHeight) + 'px'; - let postEditScrollHeight = messageInput.scrollHeight; - let heightAdjustment = postEditScrollHeight - originalScrollHeight; - messageInput.style.height = ''; - messageInput.addEventListener('input', function () { - // Reset the height to a single row to get the scroll height for the current content - this.style.height = 'auto'; - // Set the height to the scroll height, which represents the height of the content - this.style.height = (this.scrollHeight - heightAdjustment) + 'px'; - - // Get the computed style for the element - const computedStyle = window.getComputedStyle(this); - // Get the line height, check if it's 'normal', and calculate it based on the font size if needed - let lineHeight = computedStyle.lineHeight; - if (lineHeight === 'normal') { - // Use a typical browser default multiplier for 'normal' line-height - lineHeight = parseInt(computedStyle.fontSize) * 1.2; - } else { - lineHeight = parseInt(lineHeight); - } - - const maxLines = 20; - if (this.scrollHeight > lineHeight * maxLines) { - this.style.height = (lineHeight * maxLines) + 'px'; - this.style.overflowY = 'scroll'; // Enable vertical scrolling - } else { - this.style.overflowY = 'hidden'; // Hide the scrollbar when not needed - } - }); - messageInput.focus(); - } - - document.body.addEventListener('click', (event) => { - const target = event.target; - const hrefLink = findAncestor(target, '.href-link'); - if (hrefLink) { - const messageId = hrefLink.getAttribute('data-id'); - if (messageId && messageId !== '' && messageId !== null) send('!' + messageId + ',link'); - } else { - const playButton = findAncestor(target, '.play-button'); - if (playButton) { - const messageId = playButton.getAttribute('data-id'); - if (messageId && messageId !== '' && messageId !== null) send('!' + messageId + ',run'); - } else { - const regenButton = findAncestor(target, '.regen-button'); - if (regenButton) { - const messageId = regenButton.getAttribute('data-id'); - if (messageId && messageId !== '' && messageId !== null) send('!' + messageId + ',regen'); - } else { - const cancelButton = findAncestor(target, '.cancel-button'); - if (cancelButton) { - const messageId = cancelButton.getAttribute('data-id'); - if (messageId && messageId !== '' && messageId !== null) send('!' + messageId + ',stop'); - } else { - const textSubmitButton = findAncestor(target, '.text-submit-button'); - if (textSubmitButton) { - const messageId = textSubmitButton.getAttribute('data-id'); - const text = document.querySelector('.reply-input[data-id="' + messageId + '"]').value; - // url escape the text - const escapedText = encodeURIComponent(text); - if (messageId && messageId !== '' && messageId !== null) send('!' + messageId + ',userTxt,' + escapedText); - } - } - } - } - } - }); fetch('/userInfo') .then(response => { diff --git a/webui/src/main/resources/welcome/welcome.html b/webui/src/main/resources/welcome/welcome.html new file mode 100644 index 00000000..70bdbc93 --- /dev/null +++ b/webui/src/main/resources/welcome/welcome.html @@ -0,0 +1,83 @@ + + + + + + SimiaCryptus Skyenet Apps + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ + + +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/webui/src/main/resources/welcome/welcome.js b/webui/src/main/resources/welcome/welcome.js new file mode 100644 index 00000000..b2ad7cfd --- /dev/null +++ b/webui/src/main/resources/welcome/welcome.js @@ -0,0 +1,112 @@ +import {showModal} from "./functions.js"; + +document.addEventListener('DOMContentLoaded', function() { + fetchUserInfo(); + fetchAppList(); + fetchMarkdownContent(); + + document.querySelector(".close").onclick = function() { + document.getElementById("modal").style.display = "none"; + } +}); + +function fetchUserInfo() { + fetch('/api/user') + .then(response => response.json()) + .then(user => { + if (user) { + document.getElementById('login').style.display = 'none'; + const usernameElem = document.getElementById('username'); + usernameElem.textContent = user.username; + usernameElem.style.visibility = 'visible'; + document.getElementById('user-settings').style.visibility = 'visible'; + document.getElementById('user-usage').style.visibility = 'visible'; + document.getElementById('logout').style.visibility = 'visible'; + } + }) + .catch(error => console.error('Error fetching user info:', error)); +} + +function fetchAppList() { + fetch('/api/apps') + .then(response => response.json()) + .then(apps => { + const appList = document.getElementById('application-list'); + apps.forEach(app => { + const row = document.createElement('tr'); + + const nameCell = document.createElement('td'); + if(app.thumbnail) { + const img = document.createElement('img'); + img.src = app.thumbnail; + img.alt = app.applicationName; + img.className = 'app-thumbnail'; + img.onclick = () => showImageModal(app.thumbnail); + nameCell.appendChild(img); + } + const nameText = document.createTextNode(app.applicationName); + nameCell.appendChild(nameText); + row.appendChild(nameCell); + + const sessionsCell = document.createElement('td'); + const sessionsLink = document.createElement('a'); + sessionsLink.href = 'javascript:void(0);'; + sessionsLink.textContent = 'List Sessions'; + sessionsLink.onclick = () => showModal(`${app.path}/sessions`); + sessionsCell.appendChild(sessionsLink); + row.appendChild(sessionsCell); + + if(app.canWritePublic) { + const publicCell = document.createElement('td'); + const publicLink = document.createElement('a'); + publicLink.className = 'new-session-link'; + publicLink.href = `${app.path}/#${generateGlobalSessionId()}`; + publicLink.textContent = 'New Public Session'; + publicCell.appendChild(publicLink); + row.appendChild(publicCell); + } else { + row.appendChild(document.createElement('td')); + } + + if(app.canWrite) { + const privateCell = document.createElement('td'); + const privateLink = document.createElement('a'); + privateLink.className = 'new-session-link'; + privateLink.href = `${app.path}/#${generateUserSessionId()}`; + privateLink.textContent = 'New Private Session'; + privateCell.appendChild(privateLink); + row.appendChild(privateCell); + } else { + row.appendChild(document.createElement('td')); + } + + appList.appendChild(row); + }); + }) + .catch(error => console.error('Error fetching app list:', error)); +} +function generateGlobalSessionId() { + const date = new Date(); + const yyyyMMdd = date.toISOString().slice(0, 10).replace(/-/g, ""); + return `G-${yyyyMMdd}-${generateRandomId()}`; +} +function generateUserSessionId() { + const date = new Date(); + const yyyyMMdd = date.toISOString().slice(0, 10).replace(/-/g, ""); + return `U-${yyyyMMdd}-${generateRandomId()}`; +} +function generateRandomId() { + return Math.random().toString(36).substr(2, 4); +} + +function fetchMarkdownContent() { + // Implement fetching and rendering of welcomeMarkdown and postAppMarkdown + // This could be done via additional API endpoints if necessary +} + +function showImageModal(src) { + const modal = document.getElementById("modal"); + const modalContent = document.getElementById("modal-content"); + modalContent.innerHTML = ''; + modal.style.display = "block"; +} \ No newline at end of file diff --git a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt index bf4397a7..2f5fbfe1 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt @@ -21,7 +21,6 @@ import com.simiacryptus.skyenet.groovy.GroovyInterpreter import com.simiacryptus.skyenet.kotlin.KotlinInterpreter import com.simiacryptus.skyenet.scala.ScalaLocalInterpreter import com.simiacryptus.skyenet.webui.test.* -import java.io.File object ActorTestAppServer : com.simiacryptus.skyenet.webui.application.ApplicationDirectory(port = 8082) { diff --git a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtilTest.kt b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtilTest.kt index 3fe67d80..fded8efc 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtilTest.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/util/MarkdownUtilTest.kt @@ -1,5 +1,6 @@ package com.simiacryptus.skyenet.webui.util +import com.simiacryptus.skyenet.util.MarkdownUtil import com.vladsch.flexmark.util.data.MutableDataSet import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test