diff --git a/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt b/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt index ce517dc15305..0a683f5314b7 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt @@ -199,23 +199,21 @@ class AssistantViewModel( } fun fetchTaskList() = viewModelScope.launch(Dispatchers.IO) { - val taskType = _selectedTaskType.value ?: return@launch + val taskTypeId = _selectedTaskType.value?.id ?: return@launch - val cached = localRepository.getCachedTasks(accountName, taskType.name) + val cached = localRepository.getCachedTasks(accountName, taskTypeId) if (cached.isNotEmpty()) { _filteredTaskList.update { cached.sortedByDescending { it.id } } } - taskType.id?.let { typeId -> - remoteRepository.getTaskList(typeId)?.let { result -> - taskList = result - _filteredTaskList.value = result.sortedByDescending { it.id } - localRepository.cacheTasks(result, accountName) - updateSnackbarMessage(null) - } ?: updateSnackbarMessage(R.string.assistant_screen_task_list_error_state_message) - } + remoteRepository.getTaskList(taskTypeId)?.let { result -> + taskList = result + _filteredTaskList.value = result.sortedByDescending { it.id } + localRepository.cacheTasks(result, accountName) + updateSnackbarMessage(null) + } ?: updateSnackbarMessage(R.string.assistant_screen_task_list_error_state_message) } fun deleteTask(id: Long) = viewModelScope.launch(Dispatchers.IO) { @@ -228,10 +226,10 @@ class AssistantViewModel( updateSnackbarMessage(message) - val taskType = _selectedTaskType.value ?: return@launch + val taskTypeId = _selectedTaskType.value?.id ?: return@launch if (result.isSuccess) { removeTaskFromList(id) - localRepository.deleteTask(id, accountName, taskType.name) + localRepository.deleteTask(id, accountName, taskTypeId) } } // endregion diff --git a/app/src/main/java/com/nextcloud/client/assistant/chat/ChatContent.kt b/app/src/main/java/com/nextcloud/client/assistant/chat/ChatContent.kt index 5a779f73d4ce..dbcf186bf55a 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/chat/ChatContent.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/chat/ChatContent.kt @@ -107,7 +107,12 @@ fun ChatContent(chatViewModel: ChatViewModel, modifier: Modifier = Modifier) { modifier = modifier, listState = listState, bottomSlot = { - ChatErrorBanner(errorState.errorType) + Column { + ChatErrorBanner(errorState.errorType) + if (errorState.errorType == ChatErrorType.GenerateResponse) { + RetryButton(onClick = { chatViewModel.retryResponseGeneration() }) + } + } } ) } diff --git a/app/src/main/java/com/nextcloud/client/assistant/chat/ChatViewModel.kt b/app/src/main/java/com/nextcloud/client/assistant/chat/ChatViewModel.kt index 7211b1f590f1..415775df865f 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/chat/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/chat/ChatViewModel.kt @@ -9,6 +9,7 @@ package com.nextcloud.client.assistant.chat import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.nextcloud.client.assistant.model.GenerationCheckResult import com.nextcloud.client.assistant.repository.remote.AssistantRemoteRepository import com.nextcloud.utils.AppConstants.MILLIS_PER_SECOND import com.nextcloud.utils.date.DateFormatPattern @@ -54,19 +55,16 @@ class ChatViewModel(private val remoteRepository: AssistantRemoteRepository) : V fetchAllMessages(sessionId) val session = remoteRepository.checkSession(sessionId.toString()) - currentChatTaskId = session?.taskId?.toString() + currentChatTaskId = session?.messageTaskId?.toString() val lastMessageIsHuman = currentMessages.lastOrNull()?.isHuman() == true - // update session title if exists - session?.sessionTitle?.let { - _sessionTitle.update { - it - } + session?.sessionTitle?.let { title -> + _sessionTitle.update { title } } when { - currentChatTaskId != null && currentChatTaskId != "0" -> startPolling(sessionId) + currentChatTaskId != null -> startPolling(sessionId) lastMessageIsHuman -> _uiState.update { ChatUIState.RetryAvailable(currentMessages) } @@ -112,8 +110,20 @@ class ChatViewModel(private val remoteRepository: AssistantRemoteRepository) : V private suspend fun fetchNewChatMessage(sessionId: Long) { val taskId = currentChatTaskId ?: return - val newMessage = remoteRepository.checkGeneration(taskId, sessionId.toString()) ?: return + when (val result = remoteRepository.checkGeneration(taskId, sessionId.toString())) { + GenerationCheckResult.Pending -> Unit + GenerationCheckResult.Failed -> onGenerationFailed() + is GenerationCheckResult.Completed -> onNewChatMessage(result.message) + } + } + + private fun onGenerationFailed() { + stopPolling() + currentChatTaskId = null + _uiState.update { ChatUIState.Error(currentMessages, ChatErrorType.GenerateResponse) } + } + private fun onNewChatMessage(newMessage: ChatMessage) { val alreadyExists = currentMessages.any { it.id == newMessage.id || (it.timestamp == newMessage.timestamp && it.content == newMessage.content) diff --git a/app/src/main/java/com/nextcloud/client/assistant/model/GenerationCheckResult.kt b/app/src/main/java/com/nextcloud/client/assistant/model/GenerationCheckResult.kt new file mode 100644 index 000000000000..81f79edf41e5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/assistant/model/GenerationCheckResult.kt @@ -0,0 +1,16 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.assistant.model + +import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage + +sealed class GenerationCheckResult { + data class Completed(val message: ChatMessage) : GenerationCheckResult() + data object Pending : GenerationCheckResult() + data object Failed : GenerationCheckResult() +} diff --git a/app/src/main/java/com/nextcloud/client/assistant/repository/local/AssistantLocalRepositoryImpl.kt b/app/src/main/java/com/nextcloud/client/assistant/repository/local/AssistantLocalRepositoryImpl.kt index 70fb3398cee9..78e69fa49c73 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/repository/local/AssistantLocalRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/repository/local/AssistantLocalRepositoryImpl.kt @@ -9,9 +9,11 @@ package com.nextcloud.client.assistant.repository.local import com.nextcloud.client.database.dao.AssistantDao import com.nextcloud.client.database.entity.AssistantEntity +import com.nextcloud.utils.AppConstants.PROGRESS_PERCENTAGE_MAX import com.owncloud.android.lib.resources.assistant.v2.model.Task import com.owncloud.android.lib.resources.assistant.v2.model.TaskInput import com.owncloud.android.lib.resources.assistant.v2.model.TaskOutput +import kotlin.math.roundToInt class AssistantLocalRepositoryImpl(private val assistantDao: AssistantDao) : AssistantLocalRepository { @@ -45,7 +47,7 @@ class AssistantLocalRepositoryImpl(private val assistantDao: AssistantDao) : Ass input = this.input?.input, output = this.output?.output, completionExpectedAt = this.completionExpectedAt, - progress = this.progress, + progress = this.progress?.times(PROGRESS_PERCENTAGE_MAX)?.roundToInt(), lastUpdated = this.lastUpdated, scheduledAt = this.scheduledAt, endedAt = this.endedAt @@ -60,7 +62,7 @@ class AssistantLocalRepositoryImpl(private val assistantDao: AssistantDao) : Ass input = TaskInput(input = this.input), output = TaskOutput(output = this.output), completionExpectedAt = this.completionExpectedAt, - progress = this.progress, + progress = this.progress?.div(PROGRESS_PERCENTAGE_MAX.toDouble()), lastUpdated = this.lastUpdated, scheduledAt = this.scheduledAt, endedAt = this.endedAt diff --git a/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepository.kt b/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepository.kt index 4a578f6b512e..cfe177ac1504 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepository.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepository.kt @@ -7,6 +7,7 @@ package com.nextcloud.client.assistant.repository.remote +import com.nextcloud.client.assistant.model.GenerationCheckResult import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessageRequest @@ -36,7 +37,7 @@ interface AssistantRemoteRepository { suspend fun generateSession(sessionId: String): SessionTask? - suspend fun checkGeneration(taskId: String, sessionId: String): ChatMessage? + suspend fun checkGeneration(taskId: String, sessionId: String): GenerationCheckResult suspend fun translate(input: TranslationRequest, taskType: TaskTypeData): RemoteOperationResult } diff --git a/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepositoryImpl.kt b/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepositoryImpl.kt index 41f2042bda81..a2f3942d3d08 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/repository/remote/AssistantRemoteRepositoryImpl.kt @@ -6,6 +6,7 @@ */ package com.nextcloud.client.assistant.repository.remote +import com.nextcloud.client.assistant.model.GenerationCheckResult import com.nextcloud.common.NextcloudClient import com.nextcloud.utils.AppConstants import com.owncloud.android.lib.common.operations.RemoteOperationResult @@ -38,10 +39,16 @@ import com.owncloud.android.lib.resources.status.NextcloudVersion import com.owncloud.android.lib.resources.status.OCCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.net.HttpURLConnection class AssistantRemoteRepositoryImpl(private val client: NextcloudClient, capability: OCCapability) : AssistantRemoteRepository { + companion object { + private val GENERATION_FAILED_HTTP_CODES = + setOf(HttpURLConnection.HTTP_BAD_REQUEST, HttpURLConnection.HTTP_NOT_FOUND) + } + private val supportsV2 = capability.version.isNewerOrEqual(NextcloudVersion.nextcloud_30) override suspend fun fetchTaskTypes(): List? = withContext(Dispatchers.IO) { @@ -121,10 +128,17 @@ class AssistantRemoteRepositoryImpl(private val client: NextcloudClient, capabil if (result.isSuccess) result.resultData else null } - override suspend fun checkGeneration(taskId: String, sessionId: String): ChatMessage? = + override suspend fun checkGeneration(taskId: String, sessionId: String): GenerationCheckResult = withContext(Dispatchers.IO) { val result = CheckGenerationRemoteOperation(taskId, sessionId).execute(client) - if (result.isSuccess) result.resultData else null + when { + result.isSuccess -> result.resultData?.let { GenerationCheckResult.Completed(it) } + ?: GenerationCheckResult.Pending + + result.httpCode in GENERATION_FAILED_HTTP_CODES -> GenerationCheckResult.Failed + + else -> GenerationCheckResult.Pending + } } override suspend fun translate(input: TranslationRequest, taskType: TaskTypeData): RemoteOperationResult = diff --git a/app/src/main/java/com/nextcloud/client/assistant/repository/remote/MockAssistantRemoteRepository.kt b/app/src/main/java/com/nextcloud/client/assistant/repository/remote/MockAssistantRemoteRepository.kt index 0788cc4f8535..779c74626ec1 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/repository/remote/MockAssistantRemoteRepository.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/repository/remote/MockAssistantRemoteRepository.kt @@ -6,6 +6,7 @@ */ package com.nextcloud.client.assistant.repository.remote +import com.nextcloud.client.assistant.model.GenerationCheckResult import com.nextcloud.utils.extensions.getRandomString import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage @@ -60,7 +61,7 @@ class MockAssistantRemoteRepository(private val giveEmptyTasks: Boolean = false) TaskInput("Give me some long text 1"), TaskOutput("Lorem ipsum".getRandomString(100)), 1707692337, - 1707692337, + 1.0, 1707692337, 1707692337, 1707692337 @@ -76,7 +77,8 @@ class MockAssistantRemoteRepository(private val giveEmptyTasks: Boolean = false) override suspend fun checkSession(sessionId: String): Session? = null override suspend fun generateSession(sessionId: String): SessionTask? = null - override suspend fun checkGeneration(taskId: String, sessionId: String): ChatMessage? = null + override suspend fun checkGeneration(taskId: String, sessionId: String): GenerationCheckResult = + GenerationCheckResult.Pending override suspend fun translate(input: TranslationRequest, taskType: TaskTypeData): RemoteOperationResult = RemoteOperationResult(RemoteOperationResult.ResultCode.OK) } diff --git a/app/src/main/java/com/nextcloud/client/assistant/task/TaskView.kt b/app/src/main/java/com/nextcloud/client/assistant/task/TaskView.kt index 508839ef10c0..a2b8948594c9 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/task/TaskView.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/task/TaskView.kt @@ -149,7 +149,7 @@ private fun TaskViewPreview() { " cryptocurrency market" ), 1707692337, - 1707692337, + 1.0, 1707692337, 1707692337, 1707692337 diff --git a/app/src/main/java/com/nextcloud/client/assistant/taskDetail/TaskDetailBottomSheet.kt b/app/src/main/java/com/nextcloud/client/assistant/taskDetail/TaskDetailBottomSheet.kt index 47dc45c519b0..6a4efa4998a3 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/taskDetail/TaskDetailBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/taskDetail/TaskDetailBottomSheet.kt @@ -175,7 +175,7 @@ private fun TaskDetailScreenPreview() { TaskInput("Give me text".getRandomString(100)), TaskOutput("output".getRandomString(300)), 1707692337, - 1707692337, + 1.0, 1707692337, 1707692337, 1707692337 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5f787af2b88..32a64b5caeba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.34.0" androidGifDrawableVersion = "1.2.32" androidImageCropperVersion = "4.7.0" -androidLibraryVersion ="96d2d296a6ab80004cf73beb277990842011a743" +androidLibraryVersion ="9af75eff8bd6993c92fa713feb89eaeca159c8e6" androidOpensslVersion = "3.5.6.1" androidPluginVersion = "9.5.0" androidsvgVersion = "1.4" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 77f587d33174..c0b04ea69bc9 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22973,6 +22973,14 @@ + + + + + + + + @@ -23285,6 +23293,14 @@ + + + + + + + + @@ -23421,6 +23437,14 @@ + + + + + + + +