Handle errors

This commit is contained in:
Minecon724 2025-06-21 18:55:04 +02:00
commit 597fecd908
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
4 changed files with 44 additions and 26 deletions

View file

@ -39,6 +39,7 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
@ -125,7 +126,7 @@ class ChatActivity : ComponentActivity() {
if (uiState.requestInProgress) {
chatState.composerValue = ""
// scroll to the last user message too
// scroll to the last user message
threadViewLazyListState.animateScrollToItem(uiState.messages.size - 2)
} else if (uiState.messages.isNotEmpty()) {
// scroll to the last user message too
@ -148,6 +149,14 @@ class ChatActivity : ComponentActivity() {
is ChatActivityUiEvent.Error -> {
Toast.makeText(context, event.error, Toast.LENGTH_SHORT)
.show() // TODO better way of showing this. snackbar?
// the user might have scrolled so this is good
threadViewLazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
it.key == "composer"
}?.let {
chatState.requestFocus()
softwareKeyboardController?.show() // TODO perhaps it's pointless to focus since we can click on the toolbar? maybe make it configurable
}
}
}
}

View file

@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
@ -51,7 +52,7 @@ class ChatActivityViewModel @Inject constructor(
}
aiApiService.getChatCompletion(ChatCompletionRequest(
model = "free-model",
model = "i-model",
messages = messages,
temperature = 1.0f,
maxTokens = 128,
@ -60,7 +61,7 @@ class ChatActivityViewModel @Inject constructor(
)).onEach { event ->
when (event) {
is SseEvent.Open -> {
// There is nothing to do here
}
is SseEvent.Event<ChatCompletionResponseEvent> -> {
event.data.choices?.firstOrNull()?.let { choice ->
@ -79,37 +80,28 @@ class ChatActivityViewModel @Inject constructor(
}
}
is SseEvent.Closed -> {
// Closed is not used in case of an error
messages.add(ChatMessage(
role = ChatMessage.Role.Assistant,
content = responseContent
))
_uiState.update {
it.copy(
requestInProgress = false,
messages = messages.toList()
)
}
}
is SseEvent.Failure -> {
// TODO here
println(event.response?.message)
_uiEvents.send(ChatActivityUiEvent.Error(event.error.toString()))
// TODO investigate if closed is called here too
// The below should do. More investigation is needed but I believe this should do
}
}
}.catch { exception ->
_uiState.update {
it.copy(
requestInProgress = false
)
}
messages.removeFirst() // Removing the new user message
_uiEvents.send(ChatActivityUiEvent.Error(exception.toString()))
// TODO investigate if closed or failure is called
}.onCompletion {
_uiState.update {
it.copy(
requestInProgress = false,
messages = messages.toList()
)
}
}.launchIn(viewModelScope)
}
}

View file

@ -14,4 +14,4 @@ data class AiApiExceptionDataWrapper(
class AiApiException(
val httpCode: Int,
val error: AiApiExceptionData?
) : Exception("SSE HTTP Error: $httpCode")
) : Exception("API problem: ${error?.message} (code $httpCode)")

View file

@ -1,6 +1,8 @@
package eu.m724.chatapp.api.retrofit.sse
import com.google.gson.Gson
import eu.m724.chatapp.api.data.AiApiException
import eu.m724.chatapp.api.data.AiApiExceptionDataWrapper
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
@ -66,8 +68,23 @@ class SseCallAdapter<T : Any>(
t: Throwable?,
response: Response?
) {
// TODO aiapiexception here
val error = t ?: RuntimeException("Unknown SSE error")
val exc = if (response != null) {
println("sse response: " + response.code)
println("sse response body: " + response.body?.string())
val apiError =
try {
gson.fromJson(response.body!!.string(), AiApiExceptionDataWrapper::class.java)
} catch (_: Exception) {
null
}?.error
AiApiException(response.code, apiError)
} else {
t
}
val error = exc ?: RuntimeException("Unknown SSE error")
trySend(SseEvent.Failure(error, response))
close(error)
}