diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt index f6bfabca..5e701a77 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/actors/ActorSystem.kt @@ -2,6 +2,8 @@ package com.simiacryptus.skyenet.actors import com.simiacryptus.skyenet.actors.record.* import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo import com.simiacryptus.skyenet.util.FunctionWrapper import com.simiacryptus.skyenet.util.JsonFunctionRecorder import java.io.File @@ -9,8 +11,8 @@ import java.io.File open class ActorSystem>( private val actors: Map>, private val dataStorage: DataStorage, - val userId: String?, - val sessionId: String + val userId: UserInfo?, + val sessionId: SessionID ) { val sessionDir = dataStorage.getSessionDir(userId, sessionId) fun getActor(actor: T): BaseActor<*> { diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt index 71c729c6..bcc5fc09 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthenticationManager.kt @@ -4,13 +4,6 @@ import java.util.HashMap open class AuthenticationManager { - data class UserInfo( - val id: String, - val email: String, - val name: String, - val picture: String - ) - private val users = HashMap() open fun getUser(sessionId: String?) = if (null == sessionId) null else users[sessionId] diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt index 65b87ac8..e01c4065 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/AuthorizationManager.kt @@ -15,16 +15,16 @@ open class AuthorizationManager { open fun isAuthorized( applicationClass: Class<*>?, - user: String?, + user: UserInfo?, operationType: OperationType, ) = try { - if (isUserAuthorized("/permissions/${operationType.name.lowercase(Locale.getDefault())}.txt", user)) { + if (isUserAuthorized("/permissions/${operationType.name.lowercase(Locale.getDefault())}.txt", user?.email)) { log.debug("User {} authorized for {} globally", user, operationType) true } else if (null != applicationClass) { val packagePath = applicationClass.`package`.name.replace('.', '/') val opName = operationType.name.lowercase(Locale.getDefault()) - if (isUserAuthorized("/$packagePath/$opName.txt", user)) { + if (isUserAuthorized("/$packagePath/$opName.txt", user?.email)) { log.debug("User {} authorized for {} on {}", user, operationType, applicationClass) true } else { diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt index f1d98bea..5374a7c8 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/DataStorage.kt @@ -10,8 +10,8 @@ open class DataStorage( ) { open fun getJson( - userId: String?, - sessionId: String, + userId: UserInfo?, + sessionId: SessionID, clazz: Class, filename: String ): T? { @@ -23,27 +23,27 @@ open class DataStorage( } open fun getMessages( - userId: String?, - sessionId: String + userId: UserInfo?, + sessionId: SessionID ): LinkedHashMap { validateSessionId(sessionId) val messageDir = File(this.getSessionDir(userId, sessionId), MESSAGE_DIR) val messages = LinkedHashMap() - log.debug("Loading messages for $sessionId: ${messageDir.absolutePath}") + log.debug("Loading messages for {}: {}", sessionId, messageDir.absolutePath) messageDir.listFiles()?.sortedBy { it.lastModified() }?.forEach { file -> val message = JsonUtil.objectMapper().readValue(file, String::class.java) messages[file.nameWithoutExtension] = message } - log.debug("Loaded ${messages.size} messages for $sessionId") + log.debug("Loaded {} messages for {}", messages.size, sessionId) return messages } open fun getSessionDir( - userId: String?, - sessionId: String + userId: UserInfo?, + sessionId: SessionID ): File { validateSessionId(sessionId) - val parts = sessionId.split("-") + val parts = sessionId.sessionId.split("-") return when (parts.size) { 3 -> { val root = when { @@ -52,17 +52,17 @@ open class DataStorage( else -> throw IllegalArgumentException("Invalid session ID: $sessionId") } val dateDir = File(root, parts[1]) - log.debug("Date Dir for $sessionId: ${dateDir.absolutePath}") + log.debug("Date Dir for {}: {}", sessionId, dateDir.absolutePath) val sessionDir = File(dateDir, parts[2]) - log.debug("Instance Dir for $sessionId: ${sessionDir.absolutePath}") + log.debug("Instance Dir for {}: {}", sessionId, sessionDir.absolutePath) sessionDir } 2 -> { val dateDir = File(dataDir, parts[0]) - log.debug("Date Dir for $sessionId: ${dateDir.absolutePath}") + log.debug("Date Dir for {}: {}", sessionId, dateDir.absolutePath) val sessionDir = File(dateDir, parts[1]) - log.debug("Instance Dir for $sessionId: ${sessionDir.absolutePath}") + log.debug("Instance Dir for {}: {}", sessionId, sessionDir.absolutePath) sessionDir } @@ -73,8 +73,8 @@ open class DataStorage( } open fun getSessionName( - userId: String?, - sessionId: String + userId: UserInfo?, + sessionId: SessionID ): String { validateSessionId(sessionId) val userMessage = File(this.getSessionDir(userId, sessionId), MESSAGE_DIR).listFiles() @@ -84,12 +84,12 @@ open class DataStorage( val fileText = messageFile.readText() val split = fileText.split("

") if (split.size < 2) { - log.debug("Session $sessionId: No messages") + log.debug("Session {}: No messages", sessionId) "" } else { val stringList = split[1].split("

") if (stringList.isEmpty()) { - log.debug("Session $sessionId: No messages") + log.debug("Session {}: No messages", sessionId) "" } else { stringList.first() @@ -97,25 +97,25 @@ open class DataStorage( } }?.firstOrNull { it.isNotEmpty() } return if (null != userMessage) { - log.debug("Session $sessionId: $userMessage") + log.debug("Session {}: {}", sessionId, userMessage) userMessage } else { - log.debug("Session $sessionId: No messages") - sessionId + log.debug("Session {}: No messages", sessionId) + sessionId.sessionId } } open fun listSessions( - userId: String? - ): List { + userId: UserInfo? + ): List { val globalSessions = listSessions(dataDir) val userSessions = if (userId == null) listOf() else listSessions(userRoot(userId)) - return globalSessions.map { "G-$it" } + userSessions.map { "U-$it" } + return globalSessions.map { SessionID("G-$it") } + userSessions.map { SessionID("U-$it") } } open fun setJson( - userId: String?, - sessionId: String, + userId: UserInfo?, + sessionId: SessionID, settings: T, filename: String ): T { @@ -127,27 +127,19 @@ open class DataStorage( } open fun updateMessage( - userId: String?, - sessionId: String, + userId: UserInfo?, + sessionId: SessionID, messageId: String, value: String ) { validateSessionId(sessionId) val file = File(File(this.getSessionDir(userId, sessionId), MESSAGE_DIR), "$messageId.json") - log.debug("Updating message for $sessionId / $messageId: ${file.absolutePath}") + log.debug("Updating message for {} / {}: {}", sessionId, messageId, file.absolutePath) file.parentFile.mkdirs() JsonUtil.objectMapper().writeValue(file, value) } - open fun validateSessionId( - sessionId: String - ) { - if (!sessionId.matches("""([GU]-)?\d{8}-\w{8}""".toRegex())) { - throw IllegalArgumentException("Invalid session ID: $sessionId") - } - } - - private fun listSessions(dir: File): List { + private fun listSessions(dir: File): List { val files = dir.listFiles()?.flatMap { it.listFiles()?.toList() ?: listOf() }?.filter { sessionDir -> val operationDir = File(sessionDir, MESSAGE_DIR) if (!operationDir.exists()) false else { @@ -156,36 +148,42 @@ open class DataStorage( } } log.debug("Sessions: {}", files?.map { it.parentFile.name + "-" + it.name }) - return files?.map { it.parentFile.name + "-" + it.name } ?: listOf() + return files?.map { SessionID(it.parentFile.name + "-" + it.name) } ?: listOf() } - private fun userRoot(userId: String?) = File( + private fun userRoot(userId: UserInfo?) = File( File(dataDir, "users"), - userId ?: throw IllegalArgumentException("User ID required for private session") + userId?.email ?: throw IllegalArgumentException("User required for private session") ) companion object { private val log = org.slf4j.LoggerFactory.getLogger(DataStorage::class.java) - fun newGlobalID(): String { + fun validateSessionId( + sessionId: SessionID + ) { + if (!sessionId.sessionId.matches("""([GU]-)?\d{8}-\w{8}""".toRegex())) { + throw IllegalArgumentException("Invalid session ID: $sessionId") + } + } + + fun newGlobalID(): SessionID { val uuid = UUID.randomUUID().toString().split("-").first() val yyyyMMdd = java.time.LocalDate.now().toString().replace("-", "") log.debug("New ID: $yyyyMMdd-$uuid") - return "G-$yyyyMMdd-$uuid" + return SessionID("G-$yyyyMMdd-$uuid") } - fun newUserID(): String { + fun newUserID(): SessionID { val uuid = UUID.randomUUID().toString().split("-").first() val yyyyMMdd = java.time.LocalDate.now().toString().replace("-", "") log.debug("New ID: $yyyyMMdd-$uuid") - return "U-$yyyyMMdd-$uuid" + return SessionID("U-$yyyyMMdd-$uuid") } private const val MESSAGE_DIR = "messages" - fun String.stripPrefix(prefix: String) = if (!this.startsWith(prefix)) this else { - this.substring(prefix.length) - } } } + diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/SessionID.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/SessionID.kt new file mode 100644 index 00000000..7c2e9dff --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/SessionID.kt @@ -0,0 +1,11 @@ +package com.simiacryptus.skyenet.platform + +import com.simiacryptus.skyenet.platform.DataStorage.Companion.validateSessionId + +data class SessionID( + val sessionId: String +) { + init { + validateSessionId(this) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt index db28493f..6db57dd6 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UsageManager.kt @@ -14,9 +14,9 @@ open class UsageManager { private val scheduler = Executors.newSingleThreadScheduledExecutor() private val txLogFile = File(".skyenet/usage/log.csv") @Volatile private var txLogFileWriter: FileWriter? - private val usagePerSession = HashMap() - private val sessionsByUser = HashMap>() - private val usersBySession = HashMap>() + private val usagePerSession = HashMap() + private val sessionsByUser = HashMap>() + private val usersBySession = HashMap>() init { txLogFile.parentFile.mkdirs() @@ -40,15 +40,15 @@ open class UsageManager { ?: throw RuntimeException("Unknown model $model") when (direction) { "input" -> incrementUsage( - sessionId, - user, + SessionID(sessionId), + UserInfo(email=user), modelEnum, OpenAIClient.Usage(prompt_tokens = tokens.toInt()) ) "output" -> incrementUsage( - sessionId, - user, + SessionID(sessionId), + UserInfo(email=user), modelEnum, OpenAIClient.Usage(completion_tokens = tokens.toInt()) ) @@ -63,7 +63,7 @@ open class UsageManager { } @Suppress("MemberVisibilityCanBePrivate") - open fun writeCompactLog(file: File) { + protected open fun writeCompactLog(file: File) { val writer = FileWriter(file) usagePerSession.forEach { (sessionId, usage) -> val user = usersBySession[sessionId]?.firstOrNull() @@ -107,7 +107,8 @@ open class UsageManager { File(".skyenet/usage/counters.json").writeText(JsonUtil.toJson(usagePerSession)) } - open fun incrementUsage(sessionId: String, user: String?, model: OpenAIModel, tokens: OpenAIClient.Usage) { + open fun incrementUsage(sessionId: SessionID, user: UserInfo?, model: OpenAIModel, tokens: OpenAIClient.Usage) { + @Suppress("NAME_SHADOWING") val user = if(null == user) null else UserInfo(email= user.email) // Hack val usage = usagePerSession.getOrPut(sessionId) { UsageCounters() } @@ -135,8 +136,9 @@ open class UsageManager { } } - open fun getUserUsageSummary(user: String): Map = - sessionsByUser[user]?.flatMap { sessionId -> + open fun getUserUsageSummary(user: UserInfo): Map { + @Suppress("NAME_SHADOWING") val user = if(null == user) null else UserInfo(email= user.email) // Hack + return sessionsByUser[user]?.flatMap { sessionId -> val usage = usagePerSession[sessionId] usage?.tokensPerModel?.entries?.map { (model, counter) -> model.model to counter.toUsage() @@ -149,8 +151,9 @@ open class UsageManager { ) } } ?: emptyMap() + } - open fun getSessionUsageSummary(sessionId: String): Map = + open fun getSessionUsageSummary(sessionId: SessionID): Map = usagePerSession[sessionId]?.tokensPerModel?.entries?.map { (model, counter) -> model.model to counter.toUsage() }?.groupBy { it.first }?.mapValues { it.value.map { it.second }.reduce { a, b -> @@ -161,8 +164,8 @@ open class UsageManager { } } ?: emptyMap() data class UsageKey( - val sessionId: String, - val user: String?, + val sessionId: SessionID, + val user: UserInfo?, val model: OpenAIModel, ) diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserInfo.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserInfo.kt new file mode 100644 index 00000000..bb4dda84 --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserInfo.kt @@ -0,0 +1,8 @@ +package com.simiacryptus.skyenet.platform + +data class UserInfo( + val email: String, + val id: String? = null, // TODO: Remove default value + val name: String? = null, // TODO: Remove default value + val picture: String? = null, // TODO: Remove default value +) \ No newline at end of file diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt index 6cec9b29..323d57b4 100644 --- a/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/platform/UserSettingsManager.kt @@ -8,10 +8,10 @@ open class UserSettingsManager { val apiKey: String = "", ) - private val userSettings = HashMap() + private val userSettings = HashMap() private val userConfigDirectory = File(".skyenet/users") - open fun getUserSettings(user: String): UserSettings { + open fun getUserSettings(user: UserInfo): UserSettings { return userSettings.getOrPut(user) { val file = File(userConfigDirectory, "$user.json") if (file.exists()) { @@ -24,7 +24,7 @@ open class UserSettingsManager { } } - open fun updateUserSettings(user: String, settings: UserSettings) { + open fun updateUserSettings(user: UserInfo, settings: UserSettings) { userSettings[user] = settings val file = File(userConfigDirectory, "$user.json") file.parentFile.mkdirs() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt index c006d8a7..92a85f46 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationBase.kt @@ -1,5 +1,6 @@ package com.simiacryptus.skyenet +import com.simiacryptus.skyenet.servlet.AppInfoServlet import com.simiacryptus.skyenet.chat.ChatServer import com.simiacryptus.skyenet.chat.ChatSocket import com.simiacryptus.skyenet.platform.ApplicationServices.authenticationManager @@ -7,10 +8,11 @@ import com.simiacryptus.skyenet.platform.ApplicationServices.authorizationManage import com.simiacryptus.skyenet.platform.ApplicationServices.dataStorageFactory import com.simiacryptus.skyenet.servlet.* 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.platform.AuthorizationManager +import com.simiacryptus.skyenet.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.eclipse.jetty.servlet.FilterHolder @@ -18,7 +20,6 @@ import org.eclipse.jetty.servlet.ServletHolder import org.eclipse.jetty.webapp.WebAppContext import org.slf4j.LoggerFactory import java.io.File -import java.util.function.Consumer abstract class ApplicationBase( final override val applicationName: String, @@ -26,69 +27,42 @@ abstract class ApplicationBase( val temperature: Double = 0.1, ) : ChatServer(resourceBase) { + final override val dataStorage = dataStorageFactory(File(File(".skyenet"), applicationName)) + protected open val appInfo = ServletHolder("appInfo", AppInfoServlet(applicationName)) + protected open val userInfo = ServletHolder("userInfo", UserInfoServlet()) + protected open val usageServlet = ServletHolder("usage", UsageServlet()) + protected open val fileZip = ServletHolder("fileZip", ZipServlet(dataStorage)) + protected open val fileIndex = ServletHolder("fileIndex", FileServlet(dataStorage)) + protected open val sessionSettingsServlet = ServletHolder("settings", SessionSettingsServlet(this)) - inner class ApplicationSession( - sessionId: String, - userId: String?, - ) : SessionBase( - sessionId = sessionId, - dataStorage = dataStorage, - userId = userId, - applicationClass = this@ApplicationBase.javaClass, - ) { - private val threads = mutableMapOf() - private val linkTriggers = mutableMapOf>() - private val txtTriggers = mutableMapOf>() - - override fun onRun(userMessage: String, socket: ChatSocket) { - val operationID = randomID() - val sessionDiv = newSessionDiv(operationID, spinner, true) - threads[operationID] = Thread.currentThread() - processMessage(sessionId, userId = userId, userMessage, this, sessionDiv, socket) - } - - override fun onCmd(id: String, code: String, socket: ChatSocket) { - if (code == "cancel") { - threads[id]?.interrupt() - } else if (code == "link") { - val consumer = linkTriggers[id] - consumer ?: throw IllegalArgumentException("No link handler found") - consumer.accept(Unit) - } else { - throw IllegalArgumentException("Unknown command: $code") - } - } - - val spinner: String get() = """
${ApplicationBase.spinner}
""" -// val playButton: String get() = """""" -// val cancelButton: String get() = """""" -// val regenButton: String get() = """""" - - fun hrefLink(divID: String, linkText : String, classname: String = """href-link""", handler: Consumer): String { - val operationID = SessionBase.randomID() - linkTriggers[operationID] = handler - return """$linkText""" - } - fun textInput(divID: String, handler: Consumer): String { - val operationID = SessionBase.randomID() - txtTriggers[operationID] = handler - //language=HTML - return """
- - -
""".trimIndent() + override fun newSession(userId: UserInfo?, sessionId: SessionID): SessionInterface { + return object : ApplicationSession( + sessionId = sessionId, + userId = userId, + dataStorage = dataStorage, + applicationClass = this@ApplicationBase::class.java, + ) { + override fun processMessage( + sessionId: SessionID, + userId: UserInfo?, + userMessage: String, + session: ApplicationSession, + sessionDiv: SessionDiv, + socket: ChatSocket + ) = this@ApplicationBase.processMessage( + sessionId = sessionId, + userId = userId, + userMessage = userMessage, + session = session, + sessionDiv = sessionDiv, + socket = socket + ) } -// -// fun htmlTools(divID: String) = HtmlTools(this, divID) - } - - override fun newSession(userId: String?, sessionId: String): SessionInterface { - return ApplicationSession(sessionId, userId) } abstract fun processMessage( - sessionId: String, - userId: String?, + sessionId: SessionID, + userId: UserInfo?, userMessage: String, session: ApplicationSession, sessionDiv: SessionDiv, @@ -97,9 +71,9 @@ abstract class ApplicationBase( open val settingsClass: Class<*> get() = Map::class.java - open fun initSettings(sessionId: String): T? = null + open fun initSettings(sessionId: SessionID): T? = null - fun getSettings(sessionId: String, userId: String?): T? { + fun getSettings(sessionId: SessionID, userId: UserInfo?): T? { @Suppress("UNCHECKED_CAST") var settings: T? = dataStorage.getJson(userId, sessionId, settingsClass as Class, "settings.json") if (null == settings) { @@ -111,13 +85,6 @@ abstract class ApplicationBase( return settings } - final override val dataStorage = dataStorageFactory(File(File(".skyenet"), applicationName)) - protected open val appInfo = ServletHolder("appInfo", AppInfoServlet()) - protected open val userInfo = ServletHolder("userInfo", UserInfoServlet()) - protected open val usageServlet = ServletHolder("usage", UsageServlet()) - protected open val fileZip = ServletHolder("fileZip", ZipServlet(dataStorage)) - protected open val fileIndex = ServletHolder("fileIndex", FileServlet(dataStorage)) - protected open val sessionSettingsServlet = ServletHolder("settings", SessionSettingsServlet(this)) protected open fun sessionsServlet(path: String) = ServletHolder("sessionList", SessionListServlet(this.dataStorage, path)) override fun configure(webAppContext: WebAppContext, path: String, baseUrl: String) { @@ -128,7 +95,7 @@ abstract class ApplicationBase( val user = authenticationManager.getUser((request as HttpServletRequest).getCookie()) val canRead = authorizationManager.isAuthorized( applicationClass = this@ApplicationBase.javaClass, - user = user?.email, + user = user, operationType = AuthorizationManager.OperationType.Read ) if (canRead) { @@ -149,7 +116,6 @@ abstract class ApplicationBase( webAppContext.addServlet(sessionSettingsServlet, "/settings") } - companion object { private val log = LoggerFactory.getLogger(ApplicationBase::class.java) val spinner = diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationSession.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationSession.kt new file mode 100644 index 00000000..0d9215a6 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/ApplicationSession.kt @@ -0,0 +1,74 @@ +package com.simiacryptus.skyenet + +import com.simiacryptus.skyenet.chat.ChatSocket +import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo +import com.simiacryptus.skyenet.session.SessionBase +import com.simiacryptus.skyenet.session.SessionDiv +import java.util.function.Consumer + +abstract class ApplicationSession( + sessionId: SessionID, + userId: UserInfo?, + dataStorage: DataStorage?, + applicationClass: Class<*>, +) : SessionBase( + sessionId = sessionId, + dataStorage = dataStorage, + userId = userId, + applicationClass = applicationClass, +) { + private val threads = mutableMapOf() + private val linkTriggers = mutableMapOf>() + private val txtTriggers = mutableMapOf>() + + override fun onRun(userMessage: String, socket: ChatSocket) { + val operationID = randomID() + val sessionDiv = newSessionDiv(operationID, spinner, true) + threads[operationID] = Thread.currentThread() + processMessage(sessionId, userId = userId, userMessage, this, sessionDiv, socket) + } + + override fun onCmd(id: String, code: String, socket: ChatSocket) { + if (code == "cancel") { + threads[id]?.interrupt() + } else if (code == "link") { + val consumer = linkTriggers[id] + consumer ?: throw IllegalArgumentException("No link handler found") + consumer.accept(Unit) + } else { + throw IllegalArgumentException("Unknown command: $code") + } + } + + val spinner: String get() = """
${ApplicationBase.spinner}
""" +// val playButton: String get() = """""" +// val cancelButton: String get() = """""" +// val regenButton: String get() = """""" + + fun hrefLink(linkText: String, classname: String = """href-link""", handler: Consumer): String { + val operationID = randomID() + linkTriggers[operationID] = handler + return """$linkText""" + } + fun textInput(handler: Consumer): String { + val operationID = randomID() + txtTriggers[operationID] = handler + //language=HTML + return """
+ + +
""".trimIndent() + } + + + abstract fun processMessage( + sessionId: SessionID, + userId: UserInfo?, + userMessage: String, + session: ApplicationSession, + sessionDiv: SessionDiv, + socket: ChatSocket + ) +} \ 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 2e514d72..07104374 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatServer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatServer.kt @@ -1,14 +1,10 @@ package com.simiacryptus.skyenet.chat -import com.simiacryptus.skyenet.platform.ApplicationServices -import com.simiacryptus.skyenet.platform.AuthenticationManager -import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.* +import com.simiacryptus.skyenet.platform.ApplicationServices.authenticationManager +import com.simiacryptus.skyenet.platform.AuthenticationManager.Companion.COOKIE_NAME import com.simiacryptus.skyenet.servlet.NewSessionServlet import com.simiacryptus.skyenet.session.SessionInterface -import com.simiacryptus.util.JsonUtil -import jakarta.servlet.http.HttpServlet -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse import org.eclipse.jetty.servlet.DefaultServlet import org.eclipse.jetty.servlet.ServletHolder import org.eclipse.jetty.util.resource.Resource @@ -19,60 +15,48 @@ import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory abstract class ChatServer(val resourceBase: String) { - fun JettyServerUpgradeRequest.getCookie(name: String) = cookies?.find { it.name == name }?.value - abstract val applicationName: String open val dataStorage: DataStorage? = null - val stateCache: MutableMap = mutableMapOf() inner class WebSocketHandler : JettyWebSocketServlet() { + val stateCache: MutableMap = mutableMapOf() override fun configure(factory: JettyWebSocketServletFactory) { factory.setCreator { req, resp -> try { - val sessionId = req.parameterMap["sessionId"]?.firstOrNull() - val authId = req.getCookie(AuthenticationManager.COOKIE_NAME) - return@setCreator if (null == sessionId) { + val authId = req.getCookie(COOKIE_NAME) + return@setCreator if (!req.parameterMap.containsKey("sessionId")) { null } else { - val sessionState: SessionInterface - if (stateCache.containsKey(sessionId)) { - sessionState = stateCache[sessionId]!! - } else { - sessionState = newSession( - ApplicationServices.authenticationManager.getUser( - req.getCookie(AuthenticationManager.COOKIE_NAME) - )?.id, sessionId) - stateCache[sessionId] = sessionState - } - ChatSocket(sessionId, sessionState, dataStorage, ApplicationServices.authenticationManager.getUser(authId)) + val sessionId = SessionID(req.parameterMap["sessionId"]?.first()!!) + val sessionState: SessionInterface = getSession(sessionId, req) + val user = authenticationManager.getUser(authId) + ChatSocket(sessionId, sessionState, dataStorage, user) } } catch (e: Exception) { log.warn("Error configuring websocket", e) } } } - } - inner class AppInfoServlet : HttpServlet() { - override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { - resp.contentType = "text/json" - resp.status = HttpServletResponse.SC_OK - resp.writer.write( - JsonUtil.objectMapper().writeValueAsString( - mapOf( - "applicationName" to applicationName - ) - ) - ) + private fun getSession( + sessionId: SessionID, + req: JettyServerUpgradeRequest + ) = if (stateCache.containsKey(sessionId)) { + stateCache[sessionId]!! + } else { + val user = authenticationManager.getUser(req.getCookie(COOKIE_NAME)) + val sessionState = newSession(user, sessionId) + stateCache[sessionId] = sessionState + sessionState } } - abstract fun newSession(userId: String?, sessionId: String): SessionInterface + abstract fun newSession(userId: UserInfo?, sessionId: SessionID): SessionInterface open val baseResource: Resource? get() = Resource.newResource(javaClass.classLoader.getResource(resourceBase)) - protected val newSessionServlet by lazy { NewSessionServlet() } - protected val webSocketHandler by lazy { WebSocketHandler() } - protected val defaultServlet by lazy { DefaultServlet() } + private val newSessionServlet by lazy { NewSessionServlet() } + private val webSocketHandler by lazy { WebSocketHandler() } + private val defaultServlet by lazy { DefaultServlet() } open fun configure(webAppContext: WebAppContext, path: String = "/", baseUrl: String) { webAppContext.addServlet(ServletHolder(javaClass.simpleName + "/default", defaultServlet), "/") @@ -82,6 +66,7 @@ abstract class ChatServer(val resourceBase: String) { companion object { private val log = org.slf4j.LoggerFactory.getLogger(ChatServer::class.java) + fun JettyServerUpgradeRequest.getCookie(name: String) = cookies?.find { it.name == name }?.value } } 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 a1d4596a..b779514a 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSession.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSession.kt @@ -5,12 +5,13 @@ import com.simiacryptus.openai.OpenAIClientBase.Companion.toContentList import com.simiacryptus.openai.models.ChatModels import com.simiacryptus.openai.models.OpenAITextModel import com.simiacryptus.skyenet.ApplicationBase +import com.simiacryptus.skyenet.platform.SessionID import com.simiacryptus.skyenet.session.SessionBase import com.simiacryptus.skyenet.util.MarkdownUtil open class ChatSession( val parent: ChatServer, - sessionId: String, + sessionId: SessionID, val model: OpenAITextModel = ChatModels.GPT35Turbo, val userInterfacePrompt: String, val initialAssistantPrompt: String = "", 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 c8ca92e2..f714f907 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSocket.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/ChatSocket.kt @@ -2,10 +2,8 @@ package com.simiacryptus.skyenet.chat import com.simiacryptus.openai.models.OpenAIModel import com.simiacryptus.openai.OpenAIClient -import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.* 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.platform.AuthorizationManager.OperationType.GlobalKey import org.eclipse.jetty.websocket.api.Session @@ -13,10 +11,10 @@ import org.eclipse.jetty.websocket.api.WebSocketAdapter import org.slf4j.event.Level class ChatSocket( - private val sessionId: String, + private val sessionId: SessionID, private val sessionState: SessionInterface, private val dataStorage: DataStorage?, - private val user: AuthenticationManager.UserInfo?, + private val user: UserInfo?, ) : WebSocketAdapter() { val api: OpenAIClient @@ -24,16 +22,16 @@ class ChatSocket( val user = user val userApi = userApi if (userApi != null) return userApi - val canUseGlobalKey = authorizationManager.isAuthorized(null, user?.email, GlobalKey) + val canUseGlobalKey = authorizationManager.isAuthorized(null, user, GlobalKey) if (!canUseGlobalKey) throw RuntimeException("No API key") return object : OpenAIClient( logLevel = Level.DEBUG, logStreams = mutableListOf( - dataStorage?.getSessionDir(user?.id, sessionId)?.resolve("openai.log")?.outputStream()?.buffered() + dataStorage?.getSessionDir(user, sessionId)?.resolve("openai.log")?.outputStream()?.buffered() ).filterNotNull().toMutableList() ) { override fun incrementTokens(model: OpenAIModel?, tokens: Usage) { - if(null != model) ApplicationServices.usageManager.incrementUsage(sessionId, user?.id, model, tokens) + if(null != model) ApplicationServices.usageManager.incrementUsage(sessionId, user, model, tokens) super.incrementTokens(model, tokens) } } @@ -42,17 +40,17 @@ class ChatSocket( private val userApi: OpenAIClient? get() { val user = user - val userSettings = if (user == null) null else ApplicationServices.userSettingsManager.getUserSettings(user.id) + val userSettings = if (user == null) null else ApplicationServices.userSettingsManager.getUserSettings(user) return if (userSettings == null) null else { if (userSettings.apiKey.isBlank()) null else object : OpenAIClient( key = userSettings.apiKey, logLevel = Level.DEBUG, logStreams = mutableListOf( - dataStorage?.getSessionDir(user?.id, sessionId)?.resolve("openai.log")?.outputStream()?.buffered() + dataStorage?.getSessionDir(user, sessionId)?.resolve("openai.log")?.outputStream()?.buffered() ).filterNotNull().toMutableList(), ) { override fun incrementTokens(model: OpenAIModel?, tokens: Usage) { - ApplicationServices.usageManager.incrementUsage(sessionId, user?.id, model!!, tokens) + ApplicationServices.usageManager.incrementUsage(sessionId, user, model!!, tokens) super.incrementTokens(model, tokens) } } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt index ca1be55d..4a6070ac 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/chat/CodeChatServer.kt @@ -4,6 +4,9 @@ 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.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo +import com.simiacryptus.skyenet.servlet.AppInfoServlet import com.simiacryptus.skyenet.util.ClasspathResource import org.eclipse.jetty.servlet.ServletHolder import org.eclipse.jetty.util.resource.Resource @@ -20,7 +23,7 @@ class CodeChatServer( ) { override val applicationName: String get() = "Code Chat" - override fun newSession(userId: String?, sessionId: String) = object : ChatSession( + override fun newSession(userId: UserInfo?, sessionId: SessionID) = object : ChatSession( sessionId = sessionId, parent = this@CodeChatServer, model = model, @@ -46,14 +49,14 @@ class CodeChatServer( """.trimMargin(), applicationClass = ApplicationBase::class.java, ) { - override fun canWrite(user: String?): Boolean = true + override fun canWrite(user: UserInfo?): 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") + webAppContext.addServlet(ServletHolder(javaClass.simpleName + "/appInfo", AppInfoServlet(applicationName)), "/appInfo") super.configure(webAppContext, path, baseUrl) } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AppInfoServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AppInfoServlet.kt new file mode 100644 index 00000000..0a6a690f --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AppInfoServlet.kt @@ -0,0 +1,20 @@ +package com.simiacryptus.skyenet.servlet + +import com.simiacryptus.util.JsonUtil +import jakarta.servlet.http.HttpServlet +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse + +class AppInfoServlet(val applicationName:String) : HttpServlet() { + override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { + resp.contentType = "text/json" + resp.status = HttpServletResponse.SC_OK + resp.writer.write( + JsonUtil.objectMapper().writeValueAsString( + mapOf( + "applicationName" to applicationName + ) + ) + ) + } +} \ 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 c6d65167..c9f340f3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AuthenticatedWebsite.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/AuthenticatedWebsite.kt @@ -9,8 +9,8 @@ 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.platform.ApplicationServices -import com.simiacryptus.skyenet.platform.AuthenticationManager import com.simiacryptus.skyenet.platform.AuthenticationManager.Companion.COOKIE_NAME +import com.simiacryptus.skyenet.platform.UserInfo import jakarta.servlet.* import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServlet @@ -37,12 +37,13 @@ open class AuthenticatedWebsite( open fun newUserSession(userInfo: Userinfo, sessionId: String) { log.info("User $userInfo logged in with session $sessionId") - ApplicationServices.authenticationManager.setUser(sessionId, AuthenticationManager.UserInfo( + ApplicationServices.authenticationManager.setUser(sessionId, UserInfo( id = userInfo.id, email = userInfo.email, name = userInfo.name, picture = userInfo.picture - )) + ) + ) } open fun configure(context: WebAppContext, addFilter: Boolean = true): WebAppContext { 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 6574a2a3..8759946d 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/FileServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/FileServlet.kt @@ -4,6 +4,7 @@ import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.SessionID import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -12,9 +13,9 @@ import java.io.File class FileServlet(val dataStorage: DataStorage) : HttpServlet() { override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { val path = req.pathInfo ?: "/" - val pathSegments = Companion.parsePath(path) - val sessionID = pathSegments.first() - val sessionDir = dataStorage.getSessionDir(ApplicationServices.authenticationManager.getUser(req.getCookie())?.id, sessionID) + val pathSegments = parsePath(path) + val sessionID = SessionID(pathSegments.first()) + val sessionDir = dataStorage.getSessionDir(ApplicationServices.authenticationManager.getUser(req.getCookie()), sessionID) val filePath = pathSegments.drop(1).joinToString("/") val file = File(sessionDir, filePath) if (file.isFile) { @@ -98,7 +99,7 @@ class FileServlet(val dataStorage: DataStorage) : HttpServlet() { | | |

Archive

- |ZIP + |ZIP |

Folders

|
|$folders 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 022fec7f..fca7d574 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/NewSessionServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/NewSessionServlet.kt @@ -10,6 +10,6 @@ class NewSessionServlet : HttpServlet() { val sessionId = DataStorage.newGlobalID() resp.contentType = "text/plain" resp.status = HttpServletResponse.SC_OK - resp.writer.write(sessionId) + resp.writer.write(sessionId.sessionId) } } \ No newline at end of file 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 c7c9027f..ca827c7b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionListServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionListServlet.kt @@ -3,6 +3,7 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.SessionID import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -14,7 +15,7 @@ class SessionListServlet( override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { resp.contentType = "text/html" resp.status = HttpServletResponse.SC_OK - val sessions = dataStorage.listSessions(ApplicationServices.authenticationManager.getUser(req.getCookie())?.id) + val sessions = dataStorage.listSessions(ApplicationServices.authenticationManager.getUser(req.getCookie())) resp.writer.write( """ @@ -38,7 +39,7 @@ class SessionListServlet( ${sessions.joinToString("") { session -> """ - ${sessionName(req, session)} + ${sessionName(req, session)} """.trimIndent() }} @@ -49,7 +50,7 @@ class SessionListServlet( ) } - private fun sessionName(req: HttpServletRequest, session: String) = dataStorage.getSessionName( - ApplicationServices.authenticationManager.getUser(req.getCookie())?.id, session + private fun sessionName(req: HttpServletRequest, session: SessionID) = dataStorage.getSessionName( + ApplicationServices.authenticationManager.getUser(req.getCookie()), session ) } \ No newline at end of file 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 6020fb6d..f5a6d313 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionSettingsServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/SessionSettingsServlet.kt @@ -3,6 +3,7 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.SessionID import com.simiacryptus.util.JsonUtil import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest @@ -14,11 +15,11 @@ class SessionSettingsServlet( override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { resp.contentType = "text/html" resp.status = HttpServletResponse.SC_OK - val sessionId = req.getParameter("sessionId") - if (null != sessionId) { + if (req.parameterMap.containsKey("sessionId")) { + val sessionId = SessionID(req.getParameter("sessionId")) val settings = server.getSettings(sessionId, ApplicationServices.authenticationManager.getUser( req.getCookie() - )?.id) + )) val json = if(settings != null) JsonUtil.toJson(settings) else "" //language=HTML resp.writer.write( @@ -30,7 +31,7 @@ class SessionSettingsServlet( | | |
- | + | | | | @@ -48,16 +49,16 @@ class SessionSettingsServlet( override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) { resp.contentType = "text/html" resp.status = HttpServletResponse.SC_OK - val sessionId = req.getParameter("sessionId") - if (null == sessionId) { + if (!req.parameterMap.containsKey("sessionId")) { resp.status = HttpServletResponse.SC_BAD_REQUEST resp.writer.write("Session ID is required") } else { + val sessionId = SessionID(req.getParameter("sessionId")) val settings = JsonUtil.fromJson(req.getParameter("settings"), server.settingsClass) server.dataStorage.setJson( - ApplicationServices.authenticationManager.getUser(req.getCookie())?.id, + ApplicationServices.authenticationManager.getUser(req.getCookie()), sessionId, settings, "settings.json") - resp.sendRedirect("${req.contextPath}/#$sessionId") + resp.sendRedirect("${req.contextPath}/#${sessionId.sessionId}") } } } \ No newline at end of file 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 6304a24c..0e7c4575 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UsageServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UsageServlet.kt @@ -5,6 +5,7 @@ import com.simiacryptus.openai.models.OpenAIModel import com.simiacryptus.openai.models.OpenAITextModel import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.SessionID import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -14,15 +15,15 @@ class UsageServlet : HttpServlet() { resp.contentType = "text/html" resp.status = HttpServletResponse.SC_OK - val sessionId = req.getParameter("sessionId") - if (null != sessionId) { + if (req.parameterMap.containsKey("sessionId")) { + val sessionId = SessionID(req.getParameter("sessionId")) serve(resp, ApplicationServices.usageManager.getSessionUsageSummary(sessionId)) } else { val userinfo = ApplicationServices.authenticationManager.getUser(req.getCookie()) if (null == userinfo) { resp.status = HttpServletResponse.SC_BAD_REQUEST } else { - val usage = ApplicationServices.usageManager.getUserUsageSummary(userinfo.id) + val usage = ApplicationServices.usageManager.getUserUsageSummary(userinfo) serve(resp, usage) } } 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 d99727c1..c18df3f2 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserSettingsServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/UserSettingsServlet.kt @@ -16,7 +16,7 @@ class UserSettingsServlet : HttpServlet() { if (null == userinfo) { resp.status = HttpServletResponse.SC_BAD_REQUEST } else { - val settings = ApplicationServices.userSettingsManager.getUserSettings(userinfo.id) + val settings = ApplicationServices.userSettingsManager.getUserSettings(userinfo) val json = JsonUtil.toJson(settings) //language=HTML resp.writer.write( @@ -45,7 +45,7 @@ class UserSettingsServlet : HttpServlet() { resp.status = HttpServletResponse.SC_BAD_REQUEST } else { val settings = JsonUtil.fromJson(req.getParameter("settings"), UserSettings::class.java) - ApplicationServices.userSettingsManager.updateUserSettings(userinfo.id, settings) + ApplicationServices.userSettingsManager.updateUserSettings(userinfo, settings) resp.sendRedirect("/") } } 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 667f6605..778d5846 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,7 @@ 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.platform.ApplicationServices -import com.simiacryptus.skyenet.platform.AuthenticationManager -import com.simiacryptus.skyenet.platform.AuthorizationManager -import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.* import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -47,7 +44,7 @@ open class WelcomeServlet(private val parent : ApplicationDirectory) : HttpServl } } - protected open fun homepage(user: AuthenticationManager.UserInfo?): String { + protected open fun homepage(user: UserInfo?): String { @Language("HTML") val html = """ @@ -79,19 +76,19 @@ open class WelcomeServlet(private val parent : ApplicationDirectory) : HttpServl parent.childWebApps.joinToString("\n") { app -> val canRun = ApplicationServices.authorizationManager.isAuthorized( applicationClass = app.server.javaClass, - user = user?.email, + user = user, operationType = AuthorizationManager.OperationType.Write ) val canRead = ApplicationServices.authorizationManager.isAuthorized( applicationClass = app.server.javaClass, - user = user?.email, + user = user, operationType = AuthorizationManager.OperationType.Read ) if (!canRead) return@joinToString "" val newGlobalSessionLink = - if (canRun) """New Shared Session""" else "" + if (canRun) """New Shared Session""" else "" val newUserSessionLink = - if (canRun) """New Private Session""" else "" + if (canRun) """New Private Session""" else "" """ 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 ff3016d2..3b10d55f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/ZipServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/servlet/ZipServlet.kt @@ -3,6 +3,7 @@ package com.simiacryptus.skyenet.servlet import com.simiacryptus.skyenet.ApplicationBase.Companion.getCookie import com.simiacryptus.skyenet.platform.ApplicationServices import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.SessionID import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -12,11 +13,11 @@ import java.util.zip.ZipOutputStream class ZipServlet(val dataStorage: DataStorage) : HttpServlet() { override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { - val sessionID = req.getParameter("session") + val sessionID = SessionID(req.getParameter("session")) val path = req.parameterMap.get("path")?.find { it.isNotBlank() } ?: "/" FileServlet.parsePath(path) // Validate path val sessionDir = dataStorage.getSessionDir( - ApplicationServices.authenticationManager.getUser(req.getCookie())?.id, sessionID) + ApplicationServices.authenticationManager.getUser(req.getCookie()), sessionID) val file = File(sessionDir, path) val zipFile = File.createTempFile("skyenet", ".zip") try { 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 390c33b0..76216a51 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionBase.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionBase.kt @@ -1,24 +1,21 @@ package com.simiacryptus.skyenet.session 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.platform.ApplicationServices -import com.simiacryptus.skyenet.platform.AuthorizationManager -import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.* import com.simiacryptus.skyenet.util.MarkdownUtil import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger abstract class SessionBase( - protected val sessionId: String, + protected val sessionId: SessionID, private val dataStorage: DataStorage?, - protected val userId: String? = null, + protected val userId: UserInfo? = null, private val messageStates: LinkedHashMap = dataStorage?.getMessages( userId, sessionId ) ?: LinkedHashMap(), - private val applicationClass: Class, + private val applicationClass: Class<*>, ) : SessionInterface { private val sockets: MutableSet = mutableSetOf() private val messageVersions = HashMap() @@ -59,7 +56,7 @@ abstract class SessionBase( return this@SessionBase.send("""$responseContents$spinner1""") } - override fun sessionID(): String { + override fun sessionID(): SessionID { return this@SessionBase.sessionId } @@ -94,7 +91,7 @@ abstract class SessionBase( } final override fun onWebSocketText(socket: ChatSocket, message: String) { - if (canWrite(socket.user?.email)) pool.submit { + if (canWrite(userId)) pool.submit { log.debug("$sessionId - Received message: $message") try { val opCmdPattern = """![a-z]{3,7},.*""".toRegex() @@ -115,7 +112,7 @@ abstract class SessionBase( } } - open fun canWrite(user: String?) = ApplicationServices.authorizationManager.isAuthorized( + open fun canWrite(user: UserInfo?) = ApplicationServices.authorizationManager.isAuthorized( applicationClass = applicationClass, user = user, operationType = AuthorizationManager.OperationType.Write diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionDiv.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionDiv.kt index cbdf9037..dfe5ada3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionDiv.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/session/SessionDiv.kt @@ -1,7 +1,9 @@ package com.simiacryptus.skyenet.session +import com.simiacryptus.skyenet.platform.SessionID + abstract class SessionDiv { abstract fun append(htmlToAppend: String, showSpinner: Boolean) : Unit - abstract fun sessionID(): String + abstract fun sessionID(): SessionID abstract fun divID(): String } \ No newline at end of file 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 143e646f..8e3f609b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/CodingActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/CodingActorTestApp.kt @@ -1,11 +1,11 @@ package com.simiacryptus.skyenet.test import com.simiacryptus.skyenet.ApplicationBase +import com.simiacryptus.skyenet.ApplicationSession import com.simiacryptus.skyenet.actors.CodingActor import com.simiacryptus.skyenet.chat.ChatSocket -import com.simiacryptus.skyenet.platform.ApplicationServices +import com.simiacryptus.skyenet.platform.* import com.simiacryptus.skyenet.session.* -import com.simiacryptus.skyenet.platform.AuthorizationManager import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import org.slf4j.LoggerFactory import java.util.* @@ -19,8 +19,8 @@ open class CodingActorTestApp( temperature = temperature, ) { override fun processMessage( - sessionId: String, - userId: String?, + sessionId: SessionID, + userId: UserInfo?, userMessage: String, session: ApplicationSession, sessionDiv: SessionDiv, @@ -30,11 +30,11 @@ open class CodingActorTestApp( val response = actor.answer(userMessage, api = socket.api) val canPlay = ApplicationServices.authorizationManager.isAuthorized( this::class.java, - socket.user?.email, + userId, AuthorizationManager.OperationType.Execute ) val playLink = if(!canPlay) "" else { - session.hrefLink(sessionDiv.divID(), "▶", "href-link play-button") { + session.hrefLink("▶", "href-link play-button") { sessionDiv.append("""
Running...
""", true) val result = response.run() sessionDiv.append( 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 f05ea754..efc1f8d8 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/ParsedActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/ParsedActorTestApp.kt @@ -1,8 +1,11 @@ package com.simiacryptus.skyenet.test import com.simiacryptus.skyenet.ApplicationBase +import com.simiacryptus.skyenet.ApplicationSession import com.simiacryptus.skyenet.actors.ParsedActor import com.simiacryptus.skyenet.chat.ChatSocket +import com.simiacryptus.skyenet.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo import com.simiacryptus.skyenet.session.SessionDiv import com.simiacryptus.skyenet.util.MarkdownUtil.renderMarkdown import com.simiacryptus.util.JsonUtil @@ -17,8 +20,8 @@ open class ParsedActorTestApp( temperature = temperature, ) { override fun processMessage( - sessionId: String, - userId: String?, + sessionId: SessionID, + userId: UserInfo?, userMessage: String, session: ApplicationSession, sessionDiv: SessionDiv, @@ -32,7 +35,7 @@ open class ParsedActorTestApp( """ |${response.getText()} |``` - |${JsonUtil.toJson(response.getObj()!!)} + |${JsonUtil.toJson(response.getObj())} |``` """.trimMargin().trim() ) 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 45c14156..8e03300b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/test/SimpleActorTestApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/test/SimpleActorTestApp.kt @@ -1,8 +1,11 @@ package com.simiacryptus.skyenet.test import com.simiacryptus.skyenet.ApplicationBase +import com.simiacryptus.skyenet.ApplicationSession import com.simiacryptus.skyenet.actors.SimpleActor import com.simiacryptus.skyenet.chat.ChatSocket +import com.simiacryptus.skyenet.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo import com.simiacryptus.skyenet.session.* import com.simiacryptus.skyenet.util.MarkdownUtil import org.slf4j.LoggerFactory @@ -20,11 +23,11 @@ open class SimpleActorTestApp( val actor: SimpleActor? = null, ) override val settingsClass: Class<*> get() = Settings::class.java - @Suppress("UNCHECKED_CAST") override fun initSettings(sessionId: String): T? = Settings(actor=actor) as T + @Suppress("UNCHECKED_CAST") override fun initSettings(sessionId: SessionID): T? = Settings(actor=actor) as T override fun processMessage( - sessionId: String, - userId: String?, + sessionId: SessionID, + userId: UserInfo?, userMessage: String, session: ApplicationSession, sessionDiv: SessionDiv, 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 4f067f07..42b6fd4c 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EmbeddingVisualizer.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/util/EmbeddingVisualizer.kt @@ -3,17 +3,19 @@ package com.simiacryptus.skyenet.util import com.simiacryptus.openai.OpenAIClient import com.simiacryptus.openai.models.EmbeddingModels import com.simiacryptus.skyenet.platform.DataStorage +import com.simiacryptus.skyenet.platform.SessionID +import com.simiacryptus.skyenet.platform.UserInfo import com.simiacryptus.skyenet.session.SessionBase import com.simiacryptus.util.JsonUtil class EmbeddingVisualizer( val api: OpenAIClient, val dataStorage: DataStorage, - val sessionID: String, + val sessionID: SessionID, val appPath: String, val host: String, val session: SessionBase, - val userId: String, + val userId: UserInfo?, ) { private fun toVectorMap(vararg words: String): Map { diff --git a/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt b/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt index 4dc514ca..ae88761b 100644 --- a/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt +++ b/webui/src/test/kotlin/com/simiacryptus/skyenet/ActorTestAppServer.kt @@ -9,6 +9,7 @@ import com.simiacryptus.skyenet.platform.AuthorizationManager import com.simiacryptus.skyenet.heart.GroovyInterpreter import com.simiacryptus.skyenet.heart.KotlinInterpreter import com.simiacryptus.skyenet.heart.ScalaLocalInterpreter +import com.simiacryptus.skyenet.platform.UserInfo import com.simiacryptus.skyenet.test.CodingActorTestApp import com.simiacryptus.skyenet.test.ParsedActorTestApp import com.simiacryptus.skyenet.test.SimpleActorTestApp @@ -36,7 +37,7 @@ object ActorTestAppServer : ApplicationDirectory(port = 8082) { @JvmStatic fun main(args: Array) { - val mockUser = AuthenticationManager.UserInfo( + val mockUser = UserInfo( "1", "user@mock.test", "Test User", @@ -50,7 +51,7 @@ object ActorTestAppServer : ApplicationDirectory(port = 8082) { ApplicationServices.authorizationManager = object : AuthorizationManager() { override fun isAuthorized( applicationClass: Class<*>?, - user: String?, + user: UserInfo?, operationType: OperationType ): Boolean = true }