diff --git a/README.md b/README.md
index 66091f06..5d95c256 100644
--- a/README.md
+++ b/README.md
@@ -76,18 +76,18 @@ Maven:
com.simiacryptus
skyenet-webui
- 1.0.43
+ 1.0.44
```
Gradle:
```groovy
-implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.0.43'
+implementation group: 'com.simiacryptus', name: 'skyenet', version: '1.0.44'
```
```kotlin
-implementation("com.simiacryptus:skyenet:1.0.43")
+implementation("com.simiacryptus:skyenet:1.0.44")
```
### 🌟 To Use
diff --git a/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java b/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java
index 1dac9ad2..52590b81 100644
--- a/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java
+++ b/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java
@@ -7,7 +7,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
-public class OutputInterceptor {
+public final class OutputInterceptor {
private OutputInterceptor() {
// Prevent instantiation of the utility class
diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt
index f870ddf2..245ecc28 100644
--- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt
+++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/actors/CodingActor.kt
@@ -36,7 +36,7 @@ open class CodingActor(
get() = interpreterClass.java.getConstructor(Map::class.java).newInstance(symbols + runtimeSymbols)
data class CodeRequest(
- val messages: List,
+ val messages: List>,
val codePrefix: String = "",
val autoEvaluate: Boolean = false,
val fixIterations: Int = 4,
@@ -99,8 +99,8 @@ open class CodingActor(
),
) + questions.messages.map {
ChatMessage(
- role = Role.user,
- content = it.toContentList()
+ role = it.second,
+ content = it.first.toContentList()
)
}
if (questions.codePrefix.isNotBlank()) {
@@ -170,8 +170,8 @@ open class CodingActor(
}
log.info("Result: $result")
//language=HTML
- val executionResult = ExecutionResult(result.toString(), OutputInterceptor.getGlobalOutput())
- OutputInterceptor.clearGlobalOutput()
+ val executionResult = ExecutionResult(result.toString(), OutputInterceptor.getThreadOutput())
+ OutputInterceptor.clearThreadOutput()
return executionResult
}
@@ -280,22 +280,22 @@ open class CodingActor(
ChatMessage(
Role.assistant,
"""
- |```${language.lowercase()}
- |${previousCode}
- |```
- |""".trimMargin().trim().toContentList()
+ |```${language.lowercase()}
+ |${previousCode}
+ |```
+ |""".trimMargin().trim().toContentList()
),
ChatMessage(
Role.system,
"""
- |The previous code failed with the following error:
- |
- |```
- |${error.message?.trim() ?: ""}
- |```
- |
- |Correct the code and try again.
- |""".trimMargin().trim().toContentList()
+ |The previous code failed with the following error:
+ |
+ |```
+ |${error.message?.trim() ?: ""}
+ |```
+ |
+ |Correct the code and try again.
+ |""".trimMargin().trim().toContentList()
)
)
)
diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt
index e7745986..b7c414d4 100644
--- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt
+++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/ApplicationServices.kt
@@ -171,6 +171,7 @@ interface UsageInterface {
fun getUserUsageSummary(user: User): Map
fun getSessionUsageSummary(session: Session): Map
+ fun clear()
data class UsageKey(
val session: Session,
diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt
index 60adf12f..b9b3916b 100644
--- a/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt
+++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/platform/file/UsageManager.kt
@@ -12,165 +12,187 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
-open class UsageManager(val root : File = File(".skyenet/usage")) : UsageInterface {
+open class UsageManager(val root: File = File(".skyenet/usage")) : UsageInterface {
- private val scheduler = Executors.newSingleThreadScheduledExecutor()
- private val txLogFile = File(root, "log.csv")
- @Volatile private var txLogFileWriter: FileWriter?
- private val usagePerSession = ConcurrentHashMap()
- private val sessionsByUser = ConcurrentHashMap>()
- private val usersBySession = ConcurrentHashMap>()
+ private val scheduler = Executors.newSingleThreadScheduledExecutor()
+ private val txLogFile = File(root, "log.csv")
+ @Volatile
+ private var txLogFileWriter: FileWriter?
+ private val usagePerSession = ConcurrentHashMap()
+ private val sessionsByUser = ConcurrentHashMap>()
+ private val usersBySession = ConcurrentHashMap>()
- init {
- txLogFile.parentFile.mkdirs()
- loadFromLog(txLogFile)
- txLogFileWriter = FileWriter(txLogFile, true)
- scheduler.scheduleAtFixedRate({ saveCounters() }, 1, 1, TimeUnit.HOURS)
- }
+ init {
+ txLogFile.parentFile.mkdirs()
+ loadFromLog(txLogFile)
+ txLogFileWriter = FileWriter(txLogFile, true)
+ scheduler.scheduleAtFixedRate({ saveCounters() }, 1, 1, TimeUnit.HOURS)
+ }
- @Suppress("MemberVisibilityCanBePrivate")
- private fun loadFromLog(file: File) {
- if (file.exists()) {
- try {
- file.readLines().forEach { line ->
- val (sessionId, user, model, value, direction) = line.split(",")
- val modelEnum = listOf(
- ChatModels.values(),
- CompletionModels.values(),
- EditModels.values(),
- EmbeddingModels.values()
- ).flatMap { it.toList() }.find { model == it.modelName }
- ?: throw RuntimeException("Unknown model $model")
- when (direction) {
- "input" -> incrementUsage(
- Session(sessionId),
- User(email=user),
- modelEnum,
- com.simiacryptus.jopenai.ApiModel.Usage(prompt_tokens = value.toInt())
- )
+ @Suppress("MemberVisibilityCanBePrivate")
+ private fun loadFromLog(file: File) {
+ if (file.exists()) {
+ try {
+ file.readLines().forEach { line ->
+ val (sessionId, user, model, value, direction) = line.split(",")
+ val modelEnum = listOf(
+ ChatModels.values(),
+ CompletionModels.values(),
+ EditModels.values(),
+ EmbeddingModels.values()
+ ).flatMap { it.toList() }.find { model == it.modelName }
+ ?: throw RuntimeException("Unknown model $model")
+ when (direction) {
+ "input" -> incrementUsage(
+ Session(sessionId),
+ User(email = user),
+ modelEnum,
+ com.simiacryptus.jopenai.ApiModel.Usage(prompt_tokens = value.toInt())
+ )
- "output" -> incrementUsage(
- Session(sessionId),
- User(email=user),
- modelEnum,
- com.simiacryptus.jopenai.ApiModel.Usage(completion_tokens = value.toInt())
- )
+ "output" -> incrementUsage(
+ Session(sessionId),
+ User(email = user),
+ modelEnum,
+ com.simiacryptus.jopenai.ApiModel.Usage(completion_tokens = value.toInt())
+ )
- "cost" -> incrementUsage(
- Session(sessionId),
- User(email=user),
- modelEnum,
- com.simiacryptus.jopenai.ApiModel.Usage(cost = value.toDouble())
- )
+ "cost" -> incrementUsage(
+ Session(sessionId),
+ User(email = user),
+ modelEnum,
+ com.simiacryptus.jopenai.ApiModel.Usage(cost = value.toDouble())
+ )
- else -> throw RuntimeException("Unknown direction $direction")
- }
- }
- } catch (e: Exception) {
- log.warn("Error loading log file", e)
- }
+ else -> throw RuntimeException("Unknown direction $direction")
+ }
}
+ } catch (e: Exception) {
+ log.warn("Error loading log file", e)
+ }
}
+ }
- @Suppress("MemberVisibilityCanBePrivate")
- private fun writeCompactLog(file: File) {
- FileWriter(file).use { writer ->
- usagePerSession.forEach { (sessionId, usage) ->
- val user = usersBySession[sessionId]?.firstOrNull()
- usage.tokensPerModel.forEach { (model, counter) ->
- writer.write("$sessionId,${user?.email},${model.model.modelName},${counter.inputTokens.get()},input\n")
- writer.write("$sessionId,${user?.email},${model.model.modelName},${counter.outputTokens.get()},output\n")
- writer.write("$sessionId,${user?.email},${model.model.modelName},${counter.cost.get()},cost\n")
- }
- }
- writer.flush()
+ @Suppress("MemberVisibilityCanBePrivate")
+ private fun writeCompactLog(file: File) {
+ FileWriter(file).use { writer ->
+ usagePerSession.forEach { (sessionId, usage) ->
+ val user = usersBySession[sessionId]?.firstOrNull()
+ usage.tokensPerModel.forEach { (model, counter) ->
+ writer.write("$sessionId,${user?.email},${model.model.modelName},${counter.inputTokens.get()},input\n")
+ writer.write("$sessionId,${user?.email},${model.model.modelName},${counter.outputTokens.get()},output\n")
+ writer.write("$sessionId,${user?.email},${model.model.modelName},${counter.cost.get()},cost\n")
}
+ }
+ writer.flush()
}
+ }
- private fun saveCounters() {
- txLogFileWriter = FileWriter(txLogFile, true)
- val timedFile = File(txLogFile.absolutePath + "." + System.currentTimeMillis())
- writeCompactLog(timedFile)
- val swapFile = File(txLogFile.absolutePath + ".old")
- synchronized(txLogFile) {
- try {
- txLogFileWriter?.close()
- } catch (e: Exception) {
- log.warn("Error closing log file", e)
- }
- try {
- txLogFile.renameTo(swapFile)
- } catch (e: Exception) {
- log.warn("Error renaming log file", e)
- }
- try {
- timedFile.renameTo(txLogFile)
- } catch (e: Exception) {
- log.warn("Error renaming log file", e)
- }
- try {
- swapFile.renameTo(timedFile)
- } catch (e: Exception) {
- log.warn("Error renaming log file", e)
- }
- txLogFileWriter = FileWriter(txLogFile, true)
- }
- File(root,"counters.json").writeText(JsonUtil.toJson(usagePerSession))
+ private fun saveCounters() {
+ txLogFileWriter = FileWriter(txLogFile, true)
+ val timedFile = File(txLogFile.absolutePath + "." + System.currentTimeMillis())
+ writeCompactLog(timedFile)
+ val swapFile = File(txLogFile.absolutePath + ".old")
+ synchronized(txLogFile) {
+ try {
+ txLogFileWriter?.close()
+ } catch (e: Exception) {
+ log.warn("Error closing log file", e)
+ }
+ try {
+ txLogFile.renameTo(swapFile)
+ } catch (e: Exception) {
+ log.warn("Error renaming log file", e)
+ }
+ try {
+ timedFile.renameTo(txLogFile)
+ } catch (e: Exception) {
+ log.warn("Error renaming log file", e)
+ }
+ try {
+ swapFile.renameTo(timedFile)
+ } catch (e: Exception) {
+ log.warn("Error renaming log file", e)
+ }
+ txLogFileWriter = FileWriter(txLogFile, true)
}
+ val text = JsonUtil.toJson(usagePerSession)
+ File(root, "counters.json").writeText(text)
+ val toClean = txLogFile.parentFile.listFiles()
+ ?.filter { it.name.startsWith(txLogFile.name) && it.name != txLogFile.absolutePath }
+ ?.sortedBy { it.lastModified() } // oldest first
+ ?.dropLast(2) // keep 2 newest
+ ?.drop(2) // keep 2 oldest
+ toClean?.forEach { it.delete() }
+ }
- override fun incrementUsage(session: Session, user: User?, model: OpenAIModel, tokens: com.simiacryptus.jopenai.ApiModel.Usage) {
- @Suppress("NAME_SHADOWING") val user = if (null == user) null else User(email = user.email) // Hack
- usagePerSession.computeIfAbsent(session) { UsageCounters() }
- .tokensPerModel.computeIfAbsent(UsageKey(session, user, model)) { UsageValues() }
- .addAndGet(tokens)
- if (user != null) {
- sessionsByUser.computeIfAbsent(user) { HashSet() }.add(session)
- }
- try {
- val txLogFileWriter = txLogFileWriter
- if (null != txLogFileWriter) {
- synchronized(txLogFile) {
- txLogFileWriter.write("$session,${user?.email},${model.modelName},${tokens.prompt_tokens},input\n")
- txLogFileWriter.write("$session,${user?.email},${model.modelName},${tokens.completion_tokens},output\n")
- txLogFileWriter.write("$session,${user?.email},${model.modelName},${tokens.completion_tokens},cost\n")
- txLogFileWriter.flush()
- }
- }
- } catch (e: Exception) {
- log.warn("Error incrementing usage", e)
+ override fun incrementUsage(
+ session: Session,
+ user: User?,
+ model: OpenAIModel,
+ tokens: com.simiacryptus.jopenai.ApiModel.Usage
+ ) {
+ @Suppress("NAME_SHADOWING") val user = if (null == user) null else User(email = user.email) // Hack
+ usagePerSession.computeIfAbsent(session) { UsageCounters() }
+ .tokensPerModel.computeIfAbsent(UsageKey(session, user, model)) { UsageValues() }
+ .addAndGet(tokens)
+ if (user != null) {
+ sessionsByUser.computeIfAbsent(user) { HashSet() }.add(session)
+ }
+ try {
+ val txLogFileWriter = txLogFileWriter
+ if (null != txLogFileWriter) {
+ synchronized(txLogFile) {
+ txLogFileWriter.write("$session,${user?.email},${model.modelName},${tokens.prompt_tokens},input\n")
+ txLogFileWriter.write("$session,${user?.email},${model.modelName},${tokens.completion_tokens},output\n")
+ txLogFileWriter.write("$session,${user?.email},${model.modelName},${tokens.completion_tokens},cost\n")
+ txLogFileWriter.flush()
}
+ }
+ } catch (e: Exception) {
+ log.warn("Error incrementing usage", e)
}
+ }
- override fun getUserUsageSummary(user: User): Map {
- @Suppress("NAME_SHADOWING") val user = if(null == user) null else User(email= user.email) // Hack
- return sessionsByUser[user]?.flatMap { sessionId ->
- val usage = usagePerSession[sessionId]
- usage?.tokensPerModel?.entries?.map { (model, counter) ->
- model.model to counter.toUsage()
- } ?: emptyList()
- }?.groupBy { it.first }?.mapValues {
- it.value.map { it.second }.reduce { a, b ->
- com.simiacryptus.jopenai.ApiModel.Usage(
- prompt_tokens = a.prompt_tokens + b.prompt_tokens,
- completion_tokens = a.completion_tokens + b.completion_tokens,
- cost = (a.cost ?: 0.0) + (b.cost ?: 0.0)
- )
- }
- } ?: emptyMap()
- }
+ override fun getUserUsageSummary(user: User): Map {
+ @Suppress("NAME_SHADOWING") val user = if (null == user) null else User(email = user.email) // Hack
+ return sessionsByUser[user]?.flatMap { sessionId ->
+ val usage = usagePerSession[sessionId]
+ usage?.tokensPerModel?.entries?.map { (model, counter) ->
+ model.model to counter.toUsage()
+ } ?: emptyList()
+ }?.groupBy { it.first }?.mapValues {
+ it.value.map { it.second }.reduce { a, b ->
+ com.simiacryptus.jopenai.ApiModel.Usage(
+ prompt_tokens = a.prompt_tokens + b.prompt_tokens,
+ completion_tokens = a.completion_tokens + b.completion_tokens,
+ cost = (a.cost ?: 0.0) + (b.cost ?: 0.0)
+ )
+ }
+ } ?: emptyMap()
+ }
- override fun getSessionUsageSummary(session: Session): Map =
- usagePerSession[session]?.tokensPerModel?.entries?.map { (model, counter) ->
- model.model to counter.toUsage()
- }?.groupBy { it.first }?.mapValues { it.value.map { it.second }.reduce { a, b ->
- com.simiacryptus.jopenai.ApiModel.Usage(
- prompt_tokens = a.prompt_tokens + b.prompt_tokens,
- completion_tokens = a.completion_tokens + b.completion_tokens,
- cost = (a.cost ?: 0.0) + (b.cost ?: 0.0)
- )
- } } ?: emptyMap()
+ override fun getSessionUsageSummary(session: Session): Map =
+ usagePerSession[session]?.tokensPerModel?.entries?.map { (model, counter) ->
+ model.model to counter.toUsage()
+ }?.groupBy { it.first }?.mapValues {
+ it.value.map { it.second }.reduce { a, b ->
+ com.simiacryptus.jopenai.ApiModel.Usage(
+ prompt_tokens = a.prompt_tokens + b.prompt_tokens,
+ completion_tokens = a.completion_tokens + b.completion_tokens,
+ cost = (a.cost ?: 0.0) + (b.cost ?: 0.0)
+ )
+ }
+ } ?: emptyMap()
- companion object {
- private val log = org.slf4j.LoggerFactory.getLogger(UsageManager::class.java)
- }
+ override fun clear() {
+ usagePerSession.clear()
+ sessionsByUser.clear()
+ usersBySession.clear()
+ saveCounters()
+ }
+
+ companion object {
+ private val log = org.slf4j.LoggerFactory.getLogger(UsageManager::class.java)
+ }
}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 8c337a03..ae7ae0ee 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
# Gradle Releases -> https://github.com/gradle/gradle/releases
libraryGroup = com.simiacryptus.skyenet
-libraryVersion = 1.0.43
+libraryVersion = 1.0.44
gradleVersion = 7.6.1
diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt
index f6740ef4..8e675efd 100644
--- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt
+++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/coding/CodingAgent.kt
@@ -1,6 +1,7 @@
package com.simiacryptus.skyenet.apps.coding
import com.simiacryptus.jopenai.API
+import com.simiacryptus.jopenai.ApiModel
import com.simiacryptus.jopenai.proxy.ValidatedObject
import com.simiacryptus.skyenet.core.Interpreter
import com.simiacryptus.skyenet.core.actors.ActorSystem
@@ -27,8 +28,9 @@ open class CodingAgent(
val interpreter: KClass,
val symbols: Map,
temperature: Double = 0.1,
+ val details: String? = null,
val actorMap: Map = mapOf(
- ActorTypes.CodingActor to CodingActor(interpreter, symbols = symbols, temperature = temperature)
+ ActorTypes.CodingActor to CodingActor(interpreter, symbols = symbols, temperature = temperature, details = details)
),
) : ActorSystem(actorMap, dataStorage, user, session) {
enum class ActorTypes {
@@ -53,7 +55,7 @@ open class CodingAgent(
val message = ui.newTask()
try {
message.echo(renderMarkdown(userMessage))
- val codeRequest = CodingActor.CodeRequest(listOf(userMessage))
+ val codeRequest = CodingActor.CodeRequest(listOf(userMessage to ApiModel.Role.user))
displayCode(message, codeRequest)
} catch (e: Throwable) {
log.warn("Error", e)
@@ -90,15 +92,20 @@ open class CodingAgent(
response: CodeResult
) {
var formHandle: StringBuilder? = null
- var playLink: StringBuilder? = null
- playLink = task.add(if (!canPlay) "" else {
- ui.hrefLink("â–¶", "href-link play-button") {
- formHandle?.clear()
- playLink?.clear()
- val header = task.header("Running...")
- try {
- val result = response.result
- val feedback = """
+ val playHandler: (t: Unit) -> Unit = {
+ formHandle?.clear()
+ val header = task.header("Running...")
+ try {
+ val result = response.result
+ val feedback = when {
+ result.resultValue.isBlank() || result.resultValue.trim().lowercase() == "null" -> """
+ |# Output
+ |```text
+ |${result.resultOutput}
+ |```
+ """.trimMargin()
+
+ else -> """
|# Result
|```
|${result.resultValue}
@@ -108,64 +115,75 @@ open class CodingAgent(
|```text
|${result.resultOutput}
|```
- """.trimMargin()
- header?.clear()
- task.add(renderMarkdown(feedback))
- displayFeedback(task, revise(request, response, feedback), response)
- } catch (e: Throwable) {
- header?.clear()
- val message = when {
- e is ValidatedObject.ValidationError -> renderMarkdown(e.message ?: "")
- e is CodingActor.FailedToImplementException -> renderMarkdown(
- """
- |**Failed to Implement**
- |
- |${e.message}
- |
- |""".trimMargin()
- )
+ """.trimMargin()
+ }
+ header?.clear()
+ task.add(renderMarkdown(feedback))
+ displayFeedback(task, CodingActor.CodeRequest(
+ messages = request.messages +
+ listOf(
+ response.code to ApiModel.Role.assistant,
+ feedback to ApiModel.Role.system,
+ ).filter { it.first.isNotBlank() }
+ ), response)
+ } catch (e: Throwable) {
+ header?.clear()
+ val message = when {
+ e is ValidatedObject.ValidationError -> renderMarkdown(e.message ?: "")
+ e is CodingActor.FailedToImplementException -> renderMarkdown(
+ """
+ |**Failed to Implement**
+ |
+ |${e.message}
+ |
+ |""".trimMargin()
+ )
- else -> renderMarkdown(
- """
- |**Error `${e.javaClass.name}`**
- |
- |```text
- |${e.message}
- |```
- |""".trimMargin()
- )
- }
- task.add(message, true, "div", "error")
- displayCode(task, revise(request, response, message))
+ else -> renderMarkdown(
+ """
+ |**Error `${e.javaClass.name}`**
+ |
+ |```text
+ |${e.message}
+ |```
+ |""".trimMargin()
+ )
}
+ task.add(message, true, "div", "error")
+ displayCode(task, CodingActor.CodeRequest(
+ messages = request.messages +
+ listOf(
+ response.code to ApiModel.Role.assistant,
+ message to ApiModel.Role.system,
+ ).filter { it.first.isNotBlank() }
+ ))
}
- })
- formHandle = task.add(ui.textInput { feedback ->
+ }
+ val feedbackHandler: (t: String) -> Unit = { feedback ->
try {
formHandle?.clear()
- playLink?.clear()
task.echo(renderMarkdown(feedback))
- displayCode(task, revise(request, response, feedback))
+ displayCode(task, CodingActor.CodeRequest(
+ messages = request.messages +
+ listOf(
+ response?.code to ApiModel.Role.assistant,
+ feedback to ApiModel.Role.user,
+ ).filter { it.first?.isNotBlank() == true }.map { it.first!! to it.second }
+ ))
} catch (e: Throwable) {
log.warn("Error", e)
task.error(e)
}
- })
+ }
+ formHandle = task.add(
+ """
+ |${if (canPlay) ui.hrefLink("â–¶", "href-link play-button", playHandler) else ""}
+ |${ui.textInput(feedbackHandler)}
+ """.trimMargin(), className = "reply-message"
+ )
task.complete()
}
- open fun revise(
- request: CodingActor.CodeRequest,
- response: CodeResult?,
- feedback: String
- ) = CodingActor.CodeRequest(
- messages = request.messages +
- listOf(
- response?.code,
- feedback
- ).filterNotNull().filter { it.isNotBlank() }
- )
-
companion object {
private val log = LoggerFactory.getLogger(CodingAgent::class.java)
}
diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt
index b1025c73..6ac0b5da 100644
--- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt
+++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/application/ApplicationServer.kt
@@ -28,10 +28,12 @@ abstract class ApplicationServer(
open val description: String = ""
open val singleInput = true
+ open val stickyInput = false
open val appInfo: Any by lazy {
mapOf(
"applicationName" to applicationName,
- "singleInput" to singleInput
+ "singleInput" to singleInput,
+ "stickyInput" to stickyInput
)
}
diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt
index 13257c70..e06d80f8 100644
--- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt
+++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/chat/ChatSocketManager.kt
@@ -16,8 +16,8 @@ open class ChatSocketManager(
session: Session,
val model: OpenAITextModel = ChatModels.GPT35Turbo,
val userInterfacePrompt: String,
- private val initialAssistantPrompt: String = "",
- val systemPrompt: String,
+ open val initialAssistantPrompt: String = "",
+ open val systemPrompt: String,
val api: OpenAIClient,
val temperature: Double = 0.3,
applicationClass: Class,
diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt
index 48477467..103c3fc5 100644
--- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt
+++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/test/CodingActorTestApp.kt
@@ -1,6 +1,7 @@
package com.simiacryptus.skyenet.webui.test
import com.simiacryptus.jopenai.API
+import com.simiacryptus.jopenai.ApiModel
import com.simiacryptus.skyenet.core.actors.CodingActor
import com.simiacryptus.skyenet.core.platform.ApplicationServices
import com.simiacryptus.skyenet.core.platform.AuthorizationInterface.OperationType
@@ -29,7 +30,7 @@ open class CodingActorTestApp(
val message = ui.newTask()
try {
message.echo(renderMarkdown(userMessage))
- val response = actor.answer(CodingActor.CodeRequest(listOf(userMessage)), api = api)
+ val response = actor.answer(CodingActor.CodeRequest(listOf(userMessage to ApiModel.Role.user)), api = api)
val canPlay = ApplicationServices.authorizationManager.isAuthorized(
this::class.java,
user,
diff --git a/webui/src/main/resources/application/main.css b/webui/src/main/resources/application/main.css
index 559866f1..eaf62e3b 100644
--- a/webui/src/main/resources/application/main.css
+++ b/webui/src/main/resources/application/main.css
@@ -60,7 +60,7 @@ body {
display: flex;
flex-direction: column;
height: 100vh;
- padding-top: 40px;
+ padding-top: 20px;
}
.reply-form {
@@ -69,7 +69,7 @@ body {
left: 0;
right: 0;
top: 0;
- display: flex;
+ display: contents;
margin: 0;
padding: 5px;
flex-shrink: 0;
@@ -282,7 +282,7 @@ pre {
}
}
-.user-message, .response-message {
+.user-message, .response-message .reply-message {
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
@@ -293,9 +293,16 @@ pre {
border: 1px solid #d0eaff;
}
+.reply-message {
+ background-color: #fff;
+ border: 1px solid #eee;
+ display: flex;
+}
+
.response-message {
background-color: #fff;
border: 1px solid #eee;
+ display: block;
}
pre.verbose, pre.response-message {
diff --git a/webui/src/main/resources/application/main.js b/webui/src/main/resources/application/main.js
index 66e0ba1d..2933a0d7 100644
--- a/webui/src/main/resources/application/main.js
+++ b/webui/src/main/resources/application/main.js
@@ -27,6 +27,7 @@ async function fetchData(endpoint, useSession = true) {
let messageVersions = {};
let singleInput = false;
+let stickyInput = false;
function onWebSocketText(event) {
console.log('WebSocket message:', event);
@@ -61,11 +62,23 @@ function onWebSocketText(event) {
console.log("Error: Could not find .main-input");
}
}
+ if(stickyInput) {
+ const mainInput = document.getElementById('main-input');
+ if (mainInput) {
+ // Keep at top of screen
+ mainInput.style.position = 'sticky';
+ mainInput.style.zIndex = '1';
+ mainInput.style.top = '30px';
+ } else {
+ console.log("Error: Could not find .main-input");
+ }
+ }
}
messagesDiv.scrollTop = messagesDiv.scrollHeight;
Prism.highlightAll();
refreshVerbose();
+ refreshReplyForms()
}
function toggleVerbose() {
@@ -87,6 +100,25 @@ function toggleVerbose() {
}
}
+function refreshReplyForms() {
+ document.querySelectorAll('.reply-input').forEach(messageInput => {
+ messageInput.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter' && !event.shiftKey) {
+ event.preventDefault();
+ let form = messageInput.closest('form');
+ if (form) {
+ let textSubmitButton = form.querySelector('.text-submit-button');
+ if (textSubmitButton) {
+ textSubmitButton.click();
+ } else {
+ form.dispatchEvent(new Event('submit', { cancelable: true }));
+ }
+ }
+ }
+ });
+ });
+}
+
function refreshVerbose() {
let verboseToggle = document.getElementById('verbose');
@@ -221,6 +253,9 @@ document.addEventListener('DOMContentLoaded', () => {
if (data.singleInput) {
singleInput = data.singleInput;
}
+ if (data.stickyInput) {
+ stickyInput = data.stickyInput;
+ }
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);