From: Lanius Trolling Date: Mon, 13 May 2024 23:07:47 +0000 (-0400) Subject: Add admin panel for OpenAI management X-Git-Url: https://gitweb.starshipfights.net/?a=commitdiff_plain;h=df846d1fdee351c47ba6ed6897f8a0a695f939d3;p=factbooks Add admin panel for OpenAI management --- diff --git a/src/jvmMain/kotlin/info/mechyrdia/Factbooks.kt b/src/jvmMain/kotlin/info/mechyrdia/Factbooks.kt index 63f7f44..8d95c01 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/Factbooks.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/Factbooks.kt @@ -229,6 +229,9 @@ fun Application.factbooks() { get() post() post() + get() + post() + post() get() get() get() diff --git a/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt b/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt index 80f640c..2afa2ae 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt @@ -45,6 +45,7 @@ data class RobotGlobals( suspend fun get() = Table.get(RobotGlobalsId) suspend fun set(instance: RobotGlobals) = Table.put(instance) + suspend fun delete() = Table.del(RobotGlobalsId) override suspend fun initialize() = Unit } @@ -228,6 +229,39 @@ class RobotService( logger.info("Vector store update is complete") } + suspend fun reset() { + RobotGlobals.get()?.gcOldThreads()?.copy( + lastFileUpload = null, + fileIdMap = emptyMap(), + vectorStoreId = null, + assistantId = null, + )?.save() + + while (true) { + val assistants = robotClient.listAssistants().data + if (assistants.isEmpty()) break + + assistants.map { it.id }.forEach { + robotClient.deleteAssistant(it) + } + } + + while (true) { + val vectorStores = robotClient.listVectorStores().data + if (vectorStores.isEmpty()) break + + vectorStores.map { it.id }.forEach { + robotClient.deleteVectorStore(it) + } + } + + robotClient.listFiles().data.map { it.id }.forEach { + robotClient.deleteFile(it) + } + + initialize() + } + inner class Conversation(private val nationId: Id) { private var assistantId: RobotAssistantId? = null private var threadId: RobotThreadId? = null diff --git a/src/jvmMain/kotlin/info/mechyrdia/robot/ViewsRobot.kt b/src/jvmMain/kotlin/info/mechyrdia/robot/ViewsRobot.kt index fb063b0..f68b2b5 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/robot/ViewsRobot.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/robot/ViewsRobot.kt @@ -2,6 +2,7 @@ package info.mechyrdia.robot import info.mechyrdia.auth.createCsrfToken import info.mechyrdia.data.currentNation +import info.mechyrdia.lore.adminPage import info.mechyrdia.lore.page import info.mechyrdia.lore.redirectHref import info.mechyrdia.lore.standardNavBar @@ -84,3 +85,32 @@ suspend fun DefaultWebSocketServerSession.robotConversation(csrfToken: String? = conversation.close() } + +fun ApplicationCall.robotManagementPage(): HTML.() -> Unit { + val robotServiceStatus = RobotService.status + + return adminPage("NUKE Management") { + h1 { +"NUKE Management" } + when (robotServiceStatus) { + RobotServiceStatus.NOT_CONFIGURED -> p { +"Unfortunately, the NUKE is not configured on this website." } + RobotServiceStatus.LOADING -> p { +"The NUKE is still in the process of initializing." } + RobotServiceStatus.FAILED -> p { +"Tragically, the NUKE has failed to initialize due to an internal error." } + RobotServiceStatus.READY -> ul { + li { + form(action = href(Root.Admin.NukeManagement.Update()), method = FormMethod.post) { + submitInput { + value = "Manually Trigger File Update" + } + } + } + li { + form(action = href(Root.Admin.NukeManagement.Reset()), method = FormMethod.post) { + submitInput(classes = "evil") { + value = "Reset All Data And Start Over" + } + } + } + } + } + } +} diff --git a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceBodies.kt b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceBodies.kt index dea60c4..44cf57a 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceBodies.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceBodies.kt @@ -43,6 +43,12 @@ class AdminBanUserPayload(override val csrfToken: String? = null) : CsrfProtecte @Serializable class AdminUnbanUserPayload(override val csrfToken: String? = null) : CsrfProtectedResourcePayload +@Serializable +class AdminNukeUpdatePayload(override val csrfToken: String? = null) : CsrfProtectedResourcePayload + +@Serializable +class AdminNukeResetPayload(override val csrfToken: String? = null) : CsrfProtectedResourcePayload + @Serializable class AdminVfsCopyFilePayload(val from: String, override val csrfToken: String? = null) : CsrfProtectedResourcePayload diff --git a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt index b1e33b5..c1c7381 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt @@ -3,7 +3,9 @@ package info.mechyrdia.route import info.mechyrdia.auth.* import info.mechyrdia.data.* import info.mechyrdia.lore.* +import info.mechyrdia.robot.RobotService import info.mechyrdia.robot.robotConversation +import info.mechyrdia.robot.robotManagementPage import info.mechyrdia.robot.robotPage import io.ktor.http.* import io.ktor.http.content.* @@ -287,6 +289,42 @@ class Root(val error: String? = null) : ResourceHandler, ResourceFilter { } } + @Resource("nuke") + class NukeManagement(val admin: Admin = Admin()) : ResourceFilter, ResourceHandler { + override suspend fun PipelineContext.filterCall() { + with(admin) { filterCall() } + } + + override suspend fun PipelineContext.handleCall() { + filterCall() + call.respondHtml(HttpStatusCode.OK, call.robotManagementPage()) + } + + @Resource("update") + class Update(val nukeManagement: NukeManagement = NukeManagement()) : ResourceReceiver { + override suspend fun PipelineContext.handleCall(payload: AdminNukeUpdatePayload) { + with(nukeManagement) { filterCall() } + with(payload) { call.verifyCsrfToken() } + + RobotService.getInstance()?.performMaintenance() + + call.redirectHref(NukeManagement()) + } + } + + @Resource("reset") + class Reset(val nukeManagement: NukeManagement = NukeManagement()) : ResourceReceiver { + override suspend fun PipelineContext.handleCall(payload: AdminNukeResetPayload) { + with(nukeManagement) { filterCall() } + with(payload) { call.verifyCsrfToken() } + + RobotService.getInstance()?.reset() + + call.redirectHref(NukeManagement()) + } + } + } + @Resource("vfs") class Vfs(val admin: Admin = Admin()) : ResourceFilter { override suspend fun PipelineContext.filterCall() {