From aa1078be4bacd797e140409d823ea91a40347d79 Mon Sep 17 00:00:00 2001 From: Andrew Charneski Date: Sun, 19 Nov 2023 12:49:29 -0500 Subject: [PATCH] 1.0.34 (#38) * recording * fixed prism, moved platform * added code chat * Input ui enhancements * Fixes for CodingActor --- README.md | 6 +- core/build.gradle.kts | 2 +- .../kotlin/com/simiacryptus/skyenet/Brain.kt | 5 +- .../kotlin/com/simiacryptus/skyenet/Ears.kt | 2 +- .../skyenet/actors/ActorSystem.kt | 26 ++ .../skyenet/actors/CodingActor.kt | 165 +++++----- .../skyenet/actors/ParsedActor.kt | 12 +- .../skyenet/actors/ParsedResponse.kt | 7 +- .../skyenet/actors/SimpleActor.kt | 1 - .../skyenet/actors/opt/ActorOptimization.kt | 2 +- .../skyenet/actors/opt/Expectation.kt | 2 +- .../actors/record/RecordingCodingActor.kt | 85 +++++ .../actors/record/RecordingParsedActor.kt | 49 +++ .../actors/record/RecordingSimpleActor.kt | 41 +++ .../skyenet/actors/test/ActorTestBase.kt | 54 ++++ .../actors/test/CodingActorTestBase.kt | 19 ++ .../actors/test/ParsedActorTestBase.kt | 21 ++ .../ApplicationServices.kt | 2 +- .../AuthenticationManager.kt | 2 +- .../AuthorizationManager.kt | 4 +- .../{config => platform}/DataStorage.kt | 2 +- .../{config => platform}/UsageManager.kt | 2 +- .../UserSettingsManager.kt | 2 +- .../skyenet/util/FunctionWrapper.kt | 71 +++++ .../simiacryptus/skyenet/DataStorageTest.kt | 2 +- gradle.properties | 2 +- .../skyenet/heart/KotlinInterpreter.kt | 2 +- webui/build.gradle.kts | 2 +- .../simiacryptus/skyenet/ApplicationBase.kt | 13 +- .../skyenet/ApplicationDirectory.kt | 4 +- .../simiacryptus/skyenet/chat/ChatServer.kt | 8 +- .../simiacryptus/skyenet/chat/ChatSession.kt | 10 +- .../simiacryptus/skyenet/chat/ChatSocket.kt | 15 +- .../skyenet/chat/CodeChatServer.kt | 68 ++++ .../skyenet/servlet/AuthenticatedWebsite.kt | 6 +- .../skyenet/servlet/FileServlet.kt | 5 +- .../skyenet/servlet/NewSessionServlet.kt | 2 +- .../skyenet/servlet/SessionListServlet.kt | 9 +- .../skyenet/servlet/SessionSettingsServlet.kt | 3 +- .../skyenet/servlet/UsageServlet.kt | 4 +- .../skyenet/servlet/UserInfoServlet.kt | 3 +- .../skyenet/servlet/UserSettingsServlet.kt | 7 +- .../skyenet/servlet/WelcomeServlet.kt | 8 +- .../skyenet/servlet/ZipServlet.kt | 5 +- .../skyenet/session/SessionBase.kt | 11 +- .../skyenet/test/CodingActorTestApp.kt | 32 +- .../skyenet/test/ParsedActorTestApp.kt | 19 +- .../skyenet/test/SimpleActorTestApp.kt | 3 +- .../skyenet/util/EmbeddingVisualizer.kt | 3 +- .../simiacryptus/skyenet/util/HtmlTools.kt | 10 +- webui/src/main/resources/codeChat/chat.css | 291 ++++++++++++++++++ .../resources/{readOnly => codeChat}/chat.js | 9 +- .../{readOnly => codeChat}/favicon.svg | 0 webui/src/main/resources/codeChat/index.html | 42 +++ webui/src/main/resources/codeChat/main.js | 162 ++++++++++ webui/src/main/resources/readOnly/chat.css | 228 -------------- webui/src/main/resources/readOnly/index.html | 45 --- webui/src/main/resources/readOnly/main.js | 130 -------- .../src/main/resources/simpleSession/chat.css | 33 ++ .../main/resources/simpleSession/index.html | 17 +- .../src/main/resources/simpleSession/main.js | 35 +++ .../skyenet/ActorTestAppServer.kt | 6 +- 62 files changed, 1219 insertions(+), 619 deletions(-) create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingCodingActor.kt create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingParsedActor.kt create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingSimpleActor.kt create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ActorTestBase.kt create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/CodingActorTestBase.kt create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ParsedActorTestBase.kt rename core/src/main/kotlin/com/simiacryptus/skyenet/{config => platform}/ApplicationServices.kt (96%) rename core/src/main/kotlin/com/simiacryptus/skyenet/{config => platform}/AuthenticationManager.kt (94%) rename core/src/main/kotlin/com/simiacryptus/skyenet/{config => platform}/AuthorizationManager.kt (93%) rename core/src/main/kotlin/com/simiacryptus/skyenet/{config => platform}/DataStorage.kt (99%) rename core/src/main/kotlin/com/simiacryptus/skyenet/{config => platform}/UsageManager.kt (99%) rename core/src/main/kotlin/com/simiacryptus/skyenet/{config => platform}/UserSettingsManager.kt (96%) create mode 100644 core/src/main/kotlin/com/simiacryptus/skyenet/util/FunctionWrapper.kt create mode 100644 webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt create mode 100644 webui/src/main/resources/codeChat/chat.css rename webui/src/main/resources/{readOnly => codeChat}/chat.js (87%) rename webui/src/main/resources/{readOnly => codeChat}/favicon.svg (100%) create mode 100644 webui/src/main/resources/codeChat/index.html create mode 100644 webui/src/main/resources/codeChat/main.js delete mode 100644 webui/src/main/resources/readOnly/chat.css delete mode 100644 webui/src/main/resources/readOnly/index.html delete mode 100644 webui/src/main/resources/readOnly/main.js diff --git a/README.md b/README.md index da678cfc..2b0ad5f6 100644 --- a/README.md +++ b/README.md @@ -76,18 +76,18 @@ Maven: com.simiacryptus skyenet-webui - 1.0.33 + 1.0.34 ``` Gradle: ```groovy -implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.0.33' +implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.0.34' ``` ```kotlin -implementation("com.simiacryptus:skyenet:1.0.33") +implementation("com.simiacryptus:skyenet:1.0.34") ``` ### 🌟 To Use diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 76ef3150..3a972a92 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -31,7 +31,7 @@ val logback_version = "1.4.11" dependencies { - implementation(group = "com.simiacryptus", name = "joe-penai", version = "1.0.31") + implementation(group = "com.simiacryptus", name = "joe-penai", version = "1.0.32") implementation(group = "org.slf4j", name = "slf4j-api", version = "2.0.9") implementation(group = "commons-io", name = "commons-io", version = "2.15.0") diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/Brain.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/Brain.kt index d8ffbb97..248a9b65 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/Brain.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/Brain.kt @@ -2,7 +2,6 @@ package com.simiacryptus.skyenet -import com.simiacryptus.openai.models.OpenAIModel import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.OpenAIClient import com.simiacryptus.openai.OpenAIClient.* @@ -118,8 +117,8 @@ open class Brain( } companion object { - val log = org.slf4j.LoggerFactory.getLogger(Brain::class.java) - fun String.indent() = this.replace("\n", "\n ") + private val log = org.slf4j.LoggerFactory.getLogger(Brain::class.java) + fun String.indent(indent: String = " ") = this.replace("\n", "\n$indent") fun joinYamlList(typeDescriptions: List) = typeDescriptions.joinToString("\n") { "- " + it.indent() } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/Ears.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/Ears.kt index cbd90bb7..6b671950 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/Ears.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/Ears.kt @@ -119,7 +119,7 @@ open class Ears( private fun timeout(count: Long, timeUnit: TimeUnit): () -> Boolean = timeout(timeUnit.toMillis(count)) companion object { - val log = LoggerFactory.getLogger(Ears::class.java) + private val log = LoggerFactory.getLogger(Ears::class.java) } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt new file mode 100644 index 00000000..3ae4445a --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt @@ -0,0 +1,26 @@ +package com.simiacryptus.skyenet.actors + +import com.simiacryptus.skyenet.actors.record.* +import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.util.FunctionWrapper +import com.simiacryptus.skyenet.util.JsonFunctionRecorder +import java.io.File + +open class ActorSystem>( + private val actors: Map>, + private val dataStorage: DataStorage, + val userId: String?, + val sessionId: String +) { + val sessionDir = dataStorage.getSessionDir(userId, sessionId) + fun getActor(actor: T): BaseActor<*> { + val wrapper = FunctionWrapper(JsonFunctionRecorder(File(sessionDir, "${actor.name}.json"))) + return when (val baseActor = actors[actor]) { + null -> throw RuntimeException("No actor for $actor") + is SimpleActor -> RecordingSimpleActor(baseActor, wrapper) + is ParsedActor<*> -> RecordingParsedActor(baseActor, wrapper) + is CodingActor -> RecordingCodingActor(baseActor, wrapper) + else -> throw RuntimeException("Unknown actor type: ${baseActor.javaClass}") + } + } +} diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/CodingActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/CodingActor.kt index bb3c9084..5f97e5df 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/CodingActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/CodingActor.kt @@ -1,23 +1,22 @@ package com.simiacryptus.skyenet.actors -import com.simiacryptus.openai.models.OpenAIModel -import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.OpenAIClient import com.simiacryptus.openai.OpenAIClientBase.Companion.toContentList +import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.models.OpenAITextModel import com.simiacryptus.skyenet.Brain import com.simiacryptus.skyenet.Brain.Companion.indent -import com.simiacryptus.skyenet.Brain.Companion.superMethod import com.simiacryptus.skyenet.Heart import com.simiacryptus.skyenet.OutputInterceptor import com.simiacryptus.util.describe.AbbrevWhitelistYamlDescriber -import java.lang.reflect.Modifier +import com.simiacryptus.util.describe.TypeDescriber import kotlin.reflect.KClass +@Suppress("unused", "MemberVisibilityCanBePrivate") open class CodingActor( - private val interpreterClass: KClass, - private val symbols: Map = mapOf(), - private val describer: AbbrevWhitelistYamlDescriber = AbbrevWhitelistYamlDescriber( + val interpreterClass: KClass, + val symbols: Map = mapOf(), + val describer: TypeDescriber = AbbrevWhitelistYamlDescriber( "com.simiacryptus", "com.github.simiacryptus" ), @@ -26,6 +25,7 @@ open class CodingActor( model: OpenAITextModel = ChatModels.GPT35Turbo, val fallbackModel: OpenAITextModel = ChatModels.GPT4Turbo, temperature: Double = 0.1, + val autoEvaluate: Boolean = false, ) : BaseActor( prompt = "", name = name, @@ -36,101 +36,106 @@ open class CodingActor( val fixRetries = 2 override val prompt: String - get() { - val types = ArrayList>() - val apiobjs = symbols.map { (name, utilityObj) -> - val clazz = Class.forName(utilityObj.javaClass.typeName) - val methods = clazz.methods - .filter { Modifier.isPublic(it.modifiers) } - .filter { it.declaringClass == clazz } - .filter { !it.isSynthetic } - .map { it.superMethod() ?: it } - .filter { it.declaringClass != Object::class.java } - types.addAll(methods.flatMap { (listOf(it.returnType) + it.parameters.map { it.type }).filter { it != clazz } }) - types.addAll(clazz.declaredClasses.filter { Modifier.isPublic(it.modifiers) }) - """ - |$name: - | operations: - | ${Brain.joinYamlList(methods.map { describer.describe(it) }).indent().indent()} - |""".trimMargin().trim() - }.toTypedArray() - val typeDescriptions = types - .filter { !it.isPrimitive } - .filter { !it.isSynthetic } - .filter { !it.name.startsWith("java.") } - .filter { !setOf("void").contains(it.name) } - .distinct().map { - """ - |${it.simpleName}: - | ${describer.describe(it).indent()} - """.trimMargin().trim() - }.toTypedArray() - val apiDescription = """ - |api_objects: - | ${apiobjs.joinToString("\n").indent()} - |components: - | schemas: - | ${typeDescriptions.joinToString("\n").indent().indent()} - """.trimMargin() - return """ - |You will translate natural language instructions into - |an implementation using ${interpreter.getLanguage()} and the script context. - |Use ``` code blocks labeled with ${interpreter.getLanguage()} where appropriate. - |Defined symbols include ${symbols.keys.joinToString(", ")}. - |The runtime context is described below: - | - |$apiDescription - | - |${details ?: ""} - |""".trimMargin().trim() + get() = if (symbols.isNotEmpty()) """ + |You will translate natural language instructions into + |an implementation using ${interpreter.getLanguage()} and the script context. + |Use ``` code blocks labeled with ${interpreter.getLanguage()} where appropriate. + |Defined symbols include ${symbols.keys.joinToString(", ")}. + |The runtime context is described below: + | + |${this.apiDescription} + | + |${details ?: ""} + |""".trimMargin().trim() + else """ + |You will translate natural language instructions into + |an implementation using ${interpreter.getLanguage()} and the script context. + |Use ``` code blocks labeled with ${interpreter.getLanguage()} where appropriate. + | + |${details ?: ""} + |""".trimMargin().trim() - } + open val apiDescription: String + get() = this.symbols.map { (name, utilityObj) -> + """ + |$name: + | ${this.describer.describe(utilityObj.javaClass).indent(" ")} + |""".trimMargin().trim() + }.joinToString("\n") open val interpreter by lazy { interpreterClass.java.getConstructor(Map::class.java).newInstance(symbols) } override fun answer(vararg questions: String, api: OpenAIClient): CodeResult = - answer(*chatMessages(*questions), api = api) - - override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): CodeResult { - return CodeResultImpl(*messages, api = api) - } + if (!autoEvaluate) answer(*chatMessages(*questions), api = api) + else answerWithAutoEval(*chatMessages(*questions), api = api).first - fun answerWithPrefix(codePrefix: String, vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): CodeResult { - val prevList = messages.toList() - val newList = prevList.dropLast(1) + listOf( - OpenAIClient.ChatMessage(OpenAIClient.Role.assistant, codePrefix.toContentList()) - ) + prevList.last() - return CodeResultImpl(*newList.toTypedArray(), api = api) - } + override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): CodeResult = + if (!autoEvaluate) CodeResultImpl(*messages, api = api) + else answerWithAutoEval(*messages, api = api).first - fun answerWithAutoEval( + open fun answerWithPrefix( + codePrefix: String, vararg messages: OpenAIClient.ChatMessage, + api: OpenAIClient + ): CodeResult = + if (!autoEvaluate) CodeResultImpl(*injectCodePrefix(messages, codePrefix), api = api) + else answerWithAutoEval(*injectCodePrefix(messages, codePrefix), api = api).first + + open fun answerWithAutoEval( + vararg messages: String, api: OpenAIClient, codePrefix: String = "" + ) = answerWithAutoEval(*injectCodePrefix(chatMessages(*messages), codePrefix), api = api) + + open fun answerWithAutoEval( + vararg messages: OpenAIClient.ChatMessage, + api: OpenAIClient ): Pair { - val prevList = messages.toList() - val messagesWithPrefix = prevList.dropLast(1) + if(codePrefix.isBlank()) listOf() else listOf( - OpenAIClient.ChatMessage(OpenAIClient.Role.assistant, codePrefix.toContentList()) - ) + prevList.last() - var result = CodeResultImpl(*messagesWithPrefix.toTypedArray(), api = api) + var result = CodeResultImpl(*messages, api = api) + var lastError: Throwable? = null for (i in 0..fixIterations) try { return result to result.run() } catch (ex: Throwable) { - result = fix(api, messagesWithPrefix, result, ex) + lastError = ex + result = fix(api, messages, result, ex) } - throw RuntimeException("Failed to fix ${messages.map { it.content }.joinToString("\n")}") + throw RuntimeException( + """ + |Failed to fix code. Last attempt: + |```${interpreter.getLanguage().lowercase()} + |${result.getCode()} + |``` + | + |Last Error: + |``` + |${lastError?.message} + |``` + |""".trimMargin().trim() + ) } - private fun fix(api: OpenAIClient, messages: List, result: CodingActor.CodeResultImpl, ex: Throwable): CodingActor.CodeResultImpl { - val respondWithCode = brain(api, model).fixCommand(result.getCode(), ex, "", *messages.toTypedArray()) + private fun injectCodePrefix( + messages: Array, + codePrefix: String + ) = (messages.dropLast(1) + if (codePrefix.isBlank()) listOf() else listOf( + OpenAIClient.ChatMessage(OpenAIClient.Role.assistant, codePrefix.toContentList()) + ) + messages.last()).toTypedArray() + + private fun fix( + api: OpenAIClient, + messages: Array, + result: CodingActor.CodeResultImpl, + ex: Throwable + ): CodingActor.CodeResultImpl { + val respondWithCode = brain(api, model).fixCommand(result.getCode(), ex, "", *messages) val renderedResponse = getRenderedResponse(respondWithCode.second) val codedInstruction = getCode(interpreter.getLanguage(), respondWithCode.second) log.info("Response: \n\t${renderedResponse.replace("\n", "\n\t", false)}".trimMargin()) log.info("Code: \n\t${codedInstruction.replace("\n", "\n\t", false)}".trimMargin()) - return CodeResultImpl(*messages.toTypedArray(), codePrefix = codedInstruction, api = api) + return CodeResultImpl(*messages, codePrefix = codedInstruction, api = api) } - fun brain(api: OpenAIClient, model: OpenAITextModel) = Brain( + private fun brain(api: OpenAIClient, model: OpenAITextModel) = Brain( api = api, symbols = symbols.mapValues { it as Object }.asJava, language = interpreter.getLanguage(), @@ -232,7 +237,7 @@ open class CodingActor( } companion object { - val log = org.slf4j.LoggerFactory.getLogger(CodingActor::class.java) + private val log = org.slf4j.LoggerFactory.getLogger(CodingActor::class.java) fun errorMessage( code: String, line: Int, diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedActor.kt index 7cee2516..c33b31b1 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedActor.kt @@ -1,13 +1,12 @@ package com.simiacryptus.skyenet.actors -import com.simiacryptus.openai.models.OpenAIModel -import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.OpenAIClient +import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.models.OpenAITextModel import com.simiacryptus.openai.proxy.ChatProxy import java.util.function.Function -open class ParsedActor( +open class ParsedActor( val parserClass: Class>, prompt: String, val action: String? = null, @@ -19,7 +18,9 @@ open class ParsedActor( model = model, temperature = temperature, ) { - private inner class ParsedResponseImpl(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient) : ParsedResponse { + val resultClass: Class by lazy { parserClass.getMethod("apply", String::class.java).returnType as Class } + + private inner class ParsedResponseImpl(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient) : ParsedResponse(resultClass) { val parser: Function = ChatProxy( clazz = parserClass, api = api, @@ -29,11 +30,10 @@ open class ParsedActor( private val _text: String by lazy { response(*messages, api = api).choices.first().message?.content ?: throw RuntimeException("No response") } private val _obj: T by lazy { parser.apply(getText()) } override fun getText(): String = _text - override fun getObj(): T = _obj + override fun getObj(clazz: Class): T = _obj } override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): ParsedResponse { return ParsedResponseImpl(*messages, api = api) } } - diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedResponse.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedResponse.kt index a447dab3..fb19ebc8 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedResponse.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ParsedResponse.kt @@ -1,6 +1,7 @@ package com.simiacryptus.skyenet.actors -interface ParsedResponse { - fun getText(): String - fun getObj(): T +abstract class ParsedResponse(val clazz: Class) { + abstract fun getText(): String + abstract fun getObj(clazz: Class): T + fun getObj(): T = getObj(clazz) } \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/SimpleActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/SimpleActor.kt index 1ce37950..e469e98c 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/SimpleActor.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/SimpleActor.kt @@ -20,4 +20,3 @@ open class SimpleActor( override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): String = response(*messages, api = api).choices.first().message?.content ?: throw RuntimeException("No response") } - diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/ActorOptimization.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/ActorOptimization.kt index d58315a6..49eb36aa 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/ActorOptimization.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/ActorOptimization.kt @@ -188,7 +188,7 @@ open class ActorOptimization( ).create() companion object { - val log = LoggerFactory.getLogger(ActorOptimization::class.java) + private val log = LoggerFactory.getLogger(ActorOptimization::class.java) } } diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/Expectation.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/Expectation.kt index 39d29f01..d94f3a1f 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/Expectation.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/opt/Expectation.kt @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory abstract class Expectation { companion object { - val log = LoggerFactory.getLogger(Expectation::class.java) + private val log = LoggerFactory.getLogger(Expectation::class.java) } open class VectorMatch(val example: String, private val metric: DistanceType = DistanceType.Cosine) : Expectation() { diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingCodingActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingCodingActor.kt new file mode 100644 index 00000000..4d5a8de7 --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingCodingActor.kt @@ -0,0 +1,85 @@ +package com.simiacryptus.skyenet.actors.record + +import com.simiacryptus.openai.OpenAIClient +import com.simiacryptus.openai.models.OpenAIModel +import com.simiacryptus.skyenet.actors.CodeResult +import com.simiacryptus.skyenet.actors.CodingActor +import com.simiacryptus.skyenet.util.FunctionWrapper + +class RecordingCodingActor( + val inner: CodingActor, + val functionInterceptor: FunctionWrapper, +) : CodingActor( + interpreterClass = inner.interpreterClass, + symbols = inner.symbols, + describer = inner.describer, + name = inner.name, + details = inner.details, + model = inner.model, + fallbackModel = inner.fallbackModel, + temperature = inner.temperature, + autoEvaluate = inner.autoEvaluate, +) { + override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): CodeResult { + return functionInterceptor.wrap(messages.toList().toTypedArray()) { + messages: Array -> + RecordingCodeResultImpl(*messages, api = api, inner = inner.answer(*messages, api = api)) + } + } + + private inner class RecordingCodeResultImpl( + vararg messages: OpenAIClient.ChatMessage, + val inner: CodeResult, + api: OpenAIClient, + ) : CodeResult { + override fun getStatus() = functionInterceptor.wrap { inner.getStatus() } + override fun getCode() = functionInterceptor.wrap { inner.getCode() } + override fun run() = functionInterceptor.wrap { inner.run() } + + } + + override fun response( + vararg messages: OpenAIClient.ChatMessage, + model: OpenAIModel, + api: OpenAIClient + ) = functionInterceptor.wrap(messages.toList().toTypedArray(), model) { + messages: Array, + model: OpenAIModel -> + inner.response(*messages, model = model, api = api) + } + + override fun chatMessages(vararg questions: String) = functionInterceptor.wrap(questions) { + inner.chatMessages(*it) + } + + override fun answer(vararg questions: String, api: OpenAIClient) = functionInterceptor.wrap(questions) { + inner.answer(*it, api = api) + } + + override fun answerWithPrefix( + codePrefix: String, + vararg messages: OpenAIClient.ChatMessage, + api: OpenAIClient + ) = functionInterceptor.wrap(messages.toList().toTypedArray(), codePrefix) { + messages: Array, + codePrefix: String -> + inner.answerWithPrefix(codePrefix, *messages, api = api) + } + + override fun answerWithAutoEval( + vararg messages: String, + api: OpenAIClient, + codePrefix: String + ) = functionInterceptor.wrap(messages.toList().toTypedArray(), codePrefix) { + messages: Array, + codePrefix: String -> + inner.answerWithAutoEval(*messages, api = api, codePrefix = codePrefix) + } + + override fun answerWithAutoEval( + vararg messages: OpenAIClient.ChatMessage, + api: OpenAIClient + ) = functionInterceptor.wrap(messages.toList().toTypedArray()) { + inner.answerWithAutoEval(*messages, api = api) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingParsedActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingParsedActor.kt new file mode 100644 index 00000000..9db43134 --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingParsedActor.kt @@ -0,0 +1,49 @@ +package com.simiacryptus.skyenet.actors.record + +import com.simiacryptus.openai.OpenAIClient +import com.simiacryptus.openai.models.OpenAIModel +import com.simiacryptus.skyenet.actors.ParsedActor +import com.simiacryptus.skyenet.actors.ParsedResponse +import com.simiacryptus.skyenet.util.FunctionWrapper + +class RecordingParsedActor( + val inner: ParsedActor, + val functionInterceptor: FunctionWrapper, +) : ParsedActor( + parserClass = inner.parserClass, + prompt = inner.prompt, + action = inner.action, + model = inner.model, + temperature = inner.temperature, +) { + private inner class RecordingParsedResponseImpl(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient, val inner: ParsedResponse) : + ParsedResponse(this@RecordingParsedActor.inner.resultClass) { + override fun getText() = functionInterceptor.wrap { inner.getText() } + override fun getObj(clazz: Class) = functionInterceptor.intercept(clazz) { inner.getObj(clazz) } // <-- Cannot use 'T' as reified type parameter. Use a class instead. + } + + override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient): ParsedResponse { + return functionInterceptor.wrap(messages.toList().toTypedArray()) { + messages: Array -> + RecordingParsedResponseImpl(*messages, api = api, inner = inner.answer(*messages, api = api)) + } + } + + override fun response( + vararg messages: OpenAIClient.ChatMessage, + model: OpenAIModel, + api: OpenAIClient + ) = functionInterceptor.wrap(messages.toList().toTypedArray(), model) { + messages: Array, + model: OpenAIModel -> + inner.response(*messages, model = model, api = api) + } + + override fun answer(vararg questions: String, api: OpenAIClient) = functionInterceptor.wrap(questions) { + inner.answer(*it, api = api) + } + + override fun chatMessages(vararg questions: String) = functionInterceptor.wrap(questions) { + inner.chatMessages(*it) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingSimpleActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingSimpleActor.kt new file mode 100644 index 00000000..8f55aa1d --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/record/RecordingSimpleActor.kt @@ -0,0 +1,41 @@ +package com.simiacryptus.skyenet.actors.record + +import com.simiacryptus.openai.OpenAIClient +import com.simiacryptus.openai.models.OpenAIModel +import com.simiacryptus.skyenet.actors.SimpleActor +import com.simiacryptus.skyenet.util.FunctionWrapper + +class RecordingSimpleActor( + val inner: SimpleActor, + val functionInterceptor: FunctionWrapper, +) : SimpleActor( + prompt = inner.prompt, + name = inner.name, + model = inner.model, + temperature = inner.temperature, +) { + + override fun answer(vararg messages: OpenAIClient.ChatMessage, api: OpenAIClient) = + functionInterceptor.wrap(messages.toList().toTypedArray()) { + messages: Array -> + inner.answer(*messages, api = api) + } + + override fun response( + vararg messages: OpenAIClient.ChatMessage, + model: OpenAIModel, + api: OpenAIClient + ) = functionInterceptor.wrap(messages.toList().toTypedArray(), model) { + messages: Array, + model: OpenAIModel -> + inner.response(*messages, model = model, api = api) + } + + override fun chatMessages(vararg questions: String) = functionInterceptor.wrap(questions) { + inner.chatMessages(*it) + } + + override fun answer(vararg questions: String, api: OpenAIClient) = functionInterceptor.wrap(questions) { + inner.answer(*it, api = api) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ActorTestBase.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ActorTestBase.kt new file mode 100644 index 00000000..2475b24e --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ActorTestBase.kt @@ -0,0 +1,54 @@ +package com.simiacryptus.skyenet.actors.test + +import com.simiacryptus.openai.OpenAIClient +import com.simiacryptus.skyenet.actors.BaseActor +import com.simiacryptus.skyenet.actors.opt.ActorOptimization +import org.slf4j.LoggerFactory +import org.slf4j.event.Level + +abstract class ActorTestBase { + + open val api = OpenAIClient(logLevel = Level.DEBUG) + + abstract val testCases: List + abstract val actor: BaseActor + abstract fun actorFactory(prompt: String): BaseActor + abstract fun getPrompt(actor: BaseActor): String + abstract fun resultMapper(result: R): String + + open fun opt( + actor: BaseActor = this.actor, + testCases: List = this.testCases, + actorFactory: (String) -> BaseActor = this::actorFactory, + resultMapper: (R) -> String = this::resultMapper + ) { + ActorOptimization( + api + ).runGeneticGenerations( + populationSize = 1, + generations = 1, + selectionSize = 1, + actorFactory = actorFactory, + resultMapper = resultMapper, + prompts = listOf( + getPrompt(actor), + ), + testCases = testCases, + ) + } + + open fun testOptimize() { + opt() + } + + open fun testRun() { + testCases.forEach { testCase -> + val answer = actor.answer(questions = testCase.userMessages.toTypedArray(), api) + log.info("Answer: ${resultMapper(answer)}") + } + } + + companion object { + private val log = LoggerFactory.getLogger(ActorTestBase::class.java) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/CodingActorTestBase.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/CodingActorTestBase.kt new file mode 100644 index 00000000..33392132 --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/CodingActorTestBase.kt @@ -0,0 +1,19 @@ +package com.simiacryptus.skyenet.actors.test + +import com.simiacryptus.skyenet.Heart +import com.simiacryptus.skyenet.actors.BaseActor +import com.simiacryptus.skyenet.actors.CodeResult +import com.simiacryptus.skyenet.actors.CodingActor +import kotlin.reflect.KClass + +abstract class CodingActorTestBase : ActorTestBase() { + abstract val interpreterClass: KClass + override fun actorFactory(prompt: String): CodingActor = CodingActor( + interpreterClass = interpreterClass, + details = prompt, + ) + + override fun getPrompt(actor: BaseActor): String = (actor as CodingActor).details!! + override fun resultMapper(result: CodeResult): String = result.getCode() +} + diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ParsedActorTestBase.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ParsedActorTestBase.kt new file mode 100644 index 00000000..739d3bfd --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/test/ParsedActorTestBase.kt @@ -0,0 +1,21 @@ +package com.simiacryptus.skyenet.actors.test + +import com.simiacryptus.skyenet.actors.BaseActor +import com.simiacryptus.skyenet.actors.ParsedActor +import com.simiacryptus.skyenet.actors.ParsedResponse +import java.util.function.Function + +abstract class ParsedActorTestBase( + val parserClass: Class>, +) : ActorTestBase>() { + + override fun actorFactory(prompt: String) = ParsedActor( + parserClass = parserClass, + prompt = prompt, + ) + + override fun getPrompt(actor: BaseActor>): String = actor.prompt + + override fun resultMapper(result: ParsedResponse): String = result.getText() + +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/config/ApplicationServices.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/ApplicationServices.kt similarity index 96% rename from core/src/main/kotlin/com/simiacryptus/skyenet/config/ApplicationServices.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/platform/ApplicationServices.kt index 2ad5f7cc..3ae1357e 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/config/ApplicationServices.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/ApplicationServices.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.config +package com.simiacryptus.skyenet.platform import java.io.File diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/config/AuthenticationManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt similarity index 94% rename from core/src/main/kotlin/com/simiacryptus/skyenet/config/AuthenticationManager.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt index 14d89b6c..71c729c6 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/config/AuthenticationManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.config +package com.simiacryptus.skyenet.platform import java.util.HashMap diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/config/AuthorizationManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt similarity index 93% rename from core/src/main/kotlin/com/simiacryptus/skyenet/config/AuthorizationManager.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt index 81aa96a7..65b87ac8 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/config/AuthorizationManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.config +package com.simiacryptus.skyenet.platform import java.util.* @@ -53,6 +53,6 @@ open class AuthorizationManager { } } ?: false - val log = org.slf4j.LoggerFactory.getLogger(AuthorizationManager::class.java) + private val log = org.slf4j.LoggerFactory.getLogger(AuthorizationManager::class.java) } \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/config/DataStorage.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt similarity index 99% rename from core/src/main/kotlin/com/simiacryptus/skyenet/config/DataStorage.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt index b3d5bd31..8627cfa3 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/config/DataStorage.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.config +package com.simiacryptus.skyenet.platform import com.simiacryptus.util.JsonUtil import java.io.File diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/config/UsageManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt similarity index 99% rename from core/src/main/kotlin/com/simiacryptus/skyenet/config/UsageManager.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt index c37cfbbf..db28493f 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/config/UsageManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.config +package com.simiacryptus.skyenet.platform import com.simiacryptus.openai.OpenAIClient import com.simiacryptus.openai.models.* diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/config/UserSettingsManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt similarity index 96% rename from core/src/main/kotlin/com/simiacryptus/skyenet/config/UserSettingsManager.kt rename to core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt index 7aee7390..6cec9b29 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/config/UserSettingsManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt @@ -1,4 +1,4 @@ -package com.simiacryptus.skyenet.config +package com.simiacryptus.skyenet.platform import com.simiacryptus.util.JsonUtil import java.io.File diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/util/FunctionWrapper.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/util/FunctionWrapper.kt new file mode 100644 index 00000000..45614c9f --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/util/FunctionWrapper.kt @@ -0,0 +1,71 @@ +@file:Suppress("unused") + +package com.simiacryptus.skyenet.util + +import com.simiacryptus.util.JsonUtil +import java.io.Closeable +import java.io.File + +class FunctionWrapper(val inner: FunctionInterceptor) : FunctionInterceptor { + inline fun wrap(crossinline fn: () -> T) = inner.intercept(T::class.java) { fn() } + inline fun wrap(p: P, crossinline fn: (P) -> T) = inner.intercept(p, T::class.java) { fn(it) } + inline fun wrap(p1: P1, p2: P2, crossinline fn: (P1, P2) -> T) = + inner.intercept(p1, p2, T::class.java) { p1, p2 -> fn(p1, p2) } + + override fun intercept(returnClazz: Class, fn: () -> T) = inner.intercept(returnClazz, fn) + + override fun

intercept(p: P, returnClazz: Class, fn: (P) -> T) = inner.intercept(p, returnClazz, fn) + + override fun intercept( + p1: P1, + p2: P2, + returnClazz: Class, + fn: (P1, P2) -> T + ) = inner.intercept(p1, p2, returnClazz, fn) +} + +interface FunctionInterceptor { + fun intercept(returnClazz: Class, fn: () -> T) = fn() + fun intercept(p: P, returnClazz: Class, fn: (P) -> T) = fn(p) + fun intercept(p1: P1, p2: P2, returnClazz: Class, fn: (P1, P2) -> T) = + intercept(listOf(p1, p2), returnClazz) { + @Suppress("UNCHECKED_CAST") + fn(it[0] as P1, it[1] as P2) + } +} + +class NoopFunctionInterceptor : FunctionInterceptor { + override fun intercept(returnClazz: Class, fn: () -> T) = fn() + override fun intercept(p: P, returnClazz: Class, fn: (P) -> T) = fn(p) +} + +class JsonFunctionRecorder(file: File) : FunctionInterceptor, Closeable { + private val fileOutput = file.outputStream().bufferedWriter() + + override fun close() { + fileOutput.close() + } + + override fun intercept(returnClazz: Class, fn: () -> T): T { + val result = fn() + synchronized(fileOutput) { + fileOutput.append(JsonUtil.toJson(result)) + fileOutput.flush() + } + return result + } + + override fun intercept(p: P, returnClazz: Class, fn: (P) -> T): T { + synchronized(fileOutput) { + fileOutput.append(JsonUtil.toJson(p)) + fileOutput.append("\n") + fileOutput.flush() + } + val result = fn(p) + synchronized(fileOutput) { + fileOutput.append(JsonUtil.toJson(result)) + fileOutput.flush() + } + return result + } +} diff --git a/core/src/test/java/com/simiacryptus/skyenet/DataStorageTest.kt b/core/src/test/java/com/simiacryptus/skyenet/DataStorageTest.kt index 7c3d6a04..3e663e4e 100644 --- a/core/src/test/java/com/simiacryptus/skyenet/DataStorageTest.kt +++ b/core/src/test/java/com/simiacryptus/skyenet/DataStorageTest.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.DataStorage import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue diff --git a/gradle.properties b/gradle.properties index 29d3c83a..b12c796e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.0.33 +libraryVersion = 1.0.34 gradleVersion = 7.6.1 # Opt-out flag for bundling Kotlin standard library -> https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library diff --git a/kotlin/src/main/kotlin/com/simiacryptus/skyenet/heart/KotlinInterpreter.kt b/kotlin/src/main/kotlin/com/simiacryptus/skyenet/heart/KotlinInterpreter.kt index 1e71b9e9..ac634bf5 100644 --- a/kotlin/src/main/kotlin/com/simiacryptus/skyenet/heart/KotlinInterpreter.kt +++ b/kotlin/src/main/kotlin/com/simiacryptus/skyenet/heart/KotlinInterpreter.kt @@ -247,7 +247,7 @@ open class KotlinInterpreter( } companion object { - val log = LoggerFactory.getLogger(KotlinInterpreter::class.java) + private val log = LoggerFactory.getLogger(KotlinInterpreter::class.java) val storageMap = WeakHashMap() val retrievalIndex = HashMap>() diff --git a/webui/build.gradle.kts b/webui/build.gradle.kts index 344fcd0b..7cae2c95 100644 --- a/webui/build.gradle.kts +++ b/webui/build.gradle.kts @@ -32,7 +32,7 @@ val jetty_version = "11.0.18" val jackson_version = "2.15.3" dependencies { - implementation(group = "com.simiacryptus", name = "joe-penai", version = "1.0.31") + implementation(group = "com.simiacryptus", name = "joe-penai", version = "1.0.32") implementation(project(":core")) testImplementation(project(":groovy")) diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt index 98017fcf..a811b360 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt @@ -2,16 +2,15 @@ package com.simiacryptus.skyenet import com.simiacryptus.skyenet.chat.ChatServer import com.simiacryptus.skyenet.chat.ChatSocket -import com.simiacryptus.skyenet.config.ApplicationServices.authenticationManager -import com.simiacryptus.skyenet.config.ApplicationServices.authorizationManager -import com.simiacryptus.skyenet.config.ApplicationServices.dataStorageFactory -import com.simiacryptus.skyenet.config.AuthenticationManager +import com.simiacryptus.skyenet.platform.ApplicationServices.authenticationManager +import com.simiacryptus.skyenet.platform.ApplicationServices.authorizationManager +import com.simiacryptus.skyenet.platform.ApplicationServices.dataStorageFactory import com.simiacryptus.skyenet.servlet.* -import com.simiacryptus.skyenet.config.AuthenticationManager.Companion.COOKIE_NAME +import com.simiacryptus.skyenet.platform.AuthenticationManager.Companion.COOKIE_NAME import com.simiacryptus.skyenet.session.SessionBase import com.simiacryptus.skyenet.session.SessionDiv import com.simiacryptus.skyenet.session.SessionInterface -import com.simiacryptus.skyenet.config.AuthorizationManager +import com.simiacryptus.skyenet.platform.AuthorizationManager import com.simiacryptus.skyenet.util.HtmlTools import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -131,7 +130,7 @@ abstract class ApplicationBase( companion object { - val log = LoggerFactory.getLogger(ApplicationBase::class.java) + private val log = LoggerFactory.getLogger(ApplicationBase::class.java) val spinner = """

