Handle errors
This commit is contained in:
parent
48f721e756
commit
597fecd908
4 changed files with 44 additions and 26 deletions
|
|
@ -39,6 +39,7 @@ import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||||
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
||||||
|
|
@ -125,7 +126,7 @@ class ChatActivity : ComponentActivity() {
|
||||||
if (uiState.requestInProgress) {
|
if (uiState.requestInProgress) {
|
||||||
chatState.composerValue = ""
|
chatState.composerValue = ""
|
||||||
|
|
||||||
// scroll to the last user message too
|
// scroll to the last user message
|
||||||
threadViewLazyListState.animateScrollToItem(uiState.messages.size - 2)
|
threadViewLazyListState.animateScrollToItem(uiState.messages.size - 2)
|
||||||
} else if (uiState.messages.isNotEmpty()) {
|
} else if (uiState.messages.isNotEmpty()) {
|
||||||
// scroll to the last user message too
|
// scroll to the last user message too
|
||||||
|
|
@ -148,6 +149,14 @@ class ChatActivity : ComponentActivity() {
|
||||||
is ChatActivityUiEvent.Error -> {
|
is ChatActivityUiEvent.Error -> {
|
||||||
Toast.makeText(context, event.error, Toast.LENGTH_SHORT)
|
Toast.makeText(context, event.error, Toast.LENGTH_SHORT)
|
||||||
.show() // TODO better way of showing this. snackbar?
|
.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
@ -51,7 +52,7 @@ class ChatActivityViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
aiApiService.getChatCompletion(ChatCompletionRequest(
|
aiApiService.getChatCompletion(ChatCompletionRequest(
|
||||||
model = "free-model",
|
model = "i-model",
|
||||||
messages = messages,
|
messages = messages,
|
||||||
temperature = 1.0f,
|
temperature = 1.0f,
|
||||||
maxTokens = 128,
|
maxTokens = 128,
|
||||||
|
|
@ -60,7 +61,7 @@ class ChatActivityViewModel @Inject constructor(
|
||||||
)).onEach { event ->
|
)).onEach { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is SseEvent.Open -> {
|
is SseEvent.Open -> {
|
||||||
|
// There is nothing to do here
|
||||||
}
|
}
|
||||||
is SseEvent.Event<ChatCompletionResponseEvent> -> {
|
is SseEvent.Event<ChatCompletionResponseEvent> -> {
|
||||||
event.data.choices?.firstOrNull()?.let { choice ->
|
event.data.choices?.firstOrNull()?.let { choice ->
|
||||||
|
|
@ -79,37 +80,28 @@ class ChatActivityViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SseEvent.Closed -> {
|
is SseEvent.Closed -> {
|
||||||
|
// Closed is not used in case of an error
|
||||||
|
|
||||||
messages.add(ChatMessage(
|
messages.add(ChatMessage(
|
||||||
role = ChatMessage.Role.Assistant,
|
role = ChatMessage.Role.Assistant,
|
||||||
content = responseContent
|
content = responseContent
|
||||||
))
|
))
|
||||||
|
|
||||||
_uiState.update {
|
|
||||||
it.copy(
|
|
||||||
requestInProgress = false,
|
|
||||||
messages = messages.toList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is SseEvent.Failure -> {
|
is SseEvent.Failure -> {
|
||||||
// TODO here
|
// The below should do. More investigation is needed but I believe this should do
|
||||||
println(event.response?.message)
|
|
||||||
|
|
||||||
_uiEvents.send(ChatActivityUiEvent.Error(event.error.toString()))
|
|
||||||
|
|
||||||
// TODO investigate if closed is called here too
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.catch { exception ->
|
}.catch { exception ->
|
||||||
_uiState.update {
|
messages.removeFirst() // Removing the new user message
|
||||||
it.copy(
|
|
||||||
requestInProgress = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiEvents.send(ChatActivityUiEvent.Error(exception.toString()))
|
_uiEvents.send(ChatActivityUiEvent.Error(exception.toString()))
|
||||||
|
}.onCompletion {
|
||||||
// TODO investigate if closed or failure is called
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
requestInProgress = false,
|
||||||
|
messages = messages.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,4 +14,4 @@ data class AiApiExceptionDataWrapper(
|
||||||
class AiApiException(
|
class AiApiException(
|
||||||
val httpCode: Int,
|
val httpCode: Int,
|
||||||
val error: AiApiExceptionData?
|
val error: AiApiExceptionData?
|
||||||
) : Exception("SSE HTTP Error: $httpCode")
|
) : Exception("API problem: ${error?.message} (code $httpCode)")
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package eu.m724.chatapp.api.retrofit.sse
|
package eu.m724.chatapp.api.retrofit.sse
|
||||||
|
|
||||||
import com.google.gson.Gson
|
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.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
|
@ -66,8 +68,23 @@ class SseCallAdapter<T : Any>(
|
||||||
t: Throwable?,
|
t: Throwable?,
|
||||||
response: Response?
|
response: Response?
|
||||||
) {
|
) {
|
||||||
// TODO aiapiexception here
|
val exc = if (response != null) {
|
||||||
val error = t ?: RuntimeException("Unknown SSE error")
|
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))
|
trySend(SseEvent.Failure(error, response))
|
||||||
close(error)
|
close(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue