This commit is contained in:
Minecon724 2025-10-24 17:31:33 +02:00
commit 9ccd1cd699
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
4 changed files with 34 additions and 18 deletions

View file

@ -7,6 +7,7 @@ import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.sse.SSE
import io.ktor.client.plugins.sse.SSEBufferPolicy
import io.ktor.client.plugins.sse.deserialize
import io.ktor.client.plugins.sse.sse
import io.ktor.client.request.header
import io.ktor.client.request.setBody
@ -15,14 +16,11 @@ import io.ktor.http.HttpMethod
import io.ktor.http.contentType
import io.ktor.http.headers
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.channels.awaitClose
import io.ktor.sse.TypedServerSentEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
class AiApi(
private val session: String
@ -50,17 +48,16 @@ class AiApi(
method = HttpMethod.Post
contentType(ContentType.Application.Json)
setBody(requestBody)
},
deserialize = {
typeInfo, jsonString ->
val serializer = Json.serializersModule.serializer(typeInfo.kotlinType!!)
Json.decodeFromString(serializer, jsonString)!!
}
) {
block(
incoming.mapNotNull {
try {
val streamGptEvent = Json.decodeFromString<StreamGptEvent.Completion>(it.data!!)
streamGptEvent
} catch (e: Exception) {
// TODO
null
}
incoming.mapNotNull { event: TypedServerSentEvent<String> ->
deserialize<StreamGptEvent.Completion>(event.data)
}
)
}

View file

@ -14,6 +14,7 @@ import com.jakewharton.mosaic.layout.size
import com.jakewharton.mosaic.layout.wrapContentSize
import com.jakewharton.mosaic.layout.wrapContentWidth
import com.jakewharton.mosaic.modifier.Modifier
import com.jakewharton.mosaic.ui.Color
import com.jakewharton.mosaic.ui.Text
import eu.m724.modifier.BorderedBoxWithTabs
import eu.m724.modifier.RunTitle
@ -41,6 +42,11 @@ fun App(
},
subTitle = runs[selectedTabIndex].model ?: ""
) {
Text(
value = runs[selectedTabIndex].reasoningContent.wrap(90),
color = Color(200, 200, 200)
)
Text(
value = runs[selectedTabIndex].content.wrap(90)
)

View file

@ -28,10 +28,11 @@ class ViewModel {
var index = 0
_runs.update {
index = it.size
it + Run(RunState.Queued, "")
it + Run(RunState.Queued, "", "")
}
var response = ""
var reasoningContent = ""
var responseContent = ""
val timeSource = TimeSource.Monotonic
val startMark by lazy {
@ -43,6 +44,7 @@ class ViewModel {
GlobalScope.launch(Dispatchers.IO) {
aiApi.requestCompletion(
requestBody = StreamGptRequestBody(
model = "free-model",
messages = listOf(
ChatMessage(
role = ChatMessage.Companion.Role.User,
@ -56,15 +58,25 @@ class ViewModel {
is StreamGptEvent.Completion -> {
val now = timeSource.markNow()
event.choices[0].delta.reasoning?.let { chunk ->
reasoningContent += chunk
tokens++
}
event.choices[0].delta.content?.let { chunk ->
response += chunk
responseContent += chunk
tokens++
}
_runs.update {
it.toMutableList().apply {
val tps = (tokens.toDouble() / (now - startMark).inWholeSeconds).toInt()
this[index] = Run(RunState.InProgress(tps), response, model = event.model)
this[index] = Run(
state = RunState.InProgress(tps),
reasoningContent = reasoningContent,
content = responseContent,
model = event.model
)
}
}
}
@ -84,6 +96,7 @@ class ViewModel {
data class Run(
val state: RunState,
val reasoningContent: String,
val content: String,
val model: String? = null
)

View file

@ -46,7 +46,7 @@ fun RunTitle(
}
)
if (state is RunState.InProgress) {
if (state is RunState.InProgress && state.tokensPerSecond != Int.MAX_VALUE) {
Text(
value = " ${state.tokensPerSecond}t/s",
color = Color(0, 120, 0)