From 90ac78331b3c6ed0834e4ba51dc02fefc1857278 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 10 Nov 2025 16:28:30 +0100 Subject: [PATCH 01/14] feat: chat api Signed-off-by: alperozturk --- .../assistant/v2/GetTaskTypesRemoteOperationV2.kt | 6 +++++- .../lib/resources/assistant/v2/model/TaskTypes.kt | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt index f7399ff195..f4e91cbbd3 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt @@ -47,6 +47,7 @@ import org.apache.commons.httpclient.HttpStatus */ class GetTaskTypesRemoteOperationV2 : OCSRemoteOperation>() { private val supportedTaskType = "Text" + private val chatTaskName = "Chat" @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult> { @@ -70,7 +71,10 @@ class GetTaskTypesRemoteOperationV2 : OCSRemoteOperation>() { ?.data ?.types ?.map { (key, value) -> value.copy(id = value.id ?: key) } - ?.filter { taskType -> isSingleTextInputOutput(taskType) } + ?.filter { taskType -> + isSingleTextInputOutput(taskType) || taskType.name == chatTaskName + } + ?.sortedByDescending { it.name == chatTaskName } result = RemoteOperationResult(true, getMethod) result.resultData = taskTypeList diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt index 376260fc03..7472d76562 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt @@ -18,7 +18,18 @@ data class TaskTypeData( val description: String?, val inputShape: Map, val outputShape: Map -) +) { + companion object { + private const val CONVERSATION_LIST_ID = "ConversationList" + val conversationList = TaskTypeData( + CONVERSATION_LIST_ID, + "", + "", + mapOf(), + mapOf() + ) + } +} data class Shape( val name: String, From 559651b9facdf310c8092482365b27f40264366b Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 11 Nov 2025 09:17:46 +0100 Subject: [PATCH 02/14] feat: chat add api calls Signed-off-by: alperozturk --- .../chat/CreateConversationRemoteOperation.kt | 65 +++++++++++++++++++ .../chat/CreateMessageRemoteOperation.kt | 59 +++++++++++++++++ .../chat/DeleteConversationRemoteOperation.kt | 47 ++++++++++++++ .../chat/DeleteMessageRemoteOperation.kt | 50 ++++++++++++++ .../GetConversationListRemoteOperation.kt | 51 +++++++++++++++ .../chat/GetMessagesRemoteOperation.kt | 57 ++++++++++++++++ .../assistant/chat/model/ChatMessage.kt | 23 +++++++ .../chat/model/ChatMessageRequest.kt | 27 ++++++++ .../assistant/chat/model/Conversation.kt | 22 +++++++ .../v2/GetTaskTypesRemoteOperationV2.kt | 3 +- .../resources/assistant/v2/model/TaskTypes.kt | 15 +++-- 11 files changed, 410 insertions(+), 9 deletions(-) create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt new file mode 100644 index 0000000000..f731dac64d --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt @@ -0,0 +1,65 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.google.gson.reflect.TypeToken +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.PutMethod +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.ocs.ServerResponse +import com.owncloud.android.lib.resources.OCSRemoteOperation +import com.owncloud.android.lib.resources.assistant.chat.model.Conversation +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody +import org.apache.commons.httpclient.HttpStatus + +class CreateConversationRemoteOperation( + private val title: String?, + private val timestamp: Long +) : OCSRemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val bodyMap = + hashMapOf( + "title" to title, + "timestamp" to timestamp + ) + + val json = gson.toJson(bodyMap) + val requestBody = json.toRequestBody("application/json".toMediaTypeOrNull()) + + val putMethod = PutMethod(client.baseUri.toString() + "$BASE_URL/new_session", true, requestBody) + val status = putMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val response = + getServerResponse( + putMethod, + object : TypeToken>() {} + ) + val result: RemoteOperationResult = RemoteOperationResult(true, putMethod) + result.resultData = response?.ocs?.data + result + } else { + RemoteOperationResult(false, putMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "create conversation: ", e) + RemoteOperationResult(false, putMethod) + } finally { + putMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "CreateConversationRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt new file mode 100644 index 0000000000..3a99a2ef77 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt @@ -0,0 +1,59 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.google.gson.reflect.TypeToken +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.PutMethod +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.ocs.ServerResponse +import com.owncloud.android.lib.resources.OCSRemoteOperation +import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage +import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessageRequest +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody +import org.apache.commons.httpclient.HttpStatus + +class CreateMessageRemoteOperation( + private val messageRequest: ChatMessageRequest +) : OCSRemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val json = gson.toJson(messageRequest.bodyMap) + val requestBody = json.toRequestBody("application/json".toMediaTypeOrNull()) + + val putMethod = PutMethod(client.baseUri.toString() + "$BASE_URL/new_message", true, requestBody) + val status = putMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val response = + getServerResponse( + putMethod, + object : TypeToken>() {} + ) + val result: RemoteOperationResult = RemoteOperationResult(true, putMethod) + result.resultData = response?.ocs?.data + result + } else { + RemoteOperationResult(false, putMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "create message: ", e) + RemoteOperationResult(false, putMethod) + } finally { + putMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "CreateMessageRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt new file mode 100644 index 0000000000..175f3a29f9 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt @@ -0,0 +1,47 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.DeleteMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import org.apache.commons.httpclient.HttpStatus + +class DeleteConversationRemoteOperation( + private val sessionId: String +) : RemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val deleteMethod = + DeleteMethod( + client.baseUri.toString() + "$BASE_URL/delete_session?sessionId=$sessionId", + true + ) + val status = deleteMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + RemoteOperationResult(true, deleteMethod) + } else { + RemoteOperationResult(false, deleteMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "delete session: ", e) + RemoteOperationResult(false, deleteMethod) + } finally { + deleteMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "DeleteConversationRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt new file mode 100644 index 0000000000..a04099635c --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt @@ -0,0 +1,50 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.DeleteMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import org.apache.commons.httpclient.HttpStatus + +class DeleteMessageRemoteOperation( + private val messageId: String, + private val sessionId: String +) : RemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val deleteMethod = + DeleteMethod( + client.baseUri.toString() + + "$BASE_URL/delete_message?messageId=$messageId&sessionId=$sessionId", + true + ) + + val status = deleteMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + RemoteOperationResult(true, deleteMethod) + } else { + RemoteOperationResult(false, deleteMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "delete session: ", e) + RemoteOperationResult(false, deleteMethod) + } finally { + deleteMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "DeleteMessageRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt new file mode 100644 index 0000000000..c0e9bf9d4b --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt @@ -0,0 +1,51 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.google.gson.reflect.TypeToken +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.ocs.ServerResponse +import com.owncloud.android.lib.resources.OCSRemoteOperation +import com.owncloud.android.lib.resources.assistant.chat.model.Conversation +import org.apache.commons.httpclient.HttpStatus + +class GetConversationListRemoteOperation : OCSRemoteOperation>() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult> { + val getMethod = GetMethod(client.baseUri.toString() + "$BASE_URL/sessions", true) + val status = getMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val response = + getServerResponse( + getMethod, + object : TypeToken>>() {} + ) + val result: RemoteOperationResult> = RemoteOperationResult(true, getMethod) + result.resultData = response?.ocs?.data + result + } else { + RemoteOperationResult(false, getMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "get conversation list: ", e) + RemoteOperationResult(false, getMethod) + } finally { + getMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "GetConversationListRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt new file mode 100644 index 0000000000..2468fd459f --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt @@ -0,0 +1,57 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.google.gson.reflect.TypeToken +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.ocs.ServerResponse +import com.owncloud.android.lib.resources.OCSRemoteOperation +import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage +import org.apache.commons.httpclient.HttpStatus + +class GetMessagesRemoteOperation( + private val sessionId: String +) : OCSRemoteOperation>() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult> { + val getMethod = + GetMethod( + client.baseUri.toString() + "$BASE_URL/messages?sessionId=$sessionId", + true + ) + val status = getMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val response = + getServerResponse( + getMethod, + object : TypeToken>>() {} + ) + val result: RemoteOperationResult> = RemoteOperationResult(true, getMethod) + result.resultData = response?.ocs?.data + result + } else { + RemoteOperationResult(false, getMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "get message list: ", e) + RemoteOperationResult(false, getMethod) + } finally { + getMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "GetMessagesRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt new file mode 100644 index 0000000000..002d355b13 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt @@ -0,0 +1,23 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat.model + +import com.google.gson.annotations.SerializedName + +data class ChatMessage( + val id: Long, + @SerializedName("session_id") + val sessionId: Long, + val role: String, + val content: String, + val timestamp: Long, + @SerializedName("ocp_task_id") + val ocpTaskId: Any?, + val sources: String, + val attachments: List +) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt new file mode 100644 index 0000000000..3ad170e5d2 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt @@ -0,0 +1,27 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat.model + +data class ChatMessageRequest( + val sessionId: String, + val role: String, + val content: String, + val timestamp: Long, + val attachments: List? = null, + val firstHumanMessage: Boolean = false +) { + val bodyMap = + hashMapOf( + "sessionId" to sessionId, + "role" to role, + "content" to content, + "timestamp" to timestamp, + "firstHumanMessage" to firstHumanMessage, + "attachments" to attachments + ) +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt new file mode 100644 index 0000000000..425b8a44d1 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt @@ -0,0 +1,22 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.lib.resources.assistant.chat.model + +import com.google.gson.annotations.SerializedName + +data class Conversation( + val id: Long, + @SerializedName("user_id") + val userId: String, + val title: String, + val timestamp: Long, + @SerializedName("agency_conversation_token") + val agencyConversationToken: String, + @SerializedName("agency_pending_actions") + val agencyPendingActions: Any? +) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt index f4e91cbbd3..019412f855 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt @@ -73,8 +73,7 @@ class GetTaskTypesRemoteOperationV2 : OCSRemoteOperation>() { ?.map { (key, value) -> value.copy(id = value.id ?: key) } ?.filter { taskType -> isSingleTextInputOutput(taskType) || taskType.name == chatTaskName - } - ?.sortedByDescending { it.name == chatTaskName } + }?.sortedByDescending { it.name == chatTaskName } result = RemoteOperationResult(true, getMethod) result.resultData = taskTypeList diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt index 7472d76562..18aae16cb7 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt @@ -21,13 +21,14 @@ data class TaskTypeData( ) { companion object { private const val CONVERSATION_LIST_ID = "ConversationList" - val conversationList = TaskTypeData( - CONVERSATION_LIST_ID, - "", - "", - mapOf(), - mapOf() - ) + val conversationList = + TaskTypeData( + CONVERSATION_LIST_ID, + "", + "", + mapOf(), + mapOf() + ) } } From 423fa7699b0eea77dcedaad11e89ead08e6fd39d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 11 Nov 2025 11:33:35 +0100 Subject: [PATCH 03/14] feat: chat add api calls Signed-off-by: alperozturk --- .../chat/CreateConversationRemoteOperation.kt | 21 ++++++-------- .../chat/CreateMessageRemoteOperation.kt | 15 ++++------ .../GetConversationListRemoteOperation.kt | 18 +++++------- .../chat/GetMessagesRemoteOperation.kt | 16 +++++----- .../assistant/chat/model/Conversation.kt | 29 +++++++++++++++++-- .../chat/model/CreateConversation.kt | 12 ++++++++ 6 files changed, 69 insertions(+), 42 deletions(-) create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt index f731dac64d..0070012f1e 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt @@ -10,11 +10,10 @@ package com.owncloud.android.lib.resources.assistant.chat import com.google.gson.reflect.TypeToken import com.nextcloud.common.NextcloudClient import com.nextcloud.operations.PutMethod +import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.lib.ocs.ServerResponse -import com.owncloud.android.lib.resources.OCSRemoteOperation -import com.owncloud.android.lib.resources.assistant.chat.model.Conversation +import com.owncloud.android.lib.resources.assistant.chat.model.CreateConversation import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody import org.apache.commons.httpclient.HttpStatus @@ -22,9 +21,9 @@ import org.apache.commons.httpclient.HttpStatus class CreateConversationRemoteOperation( private val title: String?, private val timestamp: Long -) : OCSRemoteOperation() { +) : RemoteOperation() { @Suppress("TooGenericExceptionCaught") - override fun run(client: NextcloudClient): RemoteOperationResult { + override fun run(client: NextcloudClient): RemoteOperationResult { val bodyMap = hashMapOf( "title" to title, @@ -39,13 +38,11 @@ class CreateConversationRemoteOperation( return try { if (status == HttpStatus.SC_OK) { - val response = - getServerResponse( - putMethod, - object : TypeToken>() {} - ) - val result: RemoteOperationResult = RemoteOperationResult(true, putMethod) - result.resultData = response?.ocs?.data + val responseBody = putMethod.getResponseBodyAsString() + val type = object : TypeToken() {}.type + val response: CreateConversation = gson.fromJson(responseBody, type) + val result: RemoteOperationResult = RemoteOperationResult(true, putMethod) + result.resultData = response result } else { RemoteOperationResult(false, putMethod) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt index 3a99a2ef77..8c160e7b8c 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt @@ -10,10 +10,9 @@ package com.owncloud.android.lib.resources.assistant.chat import com.google.gson.reflect.TypeToken import com.nextcloud.common.NextcloudClient import com.nextcloud.operations.PutMethod +import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.lib.ocs.ServerResponse -import com.owncloud.android.lib.resources.OCSRemoteOperation import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessageRequest import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -22,7 +21,7 @@ import org.apache.commons.httpclient.HttpStatus class CreateMessageRemoteOperation( private val messageRequest: ChatMessageRequest -) : OCSRemoteOperation() { +) : RemoteOperation() { @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult { val json = gson.toJson(messageRequest.bodyMap) @@ -33,13 +32,11 @@ class CreateMessageRemoteOperation( return try { if (status == HttpStatus.SC_OK) { - val response = - getServerResponse( - putMethod, - object : TypeToken>() {} - ) + val responseBody = putMethod.getResponseBodyAsString() + val type = object : TypeToken() {}.type + val response: ChatMessage = gson.fromJson(responseBody, type) val result: RemoteOperationResult = RemoteOperationResult(true, putMethod) - result.resultData = response?.ocs?.data + result.resultData = response result } else { RemoteOperationResult(false, putMethod) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt index c0e9bf9d4b..7525e5f92a 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt @@ -10,14 +10,13 @@ package com.owncloud.android.lib.resources.assistant.chat import com.google.gson.reflect.TypeToken import com.nextcloud.common.NextcloudClient import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.lib.ocs.ServerResponse -import com.owncloud.android.lib.resources.OCSRemoteOperation import com.owncloud.android.lib.resources.assistant.chat.model.Conversation import org.apache.commons.httpclient.HttpStatus -class GetConversationListRemoteOperation : OCSRemoteOperation>() { +class GetConversationListRemoteOperation : RemoteOperation>() { @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult> { val getMethod = GetMethod(client.baseUri.toString() + "$BASE_URL/sessions", true) @@ -25,13 +24,12 @@ class GetConversationListRemoteOperation : OCSRemoteOperation return try { if (status == HttpStatus.SC_OK) { - val response = - getServerResponse( - getMethod, - object : TypeToken>>() {} - ) - val result: RemoteOperationResult> = RemoteOperationResult(true, getMethod) - result.resultData = response?.ocs?.data + val responseBody = getMethod.getResponseBodyAsString() + val type = object : TypeToken>() {}.type + val conversationList: List = gson.fromJson(responseBody, type) + + val result = RemoteOperationResult>(true, getMethod) + result.resultData = conversationList result } else { RemoteOperationResult(false, getMethod) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt index 2468fd459f..fbb6053b51 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt @@ -10,16 +10,16 @@ package com.owncloud.android.lib.resources.assistant.chat import com.google.gson.reflect.TypeToken import com.nextcloud.common.NextcloudClient import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.lib.ocs.ServerResponse -import com.owncloud.android.lib.resources.OCSRemoteOperation import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage import org.apache.commons.httpclient.HttpStatus class GetMessagesRemoteOperation( private val sessionId: String -) : OCSRemoteOperation>() { +) : RemoteOperation>() { + @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult> { val getMethod = @@ -31,13 +31,11 @@ class GetMessagesRemoteOperation( return try { if (status == HttpStatus.SC_OK) { - val response = - getServerResponse( - getMethod, - object : TypeToken>>() {} - ) + val responseBody = getMethod.getResponseBodyAsString() + val type = object : TypeToken>() {}.type + val response: List = gson.fromJson(responseBody, type) val result: RemoteOperationResult> = RemoteOperationResult(true, getMethod) - result.resultData = response?.ocs?.data + result.resultData = response result } else { RemoteOperationResult(false, getMethod) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt index 425b8a44d1..bc45766d1a 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt @@ -8,15 +8,40 @@ package com.owncloud.android.lib.resources.assistant.chat.model import com.google.gson.annotations.SerializedName +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.Locale +import kotlin.time.ExperimentalTime +import kotlin.time.Instant +import kotlin.time.toJavaInstant data class Conversation( val id: Long, @SerializedName("user_id") val userId: String, - val title: String, + val title: String?, val timestamp: Long, @SerializedName("agency_conversation_token") val agencyConversationToken: String, @SerializedName("agency_pending_actions") val agencyPendingActions: Any? -) +) { + companion object { + private const val TITLE_PRESENTATION_TIME_PATTERN = "MMMM dd, yyyy HH:mm" + } + + @OptIn(ExperimentalTime::class) + fun titleRepresentation(): String { + return if (title != null) { + title + } else { + val instant = Instant.fromEpochSeconds(timestamp) + val deviceZone = ZoneId.systemDefault() + + val formatter = DateTimeFormatter.ofPattern(TITLE_PRESENTATION_TIME_PATTERN, Locale.getDefault()) + .withZone(deviceZone) + + return formatter.format(instant.toJavaInstant()) + } + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt new file mode 100644 index 0000000000..598add4733 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt @@ -0,0 +1,12 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.assistant.chat.model + +data class CreateConversation ( + val session: Conversation +) From 1193964a9c47a35babd843ec497b89e00e831296 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 11 Nov 2025 12:37:05 +0100 Subject: [PATCH 04/14] implement chat content Signed-off-by: alperozturk --- .../lib/resources/assistant/chat/model/ChatMessage.kt | 4 +++- .../resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt | 5 ++--- .../android/lib/resources/assistant/v2/model/TaskTypes.kt | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt index 002d355b13..f520af60b7 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt @@ -20,4 +20,6 @@ data class ChatMessage( val ocpTaskId: Any?, val sources: String, val attachments: List -) +) { + val isHuman: Boolean = (role == "human") +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt index 019412f855..53ee5cc1c7 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt @@ -47,7 +47,6 @@ import org.apache.commons.httpclient.HttpStatus */ class GetTaskTypesRemoteOperationV2 : OCSRemoteOperation>() { private val supportedTaskType = "Text" - private val chatTaskName = "Chat" @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult> { @@ -72,8 +71,8 @@ class GetTaskTypesRemoteOperationV2 : OCSRemoteOperation>() { ?.types ?.map { (key, value) -> value.copy(id = value.id ?: key) } ?.filter { taskType -> - isSingleTextInputOutput(taskType) || taskType.name == chatTaskName - }?.sortedByDescending { it.name == chatTaskName } + isSingleTextInputOutput(taskType) || taskType.isChat + }?.sortedByDescending { it.isChat } result = RemoteOperationResult(true, getMethod) result.resultData = taskTypeList diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt index 18aae16cb7..cae1b4ee15 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt @@ -19,6 +19,9 @@ data class TaskTypeData( val inputShape: Map, val outputShape: Map ) { + private val chatTaskName = "Chat" + val isChat: Boolean = (name == chatTaskName) + companion object { private const val CONVERSATION_LIST_ID = "ConversationList" val conversationList = From 8ab2f3df6ba4d187bedf6f23072f88a66a24f27b Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 11 Nov 2025 13:33:56 +0100 Subject: [PATCH 05/14] implement chat content Signed-off-by: alperozturk --- .../assistant/chat/model/ChatMessage.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt index f520af60b7..c15bcd4a21 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt @@ -8,6 +8,12 @@ package com.owncloud.android.lib.resources.assistant.chat.model import com.google.gson.annotations.SerializedName +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.Locale +import kotlin.time.ExperimentalTime +import kotlin.time.Instant +import kotlin.time.toJavaInstant data class ChatMessage( val id: Long, @@ -21,5 +27,22 @@ data class ChatMessage( val sources: String, val attachments: List ) { - val isHuman: Boolean = (role == "human") + companion object { + private const val TIMESTAMP_PRESENTATION_TIME_PATTERN = "HH:mm" + } + + fun isHuman(): Boolean { + return role == "human" + } + + @OptIn(ExperimentalTime::class) + fun timestampRepresentation(): String { + val instant = Instant.fromEpochSeconds(timestamp) + val deviceZone = ZoneId.systemDefault() + + val formatter = DateTimeFormatter.ofPattern(TIMESTAMP_PRESENTATION_TIME_PATTERN, Locale.getDefault()) + .withZone(deviceZone) + + return formatter.format(instant.toJavaInstant()) + } } From 871137d9c0d30b43b49babd7c9327467b8dfc35e Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 12 Nov 2025 10:02:07 +0100 Subject: [PATCH 06/14] fix license Signed-off-by: alperozturk --- .../chat/CreateConversationRemoteOperation.kt | 5 +++-- .../chat/CreateMessageRemoteOperation.kt | 5 +++-- .../chat/DeleteConversationRemoteOperation.kt | 5 +++-- .../chat/DeleteMessageRemoteOperation.kt | 5 +++-- .../chat/GetConversationListRemoteOperation.kt | 5 +++-- .../assistant/chat/GetMessagesRemoteOperation.kt | 6 +++--- .../resources/assistant/chat/model/ChatMessage.kt | 15 ++++++++------- .../assistant/chat/model/ChatMessageRequest.kt | 5 +++-- .../assistant/chat/model/Conversation.kt | 11 +++++++---- .../assistant/chat/model/CreateConversation.kt | 3 ++- 10 files changed, 38 insertions(+), 27 deletions(-) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt index 0070012f1e..51818f16cd 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateConversationRemoteOperation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt index 8c160e7b8c..1bb98be677 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CreateMessageRemoteOperation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt index 175f3a29f9..51cba71477 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteConversationRemoteOperation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt index a04099635c..7dadfb8822 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/DeleteMessageRemoteOperation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt index 7525e5f92a..a2f9db9d4a 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetConversationListRemoteOperation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt index fbb6053b51..ff26c18d36 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GetMessagesRemoteOperation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat @@ -19,7 +20,6 @@ import org.apache.commons.httpclient.HttpStatus class GetMessagesRemoteOperation( private val sessionId: String ) : RemoteOperation>() { - @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult> { val getMethod = diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt index c15bcd4a21..3e8eef8035 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat.model @@ -31,17 +32,17 @@ data class ChatMessage( private const val TIMESTAMP_PRESENTATION_TIME_PATTERN = "HH:mm" } - fun isHuman(): Boolean { - return role == "human" - } + fun isHuman(): Boolean = role == "human" @OptIn(ExperimentalTime::class) fun timestampRepresentation(): String { val instant = Instant.fromEpochSeconds(timestamp) val deviceZone = ZoneId.systemDefault() - val formatter = DateTimeFormatter.ofPattern(TIMESTAMP_PRESENTATION_TIME_PATTERN, Locale.getDefault()) - .withZone(deviceZone) + val formatter = + DateTimeFormatter + .ofPattern(TIMESTAMP_PRESENTATION_TIME_PATTERN, Locale.getDefault()) + .withZone(deviceZone) return formatter.format(instant.toJavaInstant()) } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt index 3ad170e5d2..a6f3792e8f 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessageRequest.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat.model diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt index bc45766d1a..9f350e7e2c 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt @@ -1,8 +1,9 @@ /* - * Nextcloud - Android Client + * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat.model @@ -38,8 +39,10 @@ data class Conversation( val instant = Instant.fromEpochSeconds(timestamp) val deviceZone = ZoneId.systemDefault() - val formatter = DateTimeFormatter.ofPattern(TITLE_PRESENTATION_TIME_PATTERN, Locale.getDefault()) - .withZone(deviceZone) + val formatter = + DateTimeFormatter + .ofPattern(TITLE_PRESENTATION_TIME_PATTERN, Locale.getDefault()) + .withZone(deviceZone) return formatter.format(instant.toJavaInstant()) } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt index 598add4733..a05d6094f1 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/CreateConversation.kt @@ -1,12 +1,13 @@ /* * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.assistant.chat.model -data class CreateConversation ( +data class CreateConversation( val session: Conversation ) From d855d80bd38f61ff8a1b0f9bfbc707fd4293b019 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 12 Nov 2025 12:23:08 +0100 Subject: [PATCH 07/14] add generation api Signed-off-by: alperozturk --- .../chat/CheckGenerationRemoteOperation.kt | 54 +++++++++++++++++++ .../chat/CheckSessionRemoteOperation.kt | 54 +++++++++++++++++++ .../chat/GenerateSessionRemoteOperation.kt | 52 ++++++++++++++++++ .../resources/assistant/chat/model/Session.kt | 21 ++++++++ 4 files changed, 181 insertions(+) create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt create mode 100644 library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt new file mode 100644 index 0000000000..e2d6482449 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt @@ -0,0 +1,54 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.assistant.chat +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage +import org.apache.commons.httpclient.HttpStatus + +class CheckGenerationRemoteOperation( + private val taskId: String, + private val sessionId: String +) : RemoteOperation() { + + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val url = client.baseUri.toString() + + "$BASE_URL/check_generation?taskId=$taskId&sessionId=$sessionId" + + val getMethod = GetMethod(url, true) + val status = getMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val responseBody = getMethod.getResponseBodyAsString() + val jsonResponse = gson.fromJson(responseBody, ChatMessage::class.java) + + val result = RemoteOperationResult(true, getMethod) + result.resultData = jsonResponse + result + } else { + RemoteOperationResult(false, getMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "check generation failed: ", e) + RemoteOperationResult(false, getMethod) + } finally { + getMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "CheckGenerationRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt new file mode 100644 index 0000000000..77d5feff71 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt @@ -0,0 +1,54 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.assistant.chat.model.Session +import org.apache.commons.httpclient.HttpStatus + +class CheckSessionRemoteOperation( + private val sessionId: String +) : RemoteOperation() { + + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val getMethod = GetMethod( + client.baseUri.toString() + "$BASE_URL/check_session?sessionId=$sessionId", + true + ) + val status = getMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val responseBody = getMethod.getResponseBodyAsString() + val jsonResponse = gson.fromJson(responseBody, Session::class.java) + + val result = RemoteOperationResult(true, getMethod) + result.resultData = jsonResponse + result + } else { + RemoteOperationResult(false, getMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "check session failed: ", e) + RemoteOperationResult(false, getMethod) + } finally { + getMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "CheckSessionRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt new file mode 100644 index 0000000000..6bfac67763 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt @@ -0,0 +1,52 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.assistant.chat.model.SessionTask +import org.apache.commons.httpclient.HttpStatus + +class GenerateSessionRemoteOperation( + private val sessionId: String +) : RemoteOperation() { + + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val url = client.baseUri.toString() + "$BASE_URL/generate?sessionId=$sessionId" + val getMethod = GetMethod(url, true) + val status = getMethod.execute(client) + + return try { + if (status == HttpStatus.SC_OK) { + val responseBody = getMethod.getResponseBodyAsString() + val jsonResponse = gson.fromJson(responseBody, SessionTask::class.java) + + val result = RemoteOperationResult(true, getMethod) + result.resultData = jsonResponse + result + } else { + RemoteOperationResult(false, getMethod) + } + } catch (e: Exception) { + Log_OC.e(TAG, "generate request failed: ", e) + RemoteOperationResult(false, getMethod) + } finally { + getMethod.releaseConnection() + } + } + + companion object { + private const val TAG = "GenerateRemoteOperation" + private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt new file mode 100644 index 0000000000..52413eb31b --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt @@ -0,0 +1,21 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.assistant.chat.model + +data class Session( + val messageTaskId: Int?, + val titleTaskId: Int?, + val sessionTitle: String, + val sessionAgencyPendingActions: Any?, + val taskId: Long +) + +data class SessionTask( + val taskId: Long, +) From c71f4675494966d048b794dd07efa4c0209be921 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 12 Nov 2025 13:17:28 +0100 Subject: [PATCH 08/14] fix codacy Signed-off-by: alperozturk --- .../assistant/chat/CheckGenerationRemoteOperation.kt | 6 +++--- .../assistant/chat/CheckSessionRemoteOperation.kt | 12 ++++++------ .../assistant/chat/GenerateSessionRemoteOperation.kt | 1 - .../lib/resources/assistant/chat/model/Session.kt | 2 +- .../assistant/v2/GetTaskTypesRemoteOperationV2.kt | 4 ++-- .../lib/resources/assistant/v2/model/TaskTypes.kt | 3 ++- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt index e2d6482449..138daf9b9e 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckGenerationRemoteOperation.kt @@ -19,11 +19,11 @@ class CheckGenerationRemoteOperation( private val taskId: String, private val sessionId: String ) : RemoteOperation() { - @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult { - val url = client.baseUri.toString() + - "$BASE_URL/check_generation?taskId=$taskId&sessionId=$sessionId" + val url = + client.baseUri.toString() + + "$BASE_URL/check_generation?taskId=$taskId&sessionId=$sessionId" val getMethod = GetMethod(url, true) val status = getMethod.execute(client) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt index 77d5feff71..98cedf3b7c 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/CheckSessionRemoteOperation.kt @@ -19,17 +19,17 @@ import org.apache.commons.httpclient.HttpStatus class CheckSessionRemoteOperation( private val sessionId: String ) : RemoteOperation() { - @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult { - val getMethod = GetMethod( - client.baseUri.toString() + "$BASE_URL/check_session?sessionId=$sessionId", - true - ) + val getMethod = + GetMethod( + client.baseUri.toString() + "$BASE_URL/check_session?sessionId=$sessionId", + true + ) val status = getMethod.execute(client) return try { - if (status == HttpStatus.SC_OK) { + if (status == HttpStatus.SC_OK) { val responseBody = getMethod.getResponseBodyAsString() val jsonResponse = gson.fromJson(responseBody, Session::class.java) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt index 6bfac67763..a06062f329 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/GenerateSessionRemoteOperation.kt @@ -19,7 +19,6 @@ import org.apache.commons.httpclient.HttpStatus class GenerateSessionRemoteOperation( private val sessionId: String ) : RemoteOperation() { - @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult { val url = client.baseUri.toString() + "$BASE_URL/generate?sessionId=$sessionId" diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt index 52413eb31b..e5b1048618 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Session.kt @@ -17,5 +17,5 @@ data class Session( ) data class SessionTask( - val taskId: Long, + val taskId: Long ) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt index 53ee5cc1c7..a783f41c98 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/GetTaskTypesRemoteOperationV2.kt @@ -71,8 +71,8 @@ class GetTaskTypesRemoteOperationV2 : OCSRemoteOperation>() { ?.types ?.map { (key, value) -> value.copy(id = value.id ?: key) } ?.filter { taskType -> - isSingleTextInputOutput(taskType) || taskType.isChat - }?.sortedByDescending { it.isChat } + isSingleTextInputOutput(taskType) || taskType.isChat() + }?.sortedByDescending { it.isChat() } result = RemoteOperationResult(true, getMethod) result.resultData = taskTypeList diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt index cae1b4ee15..6ce58e21e8 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/v2/model/TaskTypes.kt @@ -20,7 +20,8 @@ data class TaskTypeData( val outputShape: Map ) { private val chatTaskName = "Chat" - val isChat: Boolean = (name == chatTaskName) + + fun isChat(): Boolean = (name == chatTaskName) companion object { private const val CONVERSATION_LIST_ID = "ConversationList" From 71801ff44e4a6ff0b8a740d32915fde330033097 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 13 Nov 2025 11:09:13 +0100 Subject: [PATCH 09/14] remove client related extensions Signed-off-by: alperozturk --- .../assistant/chat/model/ChatMessage.kt | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt index 3e8eef8035..618eb95a68 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/ChatMessage.kt @@ -9,12 +9,6 @@ package com.owncloud.android.lib.resources.assistant.chat.model import com.google.gson.annotations.SerializedName -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import java.util.Locale -import kotlin.time.ExperimentalTime -import kotlin.time.Instant -import kotlin.time.toJavaInstant data class ChatMessage( val id: Long, @@ -27,23 +21,4 @@ data class ChatMessage( val ocpTaskId: Any?, val sources: String, val attachments: List -) { - companion object { - private const val TIMESTAMP_PRESENTATION_TIME_PATTERN = "HH:mm" - } - - fun isHuman(): Boolean = role == "human" - - @OptIn(ExperimentalTime::class) - fun timestampRepresentation(): String { - val instant = Instant.fromEpochSeconds(timestamp) - val deviceZone = ZoneId.systemDefault() - - val formatter = - DateTimeFormatter - .ofPattern(TIMESTAMP_PRESENTATION_TIME_PATTERN, Locale.getDefault()) - .withZone(deviceZone) - - return formatter.format(instant.toJavaInstant()) - } -} +) From e81016b7c6c91157c9d0bca4dc53672444c18076 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 17 Nov 2025 15:26:36 +0100 Subject: [PATCH 10/14] add assistant chat test Signed-off-by: alperozturk --- .../assistant/chat/AssistantChatTests.kt | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt new file mode 100644 index 0000000000..00d2c72300 --- /dev/null +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt @@ -0,0 +1,146 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.assistant.chat + +import com.owncloud.android.AbstractIT +import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessageRequest +import com.owncloud.android.lib.resources.assistant.v2.GetTaskTypesRemoteOperationV2 +import com.owncloud.android.lib.resources.status.NextcloudVersion +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test + +class AssistantChatTests : AbstractIT() { + private lateinit var sessionId: String + + @Before + fun before() { + testOnlyOnServer(NextcloudVersion.nextcloud_30) + sessionId = "test-session-${System.currentTimeMillis()}" + } + + @Test + fun testCreateAndGetMessages() { + val messageRequest = + ChatMessageRequest( + sessionId = sessionId, + role = "human", + content = "Hello assistant!", + timestamp = System.currentTimeMillis() + ) + + val createResult = CreateMessageRemoteOperation(messageRequest).execute(nextcloudClient) + assertTrue(createResult.isSuccess) + + val createdMessage = createResult.resultData!! + assertEquals("Hello assistant!", createdMessage.content) + assertEquals("human", createdMessage.role) + assertEquals(sessionId.toLongOrNull(), createdMessage.sessionId) + + // Get messages for session + val getResult = GetMessagesRemoteOperation(sessionId).execute(nextcloudClient) + assertTrue(getResult.isSuccess) + + val messages = getResult.resultData + assertTrue(messages.isNotEmpty()) + assertTrue(messages.any { it.id == createdMessage.id }) + } + + @Test + fun testDeleteMessage() { + val messageRequest = + ChatMessageRequest( + sessionId = sessionId, + role = "human", + content = "Message to delete", + timestamp = System.currentTimeMillis() + ) + val createResult = CreateMessageRemoteOperation(messageRequest).execute(nextcloudClient) + assertTrue(createResult.isSuccess) + + val messageId = createResult.resultData!!.id.toString() + + // Delete the message + val deleteResult = DeleteMessageRemoteOperation(messageId, sessionId).execute(nextcloudClient) + assertTrue(deleteResult.isSuccess) + + // Ensure the message is gone + val getResult = GetMessagesRemoteOperation(sessionId).execute(nextcloudClient) + assertTrue(getResult.isSuccess) + assertTrue(getResult.resultData!!.none { it.id.toString() == messageId }) + } + + @Test + fun testGetAndDeleteConversations() { + // Create a message to have a session + val messageRequest = + ChatMessageRequest( + sessionId = sessionId, + role = "human", + content = "Starting conversation", + timestamp = System.currentTimeMillis() + ) + CreateMessageRemoteOperation(messageRequest).execute(nextcloudClient) + + // Get list of conversations + val getConversationsResult = GetConversationListRemoteOperation().execute(nextcloudClient) + assertTrue(getConversationsResult.isSuccess) + + val conversations = getConversationsResult.resultData + assertTrue(conversations.any { it.id.toString() == sessionId }) + + // Delete conversation + val deleteResult = DeleteConversationRemoteOperation(sessionId).execute(nextcloudClient) + assertTrue(deleteResult.isSuccess) + + // Ensure conversation is gone + val getAfterDelete = GetConversationListRemoteOperation().execute(nextcloudClient) + assertTrue(getAfterDelete.isSuccess) + assertTrue(getAfterDelete.resultData!!.none { it.id.toString() == sessionId }) + } + + @Test + fun testGetTaskTypesAndVerifyChatAndSorting() { + val result = GetTaskTypesRemoteOperationV2().execute(nextcloudClient) + + assertTrue("Request must succeed", result.isSuccess) + val types = result.resultData + assertNotNull("Task types must not be null", types) + assertTrue("Task types list must not be empty", types!!.isNotEmpty()) + + val firstElementIsChat = types.first().isChat() + assertTrue( + "The first task type must be a chat type (sorted by isChat descending)", + firstElementIsChat + ) + + val chatTypes = types.filter { it.isChat() } + assertTrue("There must be at least one chat-type task", chatTypes.isNotEmpty()) + + val nonChat = types.filterNot { it.isChat() } + assertTrue( + "There must be at least one non-chat task with single text input/output", + nonChat.isNotEmpty() + ) + + val indexOfFirstNonChat = types.indexOfFirst { !it.isChat() } + if (indexOfFirstNonChat > 0) { + val anyChatAfterNonChat = types.drop(indexOfFirstNonChat).any { it.isChat() } + assertTrue( + "Chat types must appear before non-chat types in the list", + !anyChatAfterNonChat + ) + } + + types.forEach { tt -> + assertNotNull("Each task type must have an ID assigned", tt.id) + } + } +} From 3f5ac28506cd4f4aaf6d77c4df6ca02233d5ec11 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 17 Nov 2025 15:27:37 +0100 Subject: [PATCH 11/14] add assistant chat test Signed-off-by: alperozturk --- .../android/lib/resources/assistant/chat/AssistantChatTests.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt index 00d2c72300..73206eb6d3 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt @@ -1,6 +1,7 @@ /* * Nextcloud Android Library * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2025 Alper Ozturk * SPDX-License-Identifier: MIT */ From 9cd32ec47c158b59a88060be9f59b1e3087146e3 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 19 Nov 2025 14:50:28 +0100 Subject: [PATCH 12/14] add assistant chat test Signed-off-by: alperozturk --- .../lib/resources/assistant/chat/AssistantChatTests.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt index 73206eb6d3..e9b0d085ea 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt @@ -24,7 +24,14 @@ class AssistantChatTests : AbstractIT() { @Before fun before() { testOnlyOnServer(NextcloudVersion.nextcloud_30) - sessionId = "test-session-${System.currentTimeMillis()}" + + val result = + CreateConversationRemoteOperation(null, System.currentTimeMillis()) + .execute(nextcloudClient) + assertTrue(result.isSuccess) + sessionId = + result.resultData.session.id + .toString() } @Test From caecf89c98b26c500aa53694e724b999be9b85fb Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 19 Nov 2025 15:26:10 +0100 Subject: [PATCH 13/14] fix lint Signed-off-by: alperozturk --- .../android/lib/resources/assistant/chat/model/Conversation.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt index 9f350e7e2c..c5d7d66a81 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/assistant/chat/model/Conversation.kt @@ -8,6 +8,8 @@ package com.owncloud.android.lib.resources.assistant.chat.model +import android.os.Build +import androidx.annotation.RequiresApi import com.google.gson.annotations.SerializedName import java.time.ZoneId import java.time.format.DateTimeFormatter @@ -32,6 +34,7 @@ data class Conversation( } @OptIn(ExperimentalTime::class) + @RequiresApi(Build.VERSION_CODES.O) fun titleRepresentation(): String { return if (title != null) { title From 39ac8465bdd070046d1aa474ce1b320fdcb4e720 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 27 Nov 2025 09:35:22 +0100 Subject: [PATCH 14/14] testOnlyOnServer v34 for testGetTaskTypesAndVerifyChatAndSorting Signed-off-by: alperozturk --- .../lib/resources/assistant/chat/AssistantChatTests.kt | 2 ++ .../android/lib/resources/status/NextcloudVersion.kt | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt index e9b0d085ea..22e8531234 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/assistant/chat/AssistantChatTests.kt @@ -116,6 +116,8 @@ class AssistantChatTests : AbstractIT() { @Test fun testGetTaskTypesAndVerifyChatAndSorting() { + testOnlyOnServer(NextcloudVersion.nextcloud_34) + val result = GetTaskTypesRemoteOperationV2().execute(nextcloudClient) assertTrue("Request must succeed", result.isSuccess) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt b/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt index 6daf025c97..fa5c22dafa 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/status/NextcloudVersion.kt @@ -44,6 +44,12 @@ class NextcloudVersion : OwnCloudVersion { @JvmField val nextcloud_32 = NextcloudVersion(0x20000000) // 32.0 + + @JvmField + val nextcloud_33 = NextcloudVersion(0x21000000) // 33.0 + + @JvmField + val nextcloud_34 = NextcloudVersion(0x22000000) // 34.0 } constructor(string: String) : super(string)