From 1b8eecc45a5677b845f00253c9f42cb68406da76 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 5 Nov 2026 15:40:21 +0300 Subject: [PATCH 1/5] fix(assistant): flow Signed-off-by: alperozturk96 # Conflicts: # gradle/libs.versions.toml --- .../client/assistant/AssistantViewModel.kt | 22 +++++++--------- .../client/assistant/chat/ChatContent.kt | 7 ++++- .../client/assistant/chat/ChatViewModel.kt | 26 +++++++++++++------ .../assistant/model/GenerationCheckResult.kt | 16 ++++++++++++ .../local/AssistantLocalRepositoryImpl.kt | 6 +++-- .../remote/AssistantRemoteRepository.kt | 3 ++- .../remote/AssistantRemoteRepositoryImpl.kt | 18 +++++++++++-- .../remote/MockAssistantRemoteRepository.kt | 6 +++-- .../client/assistant/task/TaskView.kt | 2 +- .../taskDetail/TaskDetailBottomSheet.kt | 2 +- gradle/verification-metadata.xml | 8 ++++++ 11 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/assistant/model/GenerationCheckResult.kt 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/verification-metadata.xml b/gradle/verification-metadata.xml index 455c13d9cf89..2e4dec3ae3a3 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22941,6 +22941,14 @@ + + + + + + + + From b7cf61153d8cc70c05a7da2e06ff3e58a1f36678 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Nov 2026 13:10:53 +0100 Subject: [PATCH 2/5] ci(garm): reduce memory and disk usage of the Garm workflow Signed-off-by: alperozturk96 --- gradle/verification-metadata.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 2e4dec3ae3a3..a713e83e89f7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -23389,6 +23389,14 @@ + + + + + + + + From 9612fb6b33046f9283f20ba3113a3e720c1a9fa7 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 11 Nov 2026 13:54:09 +0100 Subject: [PATCH 3/5] wip Signed-off-by: alperozturk96 From 15189e3e6335ae75a00783a195cecc836cb44087 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 12 Nov 2026 07:45:46 +0100 Subject: [PATCH 4/5] update lib Signed-off-by: alperozturk96 [skip ci] --- gradle/verification-metadata.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a713e83e89f7..37711ea011c5 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -23253,6 +23253,14 @@ + + + + + + + + From d00dc35d1d133f7f75d74dd3de5bd4bf836b93c0 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 12 Nov 2026 09:04:47 +0100 Subject: [PATCH 5/5] update lib Signed-off-by: alperozturk96 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8fc71363a4f7..93cf533a98be 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.35.0-rc1" androidGifDrawableVersion = "1.2.32" androidImageCropperVersion = "4.7.0" -androidLibraryVersion ="rc-2.27.0" +androidLibraryVersion ="rc-2.27.0-02" 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 37711ea011c5..acc13912b540 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -23986,6 +23986,14 @@ + + + + + + + +