Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() })
}
}
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) }

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<TaskTypeData>? = withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -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<Void> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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<Void> =
RemoteOperationResult(RemoteOperationResult.ResultCode.OK)
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private fun TaskViewPreview() {
" cryptocurrency market"
),
1707692337,
1707692337,
1.0,
1707692337,
1707692337,
1707692337
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private fun TaskDetailScreenPreview() {
TaskInput("Give me text".getRandomString(100)),
TaskOutput("output".getRandomString(300)),
1707692337,
1707692337,
1.0,
1707692337,
1707692337,
1707692337
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
32 changes: 32 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22941,6 +22941,14 @@
<sha256 value="9c2fa4cf6b957dd26184d88ba952fa6ae1aacdc3b959fd36c7773edd7041eda4" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="65e1a52127">
<artifact name="android-library-65e1a52127.aar">
<sha256 value="e4aecec008f7539a4e08247083f7c6d2b1e75b66b533b81b41600c9b6a3a918e" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-65e1a52127.module">
<sha256 value="2ceba713cf1d7a146b245f33125e88f4ea1e8e92c540d714e8512bab36bf37e1" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="67f7bf5eb832b5fa24f06d9b84db0e21d4c8638c">
<artifact name="android-library-67f7bf5eb832b5fa24f06d9b84db0e21d4c8638c.aar">
<sha256 value="c253a126ca3c32dd5b373ab1c64668e4603305b3113b052fc0fc5e3c833a913c" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down Expand Up @@ -23245,6 +23253,14 @@
<sha256 value="d933646acd56cf64d0911a5e90d0abcdb34b6254cf3b0a6c815ac4c306d34a7f" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="9af75eff8bd6993c92fa713feb89eaeca159c8e6">
<artifact name="android-library-9af75eff8bd6993c92fa713feb89eaeca159c8e6.aar">
<sha256 value="64de5999b8dd773dafa3d17cbd10124c6c1ea17fb8188633105da7ee30b01937" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-9af75eff8bd6993c92fa713feb89eaeca159c8e6.module">
<sha256 value="81f1ca5afe1728221202342890c2d0ee2cb7399a400713e7ff55768d203afa13" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="9b7c9434bf9002a63d5ac911b89b4abf014fabfd">
<artifact name="android-library-9b7c9434bf9002a63d5ac911b89b4abf014fabfd.aar">
<sha256 value="395f5ffec8a46cfca3158c1fee49ed6f643fe7eae759185b0e134765066772e1" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down Expand Up @@ -23381,6 +23397,14 @@
<sha256 value="3718ac6e9dbd7b2ae476f5760b14b8210789153cb31764b149643ad438a717d0" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="b62039db6e">
<artifact name="android-library-b62039db6e.aar">
<sha256 value="570a5b650c9e02f81fe8d7dd11005deb392217605be01edb1b389b5aa9f44368" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-b62039db6e.module">
<sha256 value="f719e1cab974551864ae2945d5e54341bd8c8ea4f30ce0d856899f2f68226ad0" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="b711becb0fe4fa3b09a0ea83a498579310cc8e69">
<artifact name="android-library-b711becb0fe4fa3b09a0ea83a498579310cc8e69.aar">
<sha256 value="8f8f2cbf7caae9355ce01e1e4ae8fc9aa569845a27eba0d2817ae8c4e8062967" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down Expand Up @@ -23962,6 +23986,14 @@
<sha256 value="dbdcb6e9791d35a573c3db8d126cf611d64116b534638898c5f27cdf3881daf2" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="rc-2.27.0-02">
<artifact name="android-library-rc-2.27.0-02.aar">
<sha256 value="64de5999b8dd773dafa3d17cbd10124c6c1ea17fb8188633105da7ee30b01937" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-rc-2.27.0-02.module">
<sha256 value="ea6139b67f3aab5caef0fcbff1442bd7ef6039b0d5b723ea61fb4f9a5291f2ca" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud-deps" name="qrcodescanner" version="0.1.2.4">
<artifact name="qrcodescanner-0.1.2.4.aar">
<sha256 value="b286128792cc04f59b0defa2c937c86d9e2fc824a8011b9af9eea7fd0ea84303" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down
Loading