Loading...
""" diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationDirectory.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationDirectory.kt index b8bf1c14..e355ffb3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationDirectory.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationDirectory.kt @@ -3,7 +3,7 @@ package com.simiacryptus.skyenet import com.simiacryptus.openai.OpenAIClientBase import com.simiacryptus.skyenet.chat.ChatServer -import com.simiacryptus.skyenet.config.ApplicationServices +import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.skyenet.servlet.* import com.simiacryptus.skyenet.util.AwsUtil.decryptResource import jakarta.servlet.DispatcherType @@ -151,7 +151,7 @@ abstract class ApplicationDirectory( companion object { - val log = LoggerFactory.getLogger(ApplicationDirectory::class.java) + private val log = LoggerFactory.getLogger(ApplicationDirectory::class.java) } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatServer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatServer.kt index b3d0c52c..2e514d72 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatServer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatServer.kt @@ -1,8 +1,8 @@ package com.simiacryptus.skyenet.chat -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.AuthenticationManager +import com.simiacryptus.skyenet.platform.DataStorage import com.simiacryptus.skyenet.servlet.NewSessionServlet import com.simiacryptus.skyenet.session.SessionInterface import com.simiacryptus.util.JsonUtil @@ -81,7 +81,7 @@ abstract class ChatServer(val resourceBase: String) { } companion object { - val log = org.slf4j.LoggerFactory.getLogger(ChatServer::class.java) + private val log = org.slf4j.LoggerFactory.getLogger(ChatServer::class.java) } } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSession.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSession.kt index 2c252b23..a1d4596a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSession.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSession.kt @@ -12,8 +12,8 @@ open class ChatSession( val parent: ChatServer, sessionId: String, val model: OpenAITextModel = ChatModels.GPT35Turbo, - val visiblePrompt: String, - val hiddenPrompt: String, + val userInterfacePrompt: String, + val initialAssistantPrompt: String = "", val systemPrompt: String, val api: OpenAIClient, val temperature: Double = 0.3, @@ -21,8 +21,8 @@ open class ChatSession( ) : SessionBase(sessionId, parent.dataStorage, userId = null, applicationClass = applicationClass) { init { - if (visiblePrompt.isNotBlank()) { - send("""aaa,
${visiblePrompt}
""") + if (userInterfacePrompt.isNotBlank()) { + send("""aaa,
${MarkdownUtil.renderMarkdown(userInterfacePrompt)}
""") } } @@ -30,7 +30,7 @@ open class ChatSession( val list = listOf( OpenAIClient.ChatMessage(OpenAIClient.Role.system, systemPrompt.toContentList()), ).toMutableList() - if(hiddenPrompt.isNotBlank()) list += OpenAIClient.ChatMessage(OpenAIClient.Role.assistant, hiddenPrompt.toContentList()) + if(initialAssistantPrompt.isNotBlank()) list += OpenAIClient.ChatMessage(OpenAIClient.Role.assistant, initialAssistantPrompt.toContentList()) list } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSocket.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSocket.kt index 43bc1059..f91db2b5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSocket.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSocket.kt @@ -2,12 +2,12 @@ package com.simiacryptus.skyenet.chat import com.simiacryptus.openai.models.OpenAIModel import com.simiacryptus.openai.OpenAIClient -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.ApplicationServices.authorizationManager -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.ApplicationServices.authorizationManager +import com.simiacryptus.skyenet.platform.AuthenticationManager +import com.simiacryptus.skyenet.platform.DataStorage import com.simiacryptus.skyenet.session.SessionInterface -import com.simiacryptus.skyenet.config.AuthorizationManager.OperationType.GlobalKey +import com.simiacryptus.skyenet.platform.AuthorizationManager.OperationType.GlobalKey import org.eclipse.jetty.websocket.api.Session import org.eclipse.jetty.websocket.api.WebSocketAdapter import org.slf4j.event.Level @@ -62,7 +62,7 @@ class ChatSocket( override fun onWebSocketConnect(session: Session) { super.onWebSocketConnect(session) - ChatServer.log.debug("{} - Socket connected: {}", sessionId, session.remote) + log.debug("{} - Socket connected: {}", sessionId, session.remote) sessionState.addSocket(this) sessionState.getReplay().forEach { try { @@ -83,4 +83,7 @@ class ChatSocket( sessionState.removeSocket(this) } + companion object { + private val log = org.slf4j.LoggerFactory.getLogger(ChatSocket::class.java) + } } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt new file mode 100644 index 00000000..cf8c537e --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt @@ -0,0 +1,68 @@ +package com.simiacryptus.skyenet.chat + +import com.simiacryptus.openai.OpenAIClient +import com.simiacryptus.openai.models.ChatModels +import com.simiacryptus.openai.models.OpenAITextModel +import com.simiacryptus.skyenet.ApplicationBase +import com.simiacryptus.skyenet.util.ClasspathResource +import org.eclipse.jetty.servlet.ServletHolder +import org.eclipse.jetty.util.resource.Resource +import org.eclipse.jetty.webapp.WebAppContext + +class CodeChatServer( + val language: String, + val codeSelection: String, + val api: OpenAIClient, + val model: OpenAITextModel = ChatModels.GPT35Turbo, + resourceBase: String = "codeChat", +) : ChatServer( + resourceBase = resourceBase, +) { + override val applicationName: String get() = "Code Chat" + + override fun newSession(userId: String?, sessionId: String) = object : ChatSession( + sessionId = sessionId, + parent = this@CodeChatServer, + model = model, + api = api, + userInterfacePrompt = """ + |# Code: + | + |```$language + |${htmlEscape(codeSelection)} + |``` + | + """.trimMargin().trim(), + systemPrompt = """ + |You are a helpful AI that helps people with coding. + | + |You will be answering questions about the following code: + | + |```$language + |$codeSelection + |``` + | + |Responses may use markdown formatting. + """.trimMargin(), + applicationClass = ApplicationBase::class.java, + ) { + override fun canWrite(user: String?): Boolean = true + } + + override val baseResource: Resource + get() = ClasspathResource(javaClass.classLoader.getResource(resourceBase)!!) + + override fun configure(webAppContext: WebAppContext, path: String, baseUrl: String) { + webAppContext.addServlet(ServletHolder(javaClass.simpleName + "/appInfo", AppInfoServlet()), "/appInfo") + super.configure(webAppContext, path, baseUrl) + } + + companion object { + fun htmlEscape(html: String) = html + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AuthenticatedWebsite.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AuthenticatedWebsite.kt index 37322012..3d399e29 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AuthenticatedWebsite.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AuthenticatedWebsite.kt @@ -8,9 +8,9 @@ import com.google.api.client.json.gson.GsonFactory import com.google.api.services.oauth2.Oauth2 import com.google.api.services.oauth2.model.Userinfo import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.AuthenticationManager.Companion.COOKIE_NAME +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.AuthenticationManager +import com.simiacryptus.skyenet.platform.AuthenticationManager.Companion.COOKIE_NAME import jakarta.servlet.* import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServlet diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/FileServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/FileServlet.kt index c1d6693d..6574a2a3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/FileServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/FileServlet.kt @@ -2,9 +2,8 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.DataStorage 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/servlet/NewSessionServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/NewSessionServlet.kt index ed26808e..022fec7f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/NewSessionServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/NewSessionServlet.kt @@ -1,6 +1,6 @@ package com.simiacryptus.skyenet.servlet -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.DataStorage 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/servlet/SessionListServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionListServlet.kt index b7220c1a..c7c9027f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionListServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionListServlet.kt @@ -1,9 +1,8 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.DataStorage import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -38,8 +37,8 @@ class SessionListServlet( ${sessions.joinToString("") { session -> """ - - ${sessionName(req, session)} + + ${sessionName(req, session)} """.trimIndent() }} diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionSettingsServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionSettingsServlet.kt index 9203645b..6020fb6d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionSettingsServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionSettingsServlet.kt @@ -2,8 +2,7 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager +import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.util.JsonUtil import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UsageServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UsageServlet.kt index f7abc95e..6304a24c 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UsageServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UsageServlet.kt @@ -4,7 +4,7 @@ import com.simiacryptus.openai.OpenAIClient import com.simiacryptus.openai.models.OpenAIModel import com.simiacryptus.openai.models.OpenAITextModel import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices +import com.simiacryptus.skyenet.platform.ApplicationServices import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -84,7 +84,7 @@ class UsageServlet : HttpServlet() { } companion object { - val log = org.slf4j.LoggerFactory.getLogger(UsageServlet::class.java) + private val log = org.slf4j.LoggerFactory.getLogger(UsageServlet::class.java) } } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserInfoServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserInfoServlet.kt index db5dde9a..6352e82a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserInfoServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserInfoServlet.kt @@ -1,8 +1,7 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager.Companion.COOKIE_NAME +import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.util.JsonUtil import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserSettingsServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserSettingsServlet.kt index d8288cf7..d99727c1 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserSettingsServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserSettingsServlet.kt @@ -1,9 +1,8 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager.Companion.COOKIE_NAME -import com.simiacryptus.skyenet.config.UserSettingsManager.UserSettings +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.UserSettingsManager.UserSettings import com.simiacryptus.util.JsonUtil import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest @@ -52,6 +51,6 @@ class UserSettingsServlet : HttpServlet() { } companion object { - val log = org.slf4j.LoggerFactory.getLogger(UserSettingsServlet::class.java) + private val log = org.slf4j.LoggerFactory.getLogger(UserSettingsServlet::class.java) } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/WelcomeServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/WelcomeServlet.kt index e45106db..667f6605 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/WelcomeServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/WelcomeServlet.kt @@ -3,10 +3,10 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie import com.simiacryptus.skyenet.ApplicationDirectory -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.AuthorizationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.AuthenticationManager +import com.simiacryptus.skyenet.platform.AuthorizationManager +import com.simiacryptus.skyenet.platform.DataStorage 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/servlet/ZipServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/ZipServlet.kt index 0f8e52dc..ff3016d2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/ZipServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/ZipServlet.kt @@ -1,9 +1,8 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.DataStorage 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/session/SessionBase.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionBase.kt index a6a4e4f4..a3b1423d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionBase.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionBase.kt @@ -4,9 +4,10 @@ import com.google.common.util.concurrent.MoreExecutors import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.chat.ChatServer import com.simiacryptus.skyenet.chat.ChatSocket -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthorizationManager -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.AuthorizationManager +import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.util.MarkdownUtil import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger @@ -109,7 +110,7 @@ abstract class SessionBase( } } catch (e: Exception) { log.warn("$sessionId - Error processing message: $message", e) - send("""${randomID()},
${e.message}
""") + send("""${randomID()},
${MarkdownUtil.renderMarkdown(e.message ?: "")}
""") } } else { log.warn("$sessionId - Unauthorized message: $message") @@ -136,7 +137,7 @@ abstract class SessionBase( ) companion object { - val log = org.slf4j.LoggerFactory.getLogger(ChatServer::class.java) + private val log = org.slf4j.LoggerFactory.getLogger(ChatServer::class.java) fun randomID() = (0..5).map { ('a'..'z').random() }.joinToString("") fun divInitializer(operationID: String = randomID(), cancelable: Boolean): String = diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/CodingActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/CodingActorTestApp.kt index b29b0196..acb3c14a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/CodingActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/CodingActorTestApp.kt @@ -3,9 +3,9 @@ package com.simiacryptus.skyenet.test import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.actors.CodingActor import com.simiacryptus.skyenet.chat.ChatSocket -import com.simiacryptus.skyenet.config.ApplicationServices +import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.skyenet.session.* -import com.simiacryptus.skyenet.config.AuthorizationManager +import com.simiacryptus.skyenet.platform.AuthorizationManager import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.util.* @@ -18,7 +18,6 @@ open class CodingActorTestApp( applicationName = applicationName, temperature = temperature, ) { - override fun processMessage( sessionId: String, userMessage: String, @@ -34,19 +33,16 @@ open class CodingActorTestApp( AuthorizationManager.OperationType.Execute ) val playLink = if(!canPlay) "" else { - val htmlTools = session.htmlTools(sessionDiv.divID()) - """${ - htmlTools.hrefLink("href-link play-button") { - sessionDiv.append("""
Running...
""", true) - val result = response.run() - sessionDiv.append( - """ - |
${result.resultValue}
- |
${result.resultOutput}
- """.trimMargin(), false - ) - } - }â–¶""" + session.htmlTools(sessionDiv.divID()).hrefLink("â–¶", "href-link play-button") { + sessionDiv.append("""
Running...
""", true) + val result = response.run() + sessionDiv.append( + """ + |
${result.resultValue}
+ |
${result.resultOutput}
+ """.trimMargin(), false + ) + } } sessionDiv.append("""
${ renderMarkdown(""" @@ -58,9 +54,7 @@ open class CodingActorTestApp( }
""", false) } - companion object { - val log = LoggerFactory.getLogger(CodingActorTestApp::class.java) + private val log = LoggerFactory.getLogger(CodingActorTestApp::class.java) } - } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/ParsedActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/ParsedActorTestApp.kt index 0b253d64..8b841249 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/ParsedActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/ParsedActorTestApp.kt @@ -3,16 +3,15 @@ package com.simiacryptus.skyenet.test import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.actors.ParsedActor import com.simiacryptus.skyenet.chat.ChatSocket -import com.simiacryptus.skyenet.session.* +import com.simiacryptus.skyenet.session.SessionDiv import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import com.simiacryptus.util.JsonUtil import org.slf4j.LoggerFactory -open class ParsedActorTestApp( +open class ParsedActorTestApp( private val actor: ParsedActor, applicationName: String = "ParsedActorTest_" + actor.parserClass.simpleName, temperature: Double = 0.3, - oauthConfig: String? = null, ) : ApplicationBase( applicationName = applicationName, temperature = temperature, @@ -26,18 +25,22 @@ open class ParsedActorTestApp( ) { sessionDiv.append("""
${renderMarkdown(userMessage)}
""", true) val response = actor.answer(userMessage, api = socket.api) - sessionDiv.append("""
${ - renderMarkdown(""" + sessionDiv.append( + """
${ + renderMarkdown( + """ |${response.getText()} |``` |${JsonUtil.toJson(response.getObj()!!)} |``` - """.trimMargin().trim()) - }
""", false) + """.trimMargin().trim() + ) + }
""", false + ) } companion object { - val log = LoggerFactory.getLogger(ParsedActorTestApp::class.java) + private val log = LoggerFactory.getLogger(ParsedActorTestApp::class.java) } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/SimpleActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/SimpleActorTestApp.kt index 1bd7dfd2..c84e7c1a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/SimpleActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/SimpleActorTestApp.kt @@ -11,7 +11,6 @@ open class SimpleActorTestApp( private val actor: SimpleActor, applicationName: String = "SimpleActorTest_" + actor.javaClass.simpleName, temperature: Double = 0.3, - oauthConfig: String? = null, ) : ApplicationBase( applicationName = applicationName, temperature = temperature, @@ -37,7 +36,7 @@ open class SimpleActorTestApp( } companion object { - val log = LoggerFactory.getLogger(SimpleActorTestApp::class.java) + private val log = LoggerFactory.getLogger(SimpleActorTestApp::class.java) } } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EmbeddingVisualizer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EmbeddingVisualizer.kt index 6f374c1e..62b1d7a9 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EmbeddingVisualizer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EmbeddingVisualizer.kt @@ -1,9 +1,8 @@ package com.simiacryptus.skyenet.util -import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.OpenAIClient import com.simiacryptus.openai.models.EmbeddingModels -import com.simiacryptus.skyenet.config.DataStorage +import com.simiacryptus.skyenet.platform.DataStorage import com.simiacryptus.skyenet.session.SessionBase import com.simiacryptus.util.JsonUtil diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/util/HtmlTools.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/HtmlTools.kt index a8c55e16..16d4aaa2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/util/HtmlTools.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/HtmlTools.kt @@ -14,18 +14,18 @@ class HtmlTools( val regenButton: String get() = """""" private val txtTriggers = mutableMapOf>() - fun hrefLink(classname: String = """href-link""", handler: Consumer): String { + fun hrefLink(linkText : String, classname: String = """href-link""", handler: Consumer): String { val operationID = SessionBase.randomID() app.linkTriggers[operationID] = handler - return """""" + return """$linkText""" } fun textInput(handler: Consumer): String { val operationID = SessionBase.randomID() txtTriggers[operationID] = handler //language=HTML return """
- - -
""".trimIndent() + + + """.trimIndent() } } \ No newline at end of file diff --git a/webui/src/main/resources/codeChat/chat.css b/webui/src/main/resources/codeChat/chat.css new file mode 100644 index 00000000..380271af --- /dev/null +++ b/webui/src/main/resources/codeChat/chat.css @@ -0,0 +1,291 @@ +:root { + --primary-bg-color: #f7f7f7; + --secondary-bg-color: #ffffff; + --primary-text-color: #333; + --secondary-text-color: #555; + --link-color: #0066cc; + --link-hover-color: #0044aa; + --border-radius: 4px; + --box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + --transition-speed: 0.3s; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + color: var(--primary-text-color); + background-color: var(--primary-bg-color); + margin: 0; + padding: 0; + overflow: clip; +} + +#messages { + position: relative; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 10px 10px 10px; + flex-grow: 1; + flex-shrink: 1; + background-color: var(--secondary-bg-color); + box-shadow: var(--box-shadow); + overflow-y: auto; +} + +.chat-input { + background-color: var(--secondary-bg-color); + border-radius: var(--border-radius); + padding: 10px; + margin-bottom: 10px; + overflow: auto; + resize: vertical; + flex: 1; + border: 1px solid #ddd; + box-shadow: var(--box-shadow); +} + +#main-input { + align-items: flex-start; + left: 0; + right: 0; + bottom: 0; + display: flex; + margin: 0; + padding: 0; + background-color: var(--primary-bg-color); + width: 100%; +} + +#session { + display: flex; + flex-direction: column; + height: 100vh; +} + +.reply-form { + width: 100%; +} + +.reply-input { + resize: vertical; +} + +.href-link { + text-decoration: none; + color: var(--link-color); + transition: color var(--transition-speed); +} + +.href-link:hover { + color: var(--link-hover-color); + text-decoration: underline; +} + +#message { + flex-grow: 1; + margin-right: 5px; +} + +.message-container { + position: relative; +} + +#disconnected-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 50; + justify-content: center; + align-items: center; + color: white; + font-size: 24px; +} + +#disconnected-overlay p { + font-size: 3rem; + line-height: 1.5; + margin-bottom: 20px; + animation: bounce var(--transition-speed) infinite alternate; + left: 10%; + position: relative; + color: firebrick; +} + +.spinner-border { + display: block; + width: 40px; + height: 40px; + border: 4px solid rgba(0, 0, 0, 0.1); + border-left-color: var(--link-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +#toolbar, #namebar { + background-color: var(--secondary-bg-color); + padding: 5px; + position: fixed; + top: 0; + text-align: left; + box-shadow: var(--box-shadow); +} + + +#toolbar { + width: 100%; + z-index: 2; +} + + +#namebar { + z-index: 3; + right: 0; +} + +#toolbar a, #namebar a { + color: var(--primary-text-color); + text-decoration: none; + padding: 5px; + transition: background-color var(--transition-speed); +} + +#toolbar a:hover, #namebar a:hover { + background-color: var(--secondary-text-color); + color: var(--secondary-bg-color); +} + +.modal { + display: none; + position: fixed; + z-index: 100; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.4); +} + +.modal-content { + background-color: var(--secondary-bg-color); + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 80%; + position: relative; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); +} + +.close { + color: var(--secondary-text-color); + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; +} + +.close:hover, +.close:focus { + color: var(--primary-text-color); +} + +pre { + white-space: pre-wrap; +} + +.play-button, .cancel-button { + font-size: 48px; + font-weight: bold; + border: none; + background: transparent; + cursor: pointer; + transition: transform var(--transition-speed); + text-decoration: none; +} + +.cancel-button { + right: 0; + position: absolute; +} + +.play-button:focus, .cancel-button:focus { + outline: none; +} + +.play-button:active, .cancel-button:active { + transform: scale(0.9); +} + +.error { + color: red; +} + +.verbose { + display: block; +} + +.verbose-hidden { + display: none; +} + + +@keyframes bounce { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-10px); + } +} + + + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.initial-prompt { + background-color: #f2f2f2; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; + font-family: Arial, sans-serif; + font-size: 16px; + color: #333; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.user-message { + background-color: #e6f7ff; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; + font-family: Arial, sans-serif; + font-size: 16px; + color: #333; + font-weight: bold; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} diff --git a/webui/src/main/resources/readOnly/chat.js b/webui/src/main/resources/codeChat/chat.js similarity index 87% rename from webui/src/main/resources/readOnly/chat.js rename to webui/src/main/resources/codeChat/chat.js index f946c020..f4d1571a 100644 --- a/webui/src/main/resources/readOnly/chat.js +++ b/webui/src/main/resources/codeChat/chat.js @@ -22,6 +22,9 @@ function getSessionId() { function send(message) { console.log('Sending message:', message); + if (socket.readyState !== 1) { + throw new Error('WebSocket is not open'); + } socket.send(message); } @@ -60,6 +63,8 @@ function connect(sessionId, customReceiveFunction) { } function showDisconnectedOverlay(show) { - const overlay = document.getElementById('disconnected-overlay'); - overlay.style.display = show ? 'block' : 'none'; + const elements = document.getElementsByClassName('ws-control'); + for (let i = 0; i < elements.length; i++) { + elements[i].disabled = show; + } } diff --git a/webui/src/main/resources/readOnly/favicon.svg b/webui/src/main/resources/codeChat/favicon.svg similarity index 100% rename from webui/src/main/resources/readOnly/favicon.svg rename to webui/src/main/resources/codeChat/favicon.svg diff --git a/webui/src/main/resources/codeChat/index.html b/webui/src/main/resources/codeChat/index.html new file mode 100644 index 00000000..7a626dcb --- /dev/null +++ b/webui/src/main/resources/codeChat/index.html @@ -0,0 +1,42 @@ + + + + + Code Chat + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+ + + + + + diff --git a/webui/src/main/resources/codeChat/main.js b/webui/src/main/resources/codeChat/main.js new file mode 100644 index 00000000..80db697d --- /dev/null +++ b/webui/src/main/resources/codeChat/main.js @@ -0,0 +1,162 @@ +function showModal(endpoint) { + fetchData(endpoint); + document.getElementById('modal').style.display = 'block'; +} + +function closeModal() { + document.getElementById('modal').style.display = 'none'; +} + +async function fetchData(endpoint) { + try { + // Add session id to the endpoint as a path parameter + const sessionId = getSessionId(); + if (sessionId) { + endpoint = endpoint + "?sessionId=" + sessionId; + } + const response = await fetch(endpoint); + const text = await response.text(); + document.getElementById('modal-content').innerHTML = "
" + text + "
"; + Prism.highlightAll(); + } catch (error) { + console.error('Error fetching data:', error); + } +} + +let messageVersions = {}; + +function onWebSocketText(event) { + console.log('WebSocket message:', event); + const messagesDiv = document.getElementById('messages'); + + // Parse message e.g. "id,version,content" + const firstCommaIndex = event.data.indexOf(','); + const secondCommaIndex = event.data.indexOf(',', firstCommaIndex + 1); + const messageId = event.data.substring(0, firstCommaIndex); + const messageVersion = event.data.substring(firstCommaIndex + 1, secondCommaIndex); + const messageContent = event.data.substring(secondCommaIndex + 1); + // If messageVersion isn't more than the version for the messageId using the version map, then ignore the message + if (messageVersion <= (messageVersions[messageId] || 0)) { + console.log("Ignoring message with id " + messageId + " and version " + messageVersion); + return; + } else { + messageVersions[messageId] = messageVersion; + } + + let messageDiv = document.getElementById(messageId); + + if (messageDiv) { + messageDiv.innerHTML = messageContent; + } else { + messageDiv = document.createElement('div'); + messageDiv.className = 'message message-container'; // Add the message-container class + messageDiv.id = messageId; + messageDiv.innerHTML = messageContent; + messagesDiv.appendChild(messageDiv); + } + + messagesDiv.scrollTop = messagesDiv.scrollHeight; + Prism.highlightAll(); +} + +document.addEventListener('DOMContentLoaded', () => { + + document.querySelector('.close').addEventListener('click', closeModal); + + window.addEventListener('click', (event) => { + if (event.target === document.getElementById('modal')) { + closeModal(); + } + }); + + const form = document.getElementById('main-input'); + const messageInput = document.getElementById('chat-input'); + + form.addEventListener('submit', (event) => { + event.preventDefault(); + send(messageInput.value); + messageInput.value = ''; + }); + + 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(); + + connect(undefined, onWebSocketText); + + document.body.addEventListener('click', (event) => { + const target = event.target; + if (target.classList.contains('play-button')) { + const messageId = target.getAttribute('data-id'); + send('!' + messageId + ',run'); + } else if (target.classList.contains('regen-button')) { + const messageId = target.getAttribute('data-id'); + send('!' + messageId + ',regen'); + } else if (target.classList.contains('cancel-button')) { + const messageId = target.getAttribute('data-id'); + send('!' + messageId + ',stop'); + } else if (target.classList.contains('href-link')) { + const messageId = target.getAttribute('data-id'); + send('!' + messageId + ',link'); + } else if (target.classList.contains('text-submit-button')) { + const messageId = target.getAttribute('data-id'); + const text = document.querySelector('.reply-input[data-id="' + messageId + '"]').value; + send('!' + messageId + ',userTxt,' + text); + } + }); + + fetch('appInfo') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + if (data.applicationName) { + document.title = data.applicationName; + } + }) + .catch(error => { + console.error('There was a problem with the fetch operation:', error); + }); + +}); + diff --git a/webui/src/main/resources/readOnly/chat.css b/webui/src/main/resources/readOnly/chat.css deleted file mode 100644 index 02cac400..00000000 --- a/webui/src/main/resources/readOnly/chat.css +++ /dev/null @@ -1,228 +0,0 @@ -#messages { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 40px; - overflow-y: auto; - padding: 10px; - padding-top: 40px; - flex-grow: 1; - flex-shrink: 1; -} - -.message { - background-color: #f0f0f0; - border-radius: 5px; - padding: 10px; - margin-bottom: 10px; - overflow: scroll; - z-index: 100; -} - -.reply-form { - width: 100%; -} - -.chat-input { - resize: vertical; -} - -.reply-input { - resize: vertical; -} - -.href-link { - text-decoration: underline; - color: blue; -} - -#message { - flex-grow: 1; - margin-right: 5px; -} - -.spinner-border { - display: block; - width: 40px; - height: 40px; - border: 4px solid rgba(0, 0, 0, 0.1); - border-left-color: #007bff; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -#toolbar { - background-color: #f1f1f1; - padding: 5px; - position: fixed; - top: 0; - width: 100%; - horiz-align: left; - z-index: 1; -} - -#toolbar a { - color: #000; - text-decoration: none; - padding: 5px; -} - -#toolbar a:hover { - background-color: #ddd; -} - - -#namebar { - background-color: #f1f1f1; - padding: 5px; - position: fixed; - top: 0; - right: 0; - horiz-align: right; - z-index: 2; -} - -#namebar a { - color: #000; - text-decoration: none; - padding: 5px; -} - -#namebar a:hover { - background-color: #ddd; -} - -/* Modal */ -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(0, 0, 0, 0.4); -} - -.modal-content { - background-color: #fefefe; - margin: 15% auto; - padding: 20px; - border: 1px solid #888; - width: 80%; - position: relative; -} - -.close { - color: #aaa; - float: right; - font-size: 28px; - font-weight: bold; - cursor: pointer; -} - -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; -} - -pre { - white-space: pre-wrap; /* Since CSS modules do not automatically apply this */ -} - -#container { - display: flex; - flex-direction: column; - height: 100vh; /* Adjust this value based on your preferred container height */ -} - -.play-button { - font-size: 48px; - font-weight: bold; - border: none; - background: transparent; - cursor: pointer; - transition: transform 0.1s; -} - -.play-button:focus { - outline: none; -} - -.play-button:active { - transform: scale(0.9); -} - - -.regen-button { - font-size: 48px; - font-weight: bold; - color: chartreuse; - border: none; - background: transparent; - cursor: pointer; - transition: transform 0.1s; -} - -.regen-button:focus { - outline: none; -} - -.regen-button:active { - transform: scale(0.9); -} - -.message-container { - position: relative; /* Add this line to set the position property of the parent div */ -} - -.cancel-button { - position: absolute; - top: 8px; - right: 8px; - font-size: 16px; - font-weight: bold; - border: none; - background: transparent; - cursor: pointer; - transition: transform 0.1s; -} - -.cancel-button:focus { - outline: none; -} - -.cancel-button:active { - transform: scale(0.9); -} - -.collapsible-content { - overflow: hidden; - max-height: 0; - transition: max-height 0.2s ease-out; -} diff --git a/webui/src/main/resources/readOnly/index.html b/webui/src/main/resources/readOnly/index.html deleted file mode 100644 index aeb8b50a..00000000 --- a/webui/src/main/resources/readOnly/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - WebSocket Client - - - - - - - - - - - - -
- Login -
- - - -
-
-
- -
-

Disconnected. Attempting to reconnect...

-
- - - - diff --git a/webui/src/main/resources/readOnly/main.js b/webui/src/main/resources/readOnly/main.js deleted file mode 100644 index 7629f48c..00000000 --- a/webui/src/main/resources/readOnly/main.js +++ /dev/null @@ -1,130 +0,0 @@ -function showModal(endpoint) { - fetchData(endpoint); - document.getElementById('modal').style.display = 'block'; -} - -function closeModal() { - document.getElementById('modal').style.display = 'none'; -} - -async function fetchData(endpoint) { - try { - // Add session id to the endpoint as a path parameter - const sessionId = getSessionId(); - if (sessionId) { - endpoint = endpoint + "?sessionId=" + sessionId; - } - const response = await fetch(endpoint); - const text = await response.text(); - document.getElementById('modal-content').innerHTML = "
" + text + "
"; - Prism.highlightAll(); - } catch (error) { - console.error('Error fetching data:', error); - } -} - -let messageVersions = {}; - -function onWebSocketText(event) { - console.log('WebSocket message:', event); - const messagesDiv = document.getElementById('messages'); - - // Parse message e.g. "id,version,content" - const firstCommaIndex = event.data.indexOf(','); - const secondCommaIndex = event.data.indexOf(',', firstCommaIndex + 1); - const messageId = event.data.substring(0, firstCommaIndex); - const messageVersion = event.data.substring(firstCommaIndex + 1, secondCommaIndex); - const messageContent = event.data.substring(secondCommaIndex + 1); - // If messageVersion isn't more than the version for the messageId using the version map, then ignore the message - if (messageVersion <= (messageVersions[messageId] || 0)) { - console.log("Ignoring message with id " + messageId + " and version " + messageVersion); - return; - } else { - messageVersions[messageId] = messageVersion; - } - - let messageDiv = document.getElementById(messageId); - - if (messageDiv) { - messageDiv.innerHTML = messageContent; - } else { - messageDiv = document.createElement('div'); - messageDiv.className = 'message message-container'; // Add the message-container class - messageDiv.id = messageId; - messageDiv.innerHTML = messageContent; - messagesDiv.appendChild(messageDiv); - } - - messagesDiv.scrollTop = messagesDiv.scrollHeight; - Prism.highlightAll(); -} - -document.addEventListener('DOMContentLoaded', () => { - - document.getElementById('history').addEventListener('click', () => showModal('sessions')); - document.getElementById('settings').addEventListener('click', () => showModal('settings')); - document.querySelector('.close').addEventListener('click', closeModal); - - window.addEventListener('click', (event) => { - if (event.target === document.getElementById('modal')) { - closeModal(); - } - }); - - const messageInput = document.getElementById('message'); - const usage = document.getElementById('usage'); - - const sessionId = getSessionId(); - if (sessionId) { - connect(sessionId, onWebSocketText); - usage.href = '/usage/?sessionId=' + sessionId; - } else { - connect(undefined, onWebSocketText); - } - - document.getElementById("files").addEventListener("click", function (event) { - event.preventDefault(); // Prevent the default behavior of the anchor tag - const sessionId = getSessionId(); - const url = "fileIndex/" + sessionId + "/"; - window.open(url, "_blank"); // Open the URL in a new tab - }); - - const loginLink = document.getElementById('username'); - if (loginLink) { - loginLink.href = '/googleLogin?redirect=' + encodeURIComponent(window.location.pathname); - } - - fetch('appInfo') - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - if (data.applicationName) { - document.title = data.applicationName; - } - }) - .catch(error => { - console.error('There was a problem with the fetch operation:', error); - }); - - fetch('userInfo') - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - if (data.name && loginLink) { - loginLink.innerHTML = data.name; - loginLink.href = "/userSettings"; - } - }) - .catch(error => { - console.error('There was a problem with the fetch operation:', error); - }); -}); - diff --git a/webui/src/main/resources/simpleSession/chat.css b/webui/src/main/resources/simpleSession/chat.css index a3b83606..98f9881e 100644 --- a/webui/src/main/resources/simpleSession/chat.css +++ b/webui/src/main/resources/simpleSession/chat.css @@ -267,3 +267,36 @@ pre { } } +/* Styling for user and response messages */ +.user-message, .response-message { + padding: 10px; + margin-bottom: 10px; + border-radius: 4px; +} + +.user-message { + background-color: #e7f4ff; + border: 1px solid #d0eaff; +} + +.response-message { + background-color: #fff; + border: 1px solid #eee; +} + +/* Styling for code blocks */ +pre.verbose, pre.response-message { + background-color: #f5f5f5; + border: 1px solid #e1e1e1; + border-radius: 4px; + padding: 15px; + overflow-x: auto; + font-family: 'Courier New', Courier, monospace; +} + +/* Styling for headers within the response */ +.response-header { + font-weight: bold; + margin-top: 20px; + margin-bottom: 10px; +} diff --git a/webui/src/main/resources/simpleSession/index.html b/webui/src/main/resources/simpleSession/index.html index 5e4f47df..f8381a9d 100644 --- a/webui/src/main/resources/simpleSession/index.html +++ b/webui/src/main/resources/simpleSession/index.html @@ -4,14 +4,23 @@ WebSocket Client - + + + + - + + + + + + +
Home @@ -41,10 +50,6 @@
-
-

Disconnected. Attempting to reconnect...

-
- diff --git a/webui/src/main/resources/simpleSession/main.js b/webui/src/main/resources/simpleSession/main.js index 6d371550..7b99f772 100644 --- a/webui/src/main/resources/simpleSession/main.js +++ b/webui/src/main/resources/simpleSession/main.js @@ -124,6 +124,40 @@ document.addEventListener('DOMContentLoaded', () => { } }); + 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(); + const sessionId = getSessionId(); if (sessionId) { connect(sessionId, onWebSocketText); @@ -197,5 +231,6 @@ document.addEventListener('DOMContentLoaded', () => { .catch(error => { console.error('There was a problem with the fetch operation:', error); }); + }); diff --git a/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt b/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt index 29c27d68..4dc514ca 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt @@ -3,9 +3,9 @@ package com.simiacryptus.skyenet import com.simiacryptus.skyenet.actors.CodingActor import com.simiacryptus.skyenet.actors.ParsedActor import com.simiacryptus.skyenet.actors.SimpleActor -import com.simiacryptus.skyenet.config.ApplicationServices -import com.simiacryptus.skyenet.config.AuthenticationManager -import com.simiacryptus.skyenet.config.AuthorizationManager +import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.AuthenticationManager +import com.simiacryptus.skyenet.platform.AuthorizationManager import com.simiacryptus.skyenet.heart.GroovyInterpreter import com.simiacryptus.skyenet.heart.KotlinInterpreter import com.simiacryptus.skyenet.heart.ScalaLocalInterpreter