ChatlogRoutes.kt
src/jvmMain/kotlin/afield/ai/conversation/chatlog/ChatlogRoutes.kt · 56 lines
package afield.ai.conversation.chatlog
import afield.app.server.docsAccessPolicy
import afield.app.server.docsPassPolicy
import afield.app.server.refuseUnlessSameSiteDocsClient
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respond
import io.ktor.server.response.respondFile
import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import java.io.File
The lens viewer's two read routes (ChatLensViewer). Both answer only for a document that registered a chat
archive, and only to this machine and this site: chats can hold private work, and the archive may be another
workspace than the server's. Pictures are served only by blob name (<sha16>.<ext>) from the chat's own
ai4_blobs/.
private val blobName = Regex("[0-9a-f]{16}\\.(jpeg|jpg|png|webp|gif)")
fun Route.installChatlogRoutes()
Mounts /api/chatlog/lens, which answers ChatLensViewer.payload as JSON, and /api/chatlog/blob,
which serves one picture from a chat's own blob directory. Called once while the server builds its
routing.
fun Route.installChatlogRoutes() {
get("/api/chatlog/lens") {
if (call.refuseUnlessSameSiteDocsClient(docsPassPolicy(docsAccessPolicy))) return@get
val p = call.request.queryParameters
val document = p["document"].orEmpty()
val chat = p["chat"].orEmpty()
val result = runCatching { ChatLensViewer.payload(document, chat, requireNotNull(p["lens"]?.ifBlank { null }) { "which lens?" }) }
result.onSuccess { call.respondText(Json.encodeToString(JsonObject.serializer(), it), ContentType.Application.Json) }
.onFailure { call.respondText(it.message ?: "not found", status = HttpStatusCode.NotFound) }
}
get("/api/chatlog/blob") {
if (call.refuseUnlessSameSiteDocsClient(docsPassPolicy(docsAccessPolicy))) return@get
val p = call.request.queryParameters
val ref = p["ref"].orEmpty()
val file = runCatching {
require(blobName.matches(ref))
ChatLensViewer.store(p["document"])!!.blobFile(p["chat"].orEmpty(), ref)
}.getOrNull()
if (file == null) call.respond(HttpStatusCode.NotFound)
else {
call.response.headers.append("X-Content-Type-Options", "nosniff")
call.respondFile(file)
}
}
}