From 7e828b846d5ac2034e7b5eb263c2334bfb4a253d Mon Sep 17 00:00:00 2001 From: Andrew Charneski Date: Wed, 15 May 2024 21:07:45 -0500 Subject: [PATCH] 1.0.71 (#76) * 1.0.71 * wip * wip --- .../skyenet/core/actors/ActorSystem.kt | 5 +- gradle.properties | 2 +- .../com/simiacryptus/skyenet/Discussable.kt | 39 +- .../com/simiacryptus/skyenet/TabbedDisplay.kt | 25 +- .../skyenet/apps/coding/ShellToolAgent.kt | 5 +- .../skyenet/apps/coding/ToolAgent.kt | 437 ------------------ .../skyenet/apps/general/WebDevApp.kt | 21 +- .../webui/application/ApplicationDirectory.kt | 63 +-- .../skyenet/webui/servlet/ToolServlet.kt | 424 ----------------- .../skyenet/webui/servlet/WelcomeServlet.kt | 4 +- .../webui/session/SocketManagerBase.kt | 20 +- webui/src/main/resources/application/tabs.js | 6 +- .../skyenet/webui/ActorTestAppServer.kt | 3 +- 13 files changed, 113 insertions(+), 941 deletions(-) delete mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ToolAgent.kt delete mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ToolServlet.kt 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 4d5f0cb9..8f86d07a 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 @@ -55,8 +55,9 @@ open class ActorSystem>( else -> throw RuntimeException("Unknown actor type: ${baseActor.javaClass}") } } catch (e: Throwable) { - log.warn("Error creating actor $actor", e) - actors[actor.name]!! + val baseActor = actors[actor.name]!! + log.warn("Error creating actor $actor, returning $baseActor", e) + baseActor } } } diff --git a/gradle.properties b/gradle.properties index 0229f336..4428ca29 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.0.70 +libraryVersion = 1.0.71 gradleVersion = 7.6.1 diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt index e1c04288..13c30bb7 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt @@ -23,43 +23,48 @@ class Discussable( val tabs = object : TabbedDisplay(task) { override fun renderTabButtons() = """ - |
- |${ +
+${ tabs.withIndex().joinToString("\n") { (index: Int, t: Pair) -> """""" } } - |${ +${ ui.hrefLink("♻") { - val idx: Int = size val newTask = ui.newTask(false) val header = newTask.header("Retrying...") - this[label(idx)] = newTask.placeholder + val idx: Int = size + this.set(label(idx), newTask.placeholder) main(idx, newTask) this.selectedTab = idx header?.clear() newTask.complete() } } - |
+
""".trimMargin() } private val acceptGuard = AtomicBoolean(false) private fun main(tabIndex: Int, task: SessionTask) { + log.info("Starting main function for tabIndex: $tabIndex") try { val history = mutableListOf>() val userMessage = userMessage() + log.info("User message: $userMessage") history.add(userMessage to Role.user) val design = initialResponse(userMessage) + log.info("Initial response generated: $design") val rendered = outputFn(design) + log.info("Rendered output: $rendered") history.add(rendered to Role.assistant) val tabContent = task.add(rendered)!! val feedbackForm = feedbackForm(tabIndex, tabContent, design, history, task) tabContent?.append("\n" + feedbackForm.placeholder) task.complete() } catch (e: Throwable) { + log.error("Error in main function", e) task.error(ui, e) task.complete(ui.hrefLink("🔄 Retry") { main(tabIndex = tabIndex, task = task) @@ -74,6 +79,7 @@ class Discussable( history: List>, task: SessionTask, ) = ui.newTask(false).apply { + log.info("Creating feedback form for tabIndex: $tabIndex") val feedbackSB = add("
")!! feedbackSB.clear() feedbackSB.append( @@ -94,6 +100,7 @@ class Discussable( feedbackSB: StringBuilder, feedbackTask: SessionTask, ) = ui.hrefLink("Accept", classname = "href-link cmd-button") { + log.info("Accept link clicked for tabIndex: $tabIndex") feedbackSB.clear() feedbackTask.complete() accept(tabIndex, tabContent, design) @@ -109,6 +116,7 @@ class Discussable( ): String { val feedbackGuard = AtomicBoolean(false) return ui.textInput { userResponse -> + log.info("User response received: $userResponse") if (feedbackGuard.getAndSet(true)) return@textInput val prev = feedbackSB.toString() try { @@ -116,6 +124,7 @@ class Discussable( feedbackTask.complete() feedback(tabContent, userResponse, history, design, task) } catch (e: Exception) { + log.error("Error processing user feedback", e) task.error(ui, e) feedbackSB.set(prev) feedbackTask.complete() @@ -133,16 +142,18 @@ class Discussable( design: T, task: SessionTask, ) { + log.info("Processing feedback for user response: $userResponse") var history = history history = history + (userResponse to Role.user) val newValue = (tabContent.toString() - + "
" - + renderMarkdown(userResponse, ui = ui) - + "
") + + "
" + + renderMarkdown(userResponse, ui = ui) + + "
") tabContent.set(newValue) val stringBuilder = task.add("Processing...") tabs.update() val newDesign = reviseResponse(history) + log.info("Revised design: $newDesign") val newTask = ui.newTask(root = false) tabContent.set(newValue + "\n" + newTask.placeholder) tabs.update() @@ -162,6 +173,7 @@ class Discussable( } private fun accept(tabIndex: Int?, tabContent: StringBuilder, design: T) { + log.info("Accepting design for tabIndex: $tabIndex") if (acceptGuard.getAndSet(true)) { return } @@ -173,6 +185,7 @@ class Discussable( tabs.update() } } catch (e: Exception) { + log.error("Error accepting design", e) task.error(ui, e) acceptGuard.set(false) throw e @@ -182,21 +195,27 @@ class Discussable( } override fun call(): T { + log.info("Calling Discussable with heading: $heading") task.echo(heading) val idx = tabs.size val newTask = ui.newTask(false) val header = newTask.header("Processing...") tabs[tabs.label(idx)] = newTask.placeholder main(idx, newTask) + tabs.selectedTab = idx header?.clear() newTask.complete() semaphore.acquire() + log.info("Returning result from Discussable") return atomicRef.get() } + companion object { + private val log = org.slf4j.LoggerFactory.getLogger(Discussable::class.java) + } } fun java.lang.StringBuilder.set(newValue: String) { clear() append(newValue) -} +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt index 109e2674..d07b02a0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt @@ -23,7 +23,10 @@ open class TabbedDisplay(
""".trimIndent() - val container: StringBuilder by lazy { task.add(render())!! } + val container: StringBuilder by lazy { + log.debug("Initializing container with rendered content") + task.add(render())!! + } open fun renderTabButtons() = """
${ @@ -50,6 +53,7 @@ open class TabbedDisplay( operator fun set(name: String, content: String) = when (val index = find(name)) { null -> { + log.debug("Adding new tab: $name") val stringBuilder = StringBuilder(content) tabs.add(name to stringBuilder) update() @@ -57,6 +61,7 @@ open class TabbedDisplay( } else -> { + log.debug("Updating existing tab: $name") val stringBuilder = tabs[index].second stringBuilder.clear() stringBuilder.append(content) @@ -72,29 +77,21 @@ open class TabbedDisplay( } open fun clear() { + log.debug("Clearing all tabs") tabs.clear() update() } open fun update() { + log.debug("Updating container content") if (container != null) synchronized(container) { + if (tabs.isNotEmpty() && (selectedTab < 0 || selectedTab >= tabs.size)) { + selectedTab = 0 + } container.clear() container.append(render()) - ensureActiveTab() } task.complete() } - private fun ensureActiveTab() { - if (tabs.isNotEmpty() && (selectedTab < 0 || selectedTab >= tabs.size)) { - selectedTab = 0 - } - tabs.forEachIndexed { index, _ -> - if (index == selectedTab) { - tabs[index].second.append("active") - } else { - tabs[index].second.clear() - } - } - } } \ No newline at end of file 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 4b172238..259c9d79 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 @@ -17,7 +17,6 @@ import com.simiacryptus.skyenet.core.platform.* import com.simiacryptus.skyenet.interpreter.Interpreter import com.simiacryptus.skyenet.kotlin.KotlinInterpreter import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import com.simiacryptus.skyenet.webui.servlet.ToolServlet import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown import com.simiacryptus.skyenet.webui.util.OpenAPI @@ -230,11 +229,12 @@ abstract class ShellToolAgent( } } if (ApplicationServices.authorizationManager.isAuthorized( - ToolAgent.javaClass, + ShellToolAgent.javaClass, user, AuthorizationInterface.OperationType.Admin ) ) { +/* ToolServlet.addTool( ToolServlet.Tool( path = openAPI.paths?.entries?.first()?.key?.removePrefix(toolsPrefix) ?: "unknown", @@ -243,6 +243,7 @@ abstract class ShellToolAgent( servletCode = servletImpl ) ) +*/ } buildTestPage(openAPI, servletImpl, task) } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ToolAgent.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ToolAgent.kt deleted file mode 100644 index e960f07d..00000000 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/ToolAgent.kt +++ /dev/null @@ -1,437 +0,0 @@ -package com.simiacryptus.skyenet.apps.coding - -import com.simiacryptus.jopenai.API -import com.simiacryptus.jopenai.ApiModel -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.skyenet.core.actors.CodingActor -import com.simiacryptus.skyenet.core.actors.CodingActor.CodeResult -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.indent -import com.simiacryptus.skyenet.core.actors.CodingActor.Companion.sortCode -import com.simiacryptus.skyenet.core.actors.ParsedActor -import com.simiacryptus.skyenet.core.actors.SimpleActor -import com.simiacryptus.skyenet.core.platform.* -import com.simiacryptus.skyenet.interpreter.Interpreter -import com.simiacryptus.skyenet.kotlin.KotlinInterpreter -import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import com.simiacryptus.skyenet.webui.servlet.ToolServlet -import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown -import com.simiacryptus.skyenet.webui.util.OpenAPI -import jakarta.servlet.http.HttpServlet -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.eclipse.jetty.server.Request -import org.eclipse.jetty.server.Response -import org.eclipse.jetty.webapp.WebAppClassLoader -import org.openapitools.codegen.OpenAPIGenerator -import org.openapitools.codegen.SpecValidationException -import org.slf4j.LoggerFactory -import java.io.File -import kotlin.reflect.KClass - -abstract class ToolAgent( - api: API, - dataStorage: StorageInterface, - session: Session, - user: User?, - ui: ApplicationInterface, - interpreter: KClass, - symbols: Map, - temperature: Double = 0.1, - details: String? = null, - model: ChatModels, - mainTask: SessionTask = ui.newTask(), - actorMap: Map = mapOf( - ActorTypes.CodingActor to CodingActor( - interpreter, - symbols = symbols, - temperature = temperature, - details = details, - model = model - ) - ), -) : CodingAgent( - api, - dataStorage, - session, - user, - ui, - interpreter, - symbols, - temperature, - details, - model, - mainTask, - actorMap -) { - override fun displayFeedback(task: SessionTask, request: CodingActor.CodeRequest, response: CodeResult) { - val formText = StringBuilder() - var formHandle: StringBuilder? = null - formHandle = task.add( - """ - |
- |${super.playButton(task, request, response, formText) { formHandle!! }} - |${super.regenButton(task, request, formText) { formHandle!! }} - |${createToolButton(task, request, response, formText) { formHandle!! }} - |
- |${super.reviseMsg(task, request, response, formText) { formHandle!! }} - """.trimMargin(), className = "reply-message" - ) - formText.append(formHandle.toString()) - formHandle.toString() - task.complete() - } - - private fun createToolButton( - task: SessionTask, - request: CodingActor.CodeRequest, - response: CodeResult, - formText: StringBuilder, - formHandle: () -> StringBuilder - ) = ui.hrefLink("\uD83D\uDCE4", "href-link regen-button") { - val task = ui.newTask() - responseAction(task, "Exporting...", formHandle(), formText) { - displayCodeFeedback( - task, schemaActor(), request.copy( - messages = listOf( - response.code to ApiModel.Role.assistant, - "From the given code prototype, identify input out output data structures and generate Kotlin data classes to define this schema" to ApiModel.Role.user - ) - ) - ) { schemaCode -> - displayCodeFeedback( - task, servletActor(), request.copy( - messages = listOf( - response.code to ApiModel.Role.assistant, - "Reprocess this code prototype into a servlet using the given data schema. " + - "The last line should instantiate the new servlet class and return it via the returnBuffer collection." to ApiModel.Role.user - ), - codePrefix = schemaCode - ) - ) { servletHandler -> - val servletImpl = (schemaCode + "\n\n" + servletHandler).sortCode() - val toolsPrefix = "/tools" - var openAPI = openAPIParsedActor().getParser(api).apply(servletImpl).let { openApi -> - openApi.copy(paths = openApi.paths?.mapKeys { toolsPrefix + it.key.removePrefix(toolsPrefix) }) - } - task.add(renderMarkdown("```json\n${JsonUtil.toJson(openAPI)/*.indent(" ")*/}\n```", ui = ui)) - for (i in 0..5) { - try { - OpenAPIGenerator.main( - arrayOf( - "generate", - "-i", - File.createTempFile("openapi", ".json").apply { - writeText(JsonUtil.toJson(openAPI)) - deleteOnExit() - }.absolutePath, - "-g", - "html2", - "-o", - File( - dataStorage.getSessionDir(user, session), - "openapi/html2" - ).apply { mkdirs() }.absolutePath, - ) - ) - task.add("Validated OpenAPI Descriptor - Documentation Saved") - break - } catch (e: SpecValidationException) { - val error = """ - |${e.message} - |${e.errors.joinToString("\n") { "ERROR:" + it.toString() }} - |${e.warnings.joinToString("\n") { "WARN:" + it.toString() }} - """.trimIndent() - task.hideable(ui, renderMarkdown("```\n${error/*.indent(" ")*/}\n```", ui = ui)) - openAPI = openAPIParsedActor().answer( - listOf( - servletImpl, - JsonUtil.toJson(openAPI), - error - ), api - ).obj.let { openApi -> - val paths = HashMap(openApi.paths) - openApi.copy(paths = paths.mapKeys { toolsPrefix + it.key.removePrefix(toolsPrefix) }) - } - task.hideable( - ui, - renderMarkdown("```json\n${JsonUtil.toJson(openAPI)/*.indent(" ")*/}\n```", ui = ui) - ) - } - } - if (ApplicationServices.authorizationManager.isAuthorized( - ToolAgent.javaClass, - user, - AuthorizationInterface.OperationType.Admin - ) - ) { - ToolServlet.addTool( - ToolServlet.Tool( - path = openAPI.paths?.entries?.first()?.key?.removePrefix(toolsPrefix) ?: "unknown", - openApiDescription = openAPI, - interpreterString = getInterpreterString(), - servletCode = servletImpl - ) - ) - } - buildTestPage(openAPI, servletImpl, task) - } - } - } - } - - private fun openAPIParsedActor() = object : ParsedActor( -// parserClass = OpenApiParser::class.java, - resultClass = OpenAPI::class.java, - model = model, - prompt = "You are a code documentation assistant. You will create the OpenAPI definition for a servlet handler written in kotlin", - parsingModel = model, - ) { - override val describer: TypeDescriber - get() = object : AbbrevWhitelistYamlDescriber( - //"com.simiacryptus", "com.github.simiacryptus" - ) { - override val includeMethods: Boolean get() = false - } - } - - private fun servletActor() = object : CodingActor( - interpreterClass = actor.interpreterClass, - symbols = actor.symbols + mapOf( - "returnBuffer" to ServletBuffer(), - "json" to JsonUtil, - "req" to Request(null, null), - "resp" to Response(null, null), - ), - describer = object : AbbrevWhitelistYamlDescriber( - "com.simiacryptus", - "com.github.simiacryptus" - ) { - override fun describe( - rawType: Class, - stackMax: Int, - describedTypes: MutableSet - ): String = when (rawType) { - Request::class.java -> describe(HttpServletRequest::class.java) - Response::class.java -> describe(HttpServletResponse::class.java) - else -> super.describe(rawType, stackMax, describedTypes) - } - }, - details = actor.details, - model = actor.model, - fallbackModel = actor.fallbackModel, - temperature = actor.temperature, - runtimeSymbols = actor.runtimeSymbols - ) { - override val prompt: String - get() = super.prompt - } - - private fun schemaActor() = object : CodingActor( - interpreterClass = actor.interpreterClass, - symbols = mapOf(), - details = actor.details, - model = actor.model, - fallbackModel = actor.fallbackModel, - temperature = actor.temperature, - runtimeSymbols = actor.runtimeSymbols - ) { - override val prompt: String - get() = super.prompt - } - - - private fun displayCodeFeedback( - task: SessionTask, - actor: CodingActor, - request: CodingActor.CodeRequest, - response: CodeResult = execWrap { actor.answer(request, api = api) }, - onComplete: (String) -> Unit - ) { - task.hideable(ui, renderMarkdown("```kotlin\n${/*escapeHtml4*/(response.code)/*.indent(" ")*/}\n```", ui = ui)) - val formText = StringBuilder() - var formHandle: StringBuilder? = null - formHandle = task.add( - """ - |
- |${ - if (!super.canPlay) "" else - super.ui.hrefLink("\uD83D\uDC4D", "href-link play-button") { - super.responseAction(task, "Accepted...", formHandle!!, formText) { - onComplete(response.code) - } - } - } - |${ - super.ui.hrefLink("♻", "href-link regen-button") { - super.responseAction(task, "Regenerating...", formHandle!!, formText) { - //val task = super.ui.newTask() - val codeRequest = - request.copy(messages = request.messages.dropLastWhile { it.second == ApiModel.Role.assistant }) - try { - val lastUserMessage = - codeRequest.messages.last { it.second == ApiModel.Role.user }.first.trim() - val codeResponse: CodeResult = if (lastUserMessage.startsWith("```")) { - actor.CodeResultImpl( - messages = actor.chatMessages(codeRequest), - input = codeRequest, - api = super.api as OpenAIClient, - givenCode = lastUserMessage.removePrefix("```").removeSuffix("```") - ) - } else { - actor.answer(codeRequest, api = super.api) - } - super.displayCode(task, codeResponse) - displayCodeFeedback( - task, - actor, - super.append(codeRequest, codeResponse), - codeResponse, - onComplete - ) - } catch (e: Throwable) { - log.warn("Error", e) - val error = task.error(super.ui, e) - var regenButton: StringBuilder? = null - regenButton = task.complete(super.ui.hrefLink("♻", "href-link regen-button") { - regenButton?.clear() - val header = task.header("Regenerating...") - super.displayCode(task, codeRequest) - header?.clear() - error?.clear() - task.complete() - }) - } - } - } - } - |
- |${ - super.ui.textInput { feedback -> - super.responseAction(task, "Revising...", formHandle!!, formText) { - //val task = super.ui.newTask() - try { - task.echo(renderMarkdown(feedback, ui = ui)) - val codeRequest = CodingActor.CodeRequest( - messages = request.messages + - listOf( - response.code to ApiModel.Role.assistant, - feedback to ApiModel.Role.user, - ).filter { it.first.isNotBlank() }.map { it.first to it.second } - ) - try { - val lastUserMessage = - codeRequest.messages.last { it.second == ApiModel.Role.user }.first.trim() - val codeResponse: CodeResult = if (lastUserMessage.startsWith("```")) { - actor.CodeResultImpl( - messages = actor.chatMessages(codeRequest), - input = codeRequest, - api = super.api as OpenAIClient, - givenCode = lastUserMessage.removePrefix("```").removeSuffix("```") - ) - } else { - actor.answer(codeRequest, api = super.api) - } - displayCodeFeedback( - task, - actor, - super.append(codeRequest, codeResponse), - codeResponse, - onComplete - ) - } catch (e: Throwable) { - log.warn("Error", e) - val error = task.error(super.ui, e) - var regenButton: StringBuilder? = null - regenButton = task.complete(super.ui.hrefLink("♻", "href-link regen-button") { - regenButton?.clear() - val header = task.header("Regenerating...") - super.displayCode(task, codeRequest) - header?.clear() - error?.clear() - task.complete() - }) - } - } catch (e: Throwable) { - log.warn("Error", e) - task.error(ui, e) - } - } - } - } - """.trimMargin(), className = "reply-message" - ) - formText.append(formHandle.toString()) - formHandle.toString() - task.complete() - } - - - class ServletBuffer : ArrayList() - - private fun buildTestPage( - openAPI: OpenAPI, - servletImpl: String, - task: SessionTask - ) { - var testPage = SimpleActor( - prompt = "Given the definition for a servlet handler, create a test page that can be used to test the servlet", - model = model, - ).answer( - listOf( - JsonUtil.toJson(openAPI), - servletImpl - ), api - ) - // if ```html unwrap - if (testPage.contains("```html")) testPage = testPage.substringAfter("```html").substringBefore("```") - task.add(renderMarkdown("```html\n$testPage\n```", ui = ui)) - task.complete( - "Test Page for ${openAPI.paths?.entries?.first()?.key ?: "unknown"} Saved" - ) - } - - abstract fun getInterpreterString(): String - - private fun answer( - actor: CodingActor, - request: CodingActor.CodeRequest, - task: SessionTask = ui.newTask(), - feedback: Boolean = true, - ): CodeResult { - val response = actor.answer(request, api = api) - if (feedback) displayCodeAndFeedback(task, request, response) - else displayCode(task, response) - return response - } - - companion object { - val log = LoggerFactory.getLogger(ToolAgent::class.java) - fun execWrap(fn: () -> T): T { - val classLoader = Thread.currentThread().contextClassLoader - val prevCL = KotlinInterpreter.classLoader - KotlinInterpreter.classLoader = classLoader //req.javaClass.classLoader - return try { - WebAppClassLoader.runWithServerClassAccess { - require(null != classLoader.loadClass("org.eclipse.jetty.server.Response")) - require(null != classLoader.loadClass("org.eclipse.jetty.server.Request")) - // com.simiacryptus.jopenai.OpenAIClient - require(null != classLoader.loadClass("com.simiacryptus.jopenai.OpenAIClient")) - require(null != classLoader.loadClass("com.simiacryptus.jopenai.API")) - fn() - } - } finally { - KotlinInterpreter.classLoader = prevCL - } - } - } -} \ No newline at end of file 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 4b1be329..b175e9ac 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 @@ -20,7 +20,6 @@ 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.servlet.ToolServlet import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown import org.intellij.lang.annotations.Language @@ -235,10 +234,10 @@ class WebDevAgent( try { - val toolSpecs = tools.map { ToolServlet.tools.find { t -> t.path == it } } - .joinToString("\n\n") { it?.let { JsonUtil.toJson(it.openApiDescription) } ?: "" } - var messageWithTools = userMessage - if (toolSpecs.isNotBlank()) messageWithTools += "\n\nThese services are available:\n$toolSpecs" +// val toolSpecs = tools.map { ToolServlet.tools.find { t -> t.path == it } } +// .joinToString("\n\n") { it?.let { JsonUtil.toJson(it.openApiDescription) } ?: "" } +// var messageWithTools = userMessage +// if (toolSpecs.isNotBlank()) messageWithTools += "\n\nThese services are available:\n$toolSpecs" task.echo( renderMarkdown( "```json\n${JsonUtil.toJson(architectureResponse.obj)/*.indent(" ")*/}\n```", @@ -259,7 +258,7 @@ class WebDevAgent( task = task, request = javascriptActor.chatMessages( listOf( - messageWithTools, +// messageWithTools, architectureResponse.text, "Render $path - $description" ) @@ -273,7 +272,7 @@ class WebDevAgent( task = task, request = cssActor.chatMessages( listOf( - messageWithTools, +// messageWithTools, architectureResponse.text, "Render $path - $description" ) @@ -286,7 +285,7 @@ class WebDevAgent( task = task, request = htmlActor.chatMessages( listOf( - messageWithTools, +// messageWithTools, architectureResponse.text, "Render $path - $description" ) @@ -299,7 +298,7 @@ class WebDevAgent( task = task, request = etcActor.chatMessages( listOf( - messageWithTools, +// messageWithTools, architectureResponse.text, "Render $path - $description" ) @@ -312,7 +311,7 @@ class WebDevAgent( task = task, request = etcActor.chatMessages( listOf( - messageWithTools, +// messageWithTools, architectureResponse.text, "Render $path - $description" ) @@ -325,7 +324,7 @@ class WebDevAgent( task = task, request = etcActor.chatMessages( listOf( - messageWithTools, +// messageWithTools, architectureResponse.text, "Render $path - $description" ) 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 ef7d07c2..5f8b6e98 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 @@ -10,6 +10,7 @@ import com.simiacryptus.skyenet.webui.servlet.* import com.simiacryptus.skyenet.webui.util.Selenium2S3 import jakarta.servlet.DispatcherType import jakarta.servlet.Servlet +import jakarta.servlet.http.HttpServlet import org.eclipse.jetty.server.* import org.eclipse.jetty.server.handler.ContextHandlerCollection import org.eclipse.jetty.servlet.FilterHolder @@ -47,14 +48,14 @@ abstract class ApplicationDirectory( if (isServer) "https://$publicName" else "http://$localName:$port" open val welcomeResources = ResourceCollection(allResources("welcome").map(::newResource)) - open val userInfoServlet = UserInfoServlet() - open val userSettingsServlet = UserSettingsServlet() - open val logoutServlet = LogoutServlet() - open val usageServlet = UsageServlet() - open val proxyHttpServlet = ProxyHttpServlet() - open val apiKeyServlet = ApiKeyServlet() - open val welcomeServlet = WelcomeServlet(this) - abstract val toolServlet: ToolServlet? + open val userInfoServlet: HttpServlet = UserInfoServlet() + open val userSettingsServlet: HttpServlet = UserSettingsServlet() + open val logoutServlet: HttpServlet = LogoutServlet() + open val usageServlet: HttpServlet = UsageServlet() + 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", @@ -92,28 +93,7 @@ abstract class ApplicationDirectory( ApplicationServices.isLocked = true val server = start( port, - *(listOfNotNull( - newWebAppContext("/logout", logoutServlet), - newWebAppContext("/proxy", proxyHttpServlet), - toolServlet?.let { newWebAppContext("/tools", it) }, - newWebAppContext("/userInfo", userInfoServlet).let { - authenticatedWebsite()?.configure(it, true) ?: it - }, - newWebAppContext("/userSettings", userSettingsServlet).let { - authenticatedWebsite()?.configure(it, true) ?: it - }, - newWebAppContext("/usage", usageServlet).let { - authenticatedWebsite()?.configure(it, true) ?: it - }, - newWebAppContext("/apiKeys", apiKeyServlet).let { - authenticatedWebsite()?.configure(it, true) ?: it - }, - newWebAppContext("/", welcomeResources, "welcome", welcomeServlet).let { - authenticatedWebsite()?.configure(it, false) ?: it - }, - ).toTypedArray() + childWebApps.map { - newWebAppContext(it.path, it.server) - }) + *(webAppContexts()) ) log.info("Server started successfully on port $port") try { @@ -133,6 +113,29 @@ abstract class ApplicationDirectory( } } + open fun webAppContexts() = listOfNotNull( + newWebAppContext("/logout", logoutServlet), + newWebAppContext("/proxy", proxyHttpServlet), + // toolServlet?.let { newWebAppContext("/tools", it) }, + newWebAppContext("/userInfo", userInfoServlet).let { + authenticatedWebsite()?.configure(it, true) ?: it + }, + newWebAppContext("/userSettings", userSettingsServlet).let { + authenticatedWebsite()?.configure(it, true) ?: it + }, + newWebAppContext("/usage", usageServlet).let { + authenticatedWebsite()?.configure(it, true) ?: it + }, + newWebAppContext("/apiKeys", apiKeyServlet).let { + authenticatedWebsite()?.configure(it, true) ?: it + }, + newWebAppContext("/", welcomeResources, "welcome", welcomeServlet).let { + authenticatedWebsite()?.configure(it, false) ?: it + }, + ).toTypedArray() + childWebApps.map { + newWebAppContext(it.path, it.server) + } + open fun init(isServer: Boolean): ApplicationDirectory { OutputInterceptor.setupInterceptor() log.info("Initializing application, isServer: $isServer") diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ToolServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ToolServlet.kt deleted file mode 100644 index 63ebed06..00000000 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/ToolServlet.kt +++ /dev/null @@ -1,424 +0,0 @@ -package com.simiacryptus.skyenet.webui.servlet - - -import com.simiacryptus.jopenai.util.JsonUtil -import com.simiacryptus.skyenet.apps.coding.ToolAgent -import com.simiacryptus.skyenet.core.platform.ApplicationServices -import com.simiacryptus.skyenet.core.platform.ApplicationServices.authenticationManager -import com.simiacryptus.skyenet.core.platform.ApplicationServices.authorizationManager -import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType -import com.simiacryptus.skyenet.core.platform.User -import com.simiacryptus.skyenet.interpreter.Interpreter -import com.simiacryptus.skyenet.kotlin.KotlinInterpreter -import com.simiacryptus.skyenet.webui.application.ApplicationDirectory -import com.simiacryptus.skyenet.webui.application.ApplicationServer.Companion.getCookie -import com.simiacryptus.skyenet.webui.util.MarkdownUtil -import com.simiacryptus.skyenet.webui.util.OpenAPI -import jakarta.servlet.http.HttpServlet -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.eclipse.jetty.webapp.WebAppClassLoader -import org.intellij.lang.annotations.Language -import java.io.File -import java.util.* -import kotlin.reflect.jvm.javaType -import kotlin.reflect.typeOf - -abstract class ToolServlet(val app: ApplicationDirectory) : HttpServlet() { - - data class Tool( - val path: String, - val openApiDescription: OpenAPI, - val interpreterString: String, - val servletCode: String, - ) - - @Language("HTML") - private fun indexPage() = """ - - - Tools - $header - - - - - - - - - - - -

Tools

-
- - -
-
- ${tools.joinToString("\n") { tool -> "" }} -
- - - """.trimIndent() - - @Language("HTML") - private fun toolDetailsPage(tool: Tool) = """ - - - Tool Details - $header - - - - - - - - - - - -

Tool Details: ${tool.path}

-
-
- - - -
-
-
- - Edit - - Delete -
-
Path: -
${tool.path}
-
-
Interpreter: -
${tool.interpreterString}
-
-
-
-
-
Code: -
${MarkdownUtil.renderMarkdown("```kotlin\n${tool.servletCode}\n```")}
-
-
-
-
API Description: -
${JsonUtil.toJson(tool.openApiDescription)}
-
-
-
- - - """.trimIndent() - - private val header - @Language("HTML") - get() = """ - - - - - - - - - - - """.trimIndent() - - private fun serveEditPage(req: HttpServletRequest, resp: HttpServletResponse, tool: Tool) { - resp.contentType = "text/html" - val formHtml = """ - - - Edit Tool: ${tool.path} - $header - - -

Edit Tool: ${tool.path}

-
- - - - - - - - - - -
- - - """.trimIndent() - resp.writer.write(formHtml) - resp.writer.close() - } - - override fun doGet(req: HttpServletRequest?, resp: HttpServletResponse?) { - - val user = authenticationManager.getUser(req?.getCookie()) - if (!authorizationManager.isAuthorized(ToolServlet.javaClass, user, OperationType.Admin)) { - resp?.sendError(403) - return - } - - resp?.contentType = "text/html" - - val path = req?.getParameter("path") - if (req?.getParameter("edit") != null) { - val tool = tools.find { it.path == path } - if (tool != null) { - serveEditPage(req, resp!!, tool) - } else { - resp!!.writer.write("Tool not found") - } - return - } - - if (req?.getParameter("delete") != null) { - val tool = tools.find { it.path == path } - if (tool != null) { - tools.remove(tool) - File(userRoot, "tools.json").writeText(JsonUtil.toJson(tools)) - resp!!.sendRedirect("?") - } else { - resp!!.writer.write("Tool not found") - } - return - } - - if (req?.getParameter("export") != null) { - resp?.contentType = "application/json" - resp?.addHeader("Content-Disposition", "attachment; filename=\"tools.json\"") - resp?.writer?.write(JsonUtil.toJson(tools)) - return - } - - if (path != null) { - // Display details for a single tool - val tool = tools.find { it.path == path } - if (tool != null) { - resp?.writer?.write(toolDetailsPage(tool)) - } else { - resp?.writer?.write("Tool not found") - } - } else { - // Display index page - resp?.writer?.write(indexPage()) - } - resp?.writer?.close() - } - - override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) { - req ?: return - resp ?: return - - val path = req.getParameter("path") - val tool = tools.find { it.path == path } - if (tool != null) { - tools.remove(tool) - val newpath = req.getParameter("newpath") ?: req.getParameter("path") - tools.add( - tool.copy( - path = newpath, - interpreterString = req.getParameter("interpreterString"), - servletCode = req.getParameter("servletCode"), - openApiDescription = JsonUtil.fromJson(req.getParameter("openApiDescription"), OpenAPI::class.java) - ) - ) - File(userRoot, "tools.json").writeText(JsonUtil.toJson(tools)) - resp.sendRedirect("?path=$newpath&editSuccess=true") // Redirect to the tool's detail page or an edit success page - } else { - if (req.getParameter("import") != null) { - val inputStream = req.getPart("file")?.inputStream - val toolsJson = inputStream?.bufferedReader().use { it?.readText() } - if (toolsJson != null) { - val importedTools: List = JsonUtil.fromJson(toolsJson, typeOf>().javaType) - tools.clear() - tools.addAll(importedTools) - File(userRoot, "tools.json").writeText(JsonUtil.toJson(tools)) - resp.sendRedirect("?importSuccess=true") - } else { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid file or format") - } - return - } - resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Tool not found") - } - } - - companion object { - private val userRoot by lazy { - File( - File(ApplicationServices.dataStorageRoot, ".skyenet"), - "tools" - ).apply { mkdirs() } - } - - @OptIn(ExperimentalStdlibApi::class) - val tools by lazy { - val file = File(userRoot, "tools.json") - if (file.exists()) try { - return@lazy JsonUtil.fromJson(file.readText(), typeOf>().javaType) - } catch (e: Throwable) { - e.printStackTrace() - } - mutableListOf() - } - - fun addTool(element: Tool) { - tools += element - File(userRoot, "tools.json").writeText(JsonUtil.toJson(tools)) - } - - val apiKey = UUID.randomUUID().toString() - val instanceCache = mutableMapOf() - - } - - override fun service(req: HttpServletRequest?, resp: HttpServletResponse?) { - req ?: return - resp ?: return - val path = req.servletPath ?: "/" - val tool = tools.find { it.path == path } - if (tool != null) { - // TODO: Isolate tools per user - val user = authenticationManager.getUser(req.getCookie()) - val isAdmin = authorizationManager.isAuthorized( - ToolServlet.javaClass, user, OperationType.Admin - ) - val isHeaderAuth = apiKey == req.getHeader("Authorization")?.removePrefix("Bearer ") - if (!isAdmin && !isHeaderAuth) { - resp.sendError(403) - } else { - try { - val servlet = instanceCache.computeIfAbsent(tool) { construct(user!!, tool) } - servlet.service(req, resp) - } catch (e: RuntimeException) { - throw e - } catch (e: Throwable) { - throw RuntimeException(e) - } - } - } else { - super.service(req, resp) - } - } - - private fun construct(user: User, tool: Tool): HttpServlet { - val returnBuffer = ToolAgent.ServletBuffer() - val classLoader = Thread.currentThread().contextClassLoader - val prevCL = KotlinInterpreter.classLoader - KotlinInterpreter.classLoader = classLoader //req.javaClass.classLoader - try { - WebAppClassLoader.runWithServerClassAccess { - require(null != classLoader.loadClass("org.eclipse.jetty.server.Response")) - require(null != classLoader.loadClass("org.eclipse.jetty.server.Request")) - this.fromString(user, tool.interpreterString).let { (interpreterClass, symbols) -> - val effectiveSymbols = (symbols + mapOf( - "returnBuffer" to returnBuffer, - "json" to JsonUtil, - )).filterKeys { !it.isNullOrBlank() } - interpreterClass.getConstructor(Map::class.java).newInstance(effectiveSymbols).run(tool.servletCode) - } - } - } finally { - KotlinInterpreter.classLoader = prevCL - } - - val first = returnBuffer.first() - return first - } - - abstract fun fromString(user: User, str: String): InterpreterAndTools - -} - -data class InterpreterAndTools( - val interpreterClass: Class, - val symbols: Map = mapOf(), -) - - 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 29b7b3e8..7f07c3ba 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 @@ -28,7 +28,7 @@ open class WelcomeServlet(private val parent: ApplicationDirectory) : requestURI == "/" -> resp?.writer?.write(homepage(user).trimIndent()) requestURI == "/index.html" -> resp?.writer?.write(homepage(user).trimIndent()) requestURI.startsWith("/userInfo") -> { - parent.userInfoServlet.doGet(req, resp!!) + parent.userInfoServlet.service(req, resp!!) } else -> try { @@ -43,7 +43,7 @@ open class WelcomeServlet(private val parent: ApplicationDirectory) : override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) { val requestURI = req?.requestURI ?: "/" when { - requestURI.startsWith("/userSettings") -> parent.userSettingsServlet.doPost(req!!, resp!!) + requestURI.startsWith("/userSettings") -> parent.userSettingsServlet.service(req!!, resp!!) else -> resp?.sendError(404) } } 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 ede4c325..bbad6cd1 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 @@ -31,12 +31,14 @@ abstract class SocketManagerBase( override fun removeSocket(socket: ChatSocket) { synchronized(sockets) { + log.debug("Removing socket: {}", socket) sockets.remove(socket)?.close() } } override fun addSocket(socket: ChatSocket, session: org.eclipse.jetty.websocket.api.Session) { val user = getUser(session) + log.debug("Adding socket: {} for user: {}", socket, user) if (!ApplicationServices.authorizationManager.isAuthorized( applicationClass = applicationClass, user = user, @@ -51,9 +53,11 @@ abstract class SocketManagerBase( private fun publish( out: String, ) { + log.debug("Publishing message: {}", out) synchronized(sockets) { sockets.keys.forEach { chatSocket -> try { + log.debug("Queueing message for socket: {}", chatSocket) sendQueues.computeIfAbsent(chatSocket) { ConcurrentLinkedDeque() }.add(out) } catch (e: Exception) { log.info("Error sending message", e) @@ -64,6 +68,7 @@ abstract class SocketManagerBase( synchronized(deque) { while (true) { val msg = deque.poll() ?: break + log.debug("Sending message: {} to socket: {}", msg, chatSocket) chatSocket.remote.sendString(msg) } chatSocket.remote.flush() @@ -82,6 +87,7 @@ abstract class SocketManagerBase( ): SessionTask { val operationID = randomID(root) var responseContents = divInitializer(operationID, cancelable) + log.debug("Creating new task with operationID: {}", operationID) send(responseContents) return SessionTaskImpl(operationID, responseContents, SessionTask.spinner) } @@ -98,6 +104,7 @@ abstract class SocketManagerBase( override fun send(html: String) = this@SocketManagerBase.send(html) override fun saveFile(relativePath: String, data: ByteArray): String { + log.debug("Saving file at path: {}", relativePath) dataStorage?.getSessionDir(owner, session)?.let { dir -> dir.mkdirs() val resolve = dir.resolve(relativePath) @@ -110,6 +117,7 @@ abstract class SocketManagerBase( fun send(out: String) { try { + log.debug("Sending message: {}", out) val split = out.split(',', ignoreCase = false, limit = 2) val messageID = split[0] val newValue = split[1] @@ -149,12 +157,14 @@ abstract class SocketManagerBase( } final override fun getReplay(): List { + log.debug("Getting replay messages") return messageStates.entries.map { "${it.key},${messageVersions.computeIfAbsent(it.key) { AtomicInteger(1) }.get()},${it.value}" } } private fun setMessage(key: String, value: String): Int { + log.debug("Setting message - Key: {}, Value: {}", key, value) if (messageStates.containsKey(key)) { if (messageStates[key] == value) { return -1 @@ -169,6 +179,7 @@ abstract class SocketManagerBase( } final override fun onWebSocketText(socket: ChatSocket, message: String) { + log.debug("Received WebSocket message: {} from socket: {}", message, socket) if (canWrite(socket.user)) pool.submit { log.debug("{} - Received message: {}", session, message) try { @@ -221,6 +232,7 @@ abstract class SocketManagerBase( id: String? = null, handler: Consumer ): String { + log.debug("Creating href link with text: {}", linkText) val operationID = randomID() linkTriggers[operationID] = handler return """): String { + log.debug("Creating text input") val operationID = randomID() txtTriggers[operationID] = handler //language=HTML @@ -263,10 +276,11 @@ abstract class SocketManagerBase( if (!cancelable) """$operationID,""" else """$operationID,""" - fun getUser(session: org.eclipse.jetty.websocket.api.Session): User? = - session.upgradeRequest.cookies?.find { it.name == AuthenticationInterface.AUTH_COOKIE }?.value.let { + fun getUser(session: org.eclipse.jetty.websocket.api.Session): User? { + log.debug("Getting user from session: {}", session) + return session.upgradeRequest.cookies?.find { it.name == AuthenticationInterface.AUTH_COOKIE }?.value.let { ApplicationServices.authenticationManager.getUser(it) } - + } } } \ No newline at end of file diff --git a/webui/src/main/resources/application/tabs.js b/webui/src/main/resources/application/tabs.js index d063c407..e8bc462a 100644 --- a/webui/src/main/resources/application/tabs.js +++ b/webui/src/main/resources/application/tabs.js @@ -5,7 +5,7 @@ function updateTabs() { const forTab = button.getAttribute('data-for-tab'); let tabsParent = button.closest('.tabs-container'); tabsParent.querySelectorAll('.tab-button').forEach(tabButton => { - if (tabButton.closest('.tabs-container') === tabsParent) tabButton.classList.remove('active') + if (tabButton.closest('.tabs-container') === tabsParent) tabButton.classList.remove('active'); }); button.classList.add('active'); let selectedContent = null; @@ -16,13 +16,13 @@ function updateTabs() { content.style.display = 'block'; // Ensure the content is displayed selectedContent = content; } else { - content.classList.remove('active') + content.classList.remove('active'); content.style.display = 'none'; // Ensure the content is hidden } } }); if (selectedContent !== null) updateNestedTabs(selectedContent); - }) + }); }); } 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 a0586041..d17597d0 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/webui/ActorTestAppServer.kt @@ -12,7 +12,6 @@ import com.simiacryptus.skyenet.core.platform.User import com.simiacryptus.skyenet.groovy.GroovyInterpreter import com.simiacryptus.skyenet.kotlin.KotlinInterpreter import com.simiacryptus.skyenet.scala.ScalaLocalInterpreter -import com.simiacryptus.skyenet.webui.servlet.ToolServlet import com.simiacryptus.skyenet.webui.test.CodingActorTestApp import com.simiacryptus.skyenet.webui.test.ImageActorTestApp import com.simiacryptus.skyenet.webui.test.ParsedActorTestApp @@ -64,7 +63,7 @@ object ActorTestAppServer : com.simiacryptus.skyenet.webui.application.Applicati ), ) } - override val toolServlet: ToolServlet? get() = null +// override val toolServlet: ToolServlet? get() = null @JvmStatic fun main(args: Array) {