diff --git a/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt b/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt index 344c524..5cac2b3 100644 --- a/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt +++ b/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt @@ -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 + } } } } diff --git a/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivityViewModel.kt b/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivityViewModel.kt index 5c32674..ed03f11 100644 --- a/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivityViewModel.kt +++ b/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivityViewModel.kt @@ -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 -> { 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) } } \ No newline at end of file diff --git a/app/src/main/java/eu/m724/chatapp/api/data/AiApiException.kt b/app/src/main/java/eu/m724/chatapp/api/data/AiApiException.kt index 90630a5..7b5fb50 100644 --- a/app/src/main/java/eu/m724/chatapp/api/data/AiApiException.kt +++ b/app/src/main/java/eu/m724/chatapp/api/data/AiApiException.kt @@ -14,4 +14,4 @@ data class AiApiExceptionDataWrapper( class AiApiException( val httpCode: Int, val error: AiApiExceptionData? -) : Exception("SSE HTTP Error: $httpCode") \ No newline at end of file +) : Exception("API problem: ${error?.message} (code $httpCode)") \ No newline at end of file diff --git a/app/src/main/java/eu/m724/chatapp/api/retrofit/sse/SseCallAdapter.kt b/app/src/main/java/eu/m724/chatapp/api/retrofit/sse/SseCallAdapter.kt index 0d9267f..9355c22 100644 --- a/app/src/main/java/eu/m724/chatapp/api/retrofit/sse/SseCallAdapter.kt +++ b/app/src/main/java/eu/m724/chatapp/api/retrofit/sse/SseCallAdapter.kt @@ -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: 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) }