Skip to content

Commit f15b9c2

Browse files
Assistant Chat (#3978)
* WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * Send message Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * Refactor Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * WIP Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * NCKit Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * Refactor Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * Add error handling Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> * fix assistantButtonItem Signed-off-by: Marino Faggiana <marino.faggiana@nextcloud.com> --------- Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> Signed-off-by: Marino Faggiana <marino.faggiana@nextcloud.com> Co-authored-by: Marino Faggiana <marino.faggiana@nextcloud.com>
1 parent 2b2b3ce commit f15b9c2

19 files changed

Lines changed: 793 additions & 275 deletions

Nextcloud.xcodeproj/project.pbxproj

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,12 @@
160160
F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; };
161161
F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; };
162162
F3C587AE2D47E4FE004532DB /* PHAssetCollectionThumbnailLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C587AD2D47E4FE004532DB /* PHAssetCollectionThumbnailLoader.swift */; };
163+
F3C6F6F62F34CC0900C531B6 /* NCAssistantChatConversationsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C6F6F52F34CC0900C531B6 /* NCAssistantChatConversationsModel.swift */; };
163164
F3CA337D2D0B2B6C00672333 /* AlbumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3CA337C2D0B2B6A00672333 /* AlbumModel.swift */; };
165+
F3DDFE0F2F15453900A784C8 /* NCAssistantChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DDFE0E2F15453900A784C8 /* NCAssistantChat.swift */; };
166+
F3DDFE1E2F1F8EC600A784C8 /* ChatInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DDFE1D2F1F8EC600A784C8 /* ChatInputField.swift */; };
167+
F3DDFE212F1F953000A784C8 /* NCAssistantChatConversations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DDFE202F1F953000A784C8 /* NCAssistantChatConversations.swift */; };
168+
F3DDFE232F1FB4C300A784C8 /* NCAssistantChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DDFE222F1FB4C300A784C8 /* NCAssistantChatModel.swift */; };
164169
F3E173B02C9AF637006D177A /* ScreenAwakeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E173AF2C9AF637006D177A /* ScreenAwakeManager.swift */; };
165170
F3E173C02C9B1067006D177A /* AwakeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E173BF2C9B1067006D177A /* AwakeMode.swift */; };
166171
F3E173C12C9B1067006D177A /* AwakeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E173BF2C9B1067006D177A /* AwakeMode.swift */; };
@@ -1267,7 +1272,12 @@
12671272
F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = "<group>"; };
12681273
F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = "<group>"; };
12691274
F3C587AD2D47E4FE004532DB /* PHAssetCollectionThumbnailLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHAssetCollectionThumbnailLoader.swift; sourceTree = "<group>"; };
1275+
F3C6F6F52F34CC0900C531B6 /* NCAssistantChatConversationsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAssistantChatConversationsModel.swift; sourceTree = "<group>"; };
12701276
F3CA337C2D0B2B6A00672333 /* AlbumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumModel.swift; sourceTree = "<group>"; };
1277+
F3DDFE0E2F15453900A784C8 /* NCAssistantChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAssistantChat.swift; sourceTree = "<group>"; };
1278+
F3DDFE1D2F1F8EC600A784C8 /* ChatInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInputField.swift; sourceTree = "<group>"; };
1279+
F3DDFE202F1F953000A784C8 /* NCAssistantChatConversations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAssistantChatConversations.swift; sourceTree = "<group>"; };
1280+
F3DDFE222F1FB4C300A784C8 /* NCAssistantChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAssistantChatModel.swift; sourceTree = "<group>"; };
12711281
F3E173AF2C9AF637006D177A /* ScreenAwakeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenAwakeManager.swift; sourceTree = "<group>"; };
12721282
F3E173BF2C9B1067006D177A /* AwakeMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwakeMode.swift; sourceTree = "<group>"; };
12731283
F3F442ED2DDE292600FD701F /* NCMetadataPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataPermissions.swift; sourceTree = "<group>"; };
@@ -2107,6 +2117,7 @@
21072117
F3374A7F2D64AB40002A38F9 /* Components */ = {
21082118
isa = PBXGroup;
21092119
children = (
2120+
F3DDFE1D2F1F8EC600A784C8 /* ChatInputField.swift */,
21102121
F3374A832D64AC2C002A38F9 /* AssistantLabelStyle.swift */,
21112122
F3374A802D64AB9E002A38F9 /* StatusInfo.swift */,
21122123
F3A0478F2BD2668800658E7B /* NCAssistantEmptyView.swift */,
@@ -2138,10 +2149,12 @@
21382149
isa = PBXGroup;
21392150
children = (
21402151
F3A047962BD2668800658E7B /* NCAssistant.swift */,
2152+
F3A047932BD2668800658E7B /* NCAssistantModel.swift */,
2153+
F3DDFE0D2F15452F00A784C8 /* Chat */,
2154+
F3DDFE1F2F1F951000A784C8 /* Chat Sessions */,
21412155
F3A047902BD2668800658E7B /* Create Task */,
21422156
F3A047942BD2668800658E7B /* Task Detail */,
21432157
F3374A7F2D64AB40002A38F9 /* Components */,
2144-
F3A047922BD2668800658E7B /* Models */,
21452158
);
21462159
path = Assistant;
21472160
sourceTree = "<group>";
@@ -2154,14 +2167,6 @@
21542167
path = "Create Task";
21552168
sourceTree = "<group>";
21562169
};
2157-
F3A047922BD2668800658E7B /* Models */ = {
2158-
isa = PBXGroup;
2159-
children = (
2160-
F3A047932BD2668800658E7B /* NCAssistantModel.swift */,
2161-
);
2162-
path = Models;
2163-
sourceTree = "<group>";
2164-
};
21652170
F3A047942BD2668800658E7B /* Task Detail */ = {
21662171
isa = PBXGroup;
21672172
children = (
@@ -2181,6 +2186,24 @@
21812186
path = Cells;
21822187
sourceTree = "<group>";
21832188
};
2189+
F3DDFE0D2F15452F00A784C8 /* Chat */ = {
2190+
isa = PBXGroup;
2191+
children = (
2192+
F3DDFE0E2F15453900A784C8 /* NCAssistantChat.swift */,
2193+
F3DDFE222F1FB4C300A784C8 /* NCAssistantChatModel.swift */,
2194+
);
2195+
path = Chat;
2196+
sourceTree = "<group>";
2197+
};
2198+
F3DDFE1F2F1F951000A784C8 /* Chat Sessions */ = {
2199+
isa = PBXGroup;
2200+
children = (
2201+
F3DDFE202F1F953000A784C8 /* NCAssistantChatConversations.swift */,
2202+
F3C6F6F52F34CC0900C531B6 /* NCAssistantChatConversationsModel.swift */,
2203+
);
2204+
path = "Chat Sessions";
2205+
sourceTree = "<group>";
2206+
};
21842207
F3E173BE2C9B1057006D177A /* ScreenAwakeManager */ = {
21852208
isa = PBXGroup;
21862209
children = (
@@ -4412,6 +4435,7 @@
44124435
F7AE00F8230E81CB007ACF8A /* NCBrowserWeb.swift in Sources */,
44134436
F77DD6A82C5CC093009448FB /* NCSession.swift in Sources */,
44144437
F702F30825EE5D47008F8E80 /* NCPopupViewController.swift in Sources */,
4438+
F3C6F6F62F34CC0900C531B6 /* NCAssistantChatConversationsModel.swift in Sources */,
44154439
F76340FC2EBDF64D0056F538 /* NCManageDatabase+Tag.swift in Sources */,
44164440
F733598125C1C188002ABA72 /* NCAskAuthorization.swift in Sources */,
44174441
370D26AF248A3D7A00121797 /* NCCellMain.swift in Sources */,
@@ -4583,6 +4607,7 @@
45834607
F7816EF22C2C3E1F00A52517 /* NCPushNotification.swift in Sources */,
45844608
F76882342C0DD1E7001CF441 /* NCDisplayView.swift in Sources */,
45854609
F7C30DF6291BC0CA0017149B /* NCNetworkingE2EEUpload.swift in Sources */,
4610+
F3DDFE232F1FB4C300A784C8 /* NCAssistantChatModel.swift in Sources */,
45864611
F7501C332212E57500FB1415 /* NCMedia.swift in Sources */,
45874612
F7411C552D7B26D700F57358 /* NCNetworking+ServerError.swift in Sources */,
45884613
F72944F22A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */,
@@ -4699,7 +4724,9 @@
46994724
F7A03E2F2D425A14007AA677 /* NCFavoriteNavigationController.swift in Sources */,
47004725
F343A4BB2A1E734600DDA874 /* Optional+Extension.swift in Sources */,
47014726
F76882232C0DD1E7001CF441 /* NCCapabilitiesModel.swift in Sources */,
4727+
F3DDFE212F1F953000A784C8 /* NCAssistantChatConversations.swift in Sources */,
47024728
F7E2B64F2DDCC5C30075B4D0 /* NCMedia+TransferDelegate.swift in Sources */,
4729+
F3DDFE0F2F15453900A784C8 /* NCAssistantChat.swift in Sources */,
47034730
F7D68FCC28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */,
47044731
F76882292C0DD1E7001CF441 /* NCManageE2EEModel.swift in Sources */,
47054732
F799DF8B2C4B84EB003410B5 /* NCCollectionViewCommon+EndToEndInitialize.swift in Sources */,
@@ -4723,6 +4750,7 @@
47234750
F76882272C0DD1E7001CF441 /* NCManageE2EEView.swift in Sources */,
47244751
F7864ACC2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
47254752
F7327E302B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */,
4753+
F3DDFE1E2F1F8EC600A784C8 /* ChatInputField.swift in Sources */,
47264754
F7D61EA82EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */,
47274755
F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */,
47284756
F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */,

Tests/NextcloudUITests/AssistantUITests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ final class AssistantUITests: BaseUIXCTestCase {
5050
}
5151

5252
private func createTask(input: String) {
53-
app.navigationBars["Assistant"].buttons["CreateButton"].tap()
53+
app.navigationBars["Assistant"].buttons["ConversationsButton"].tap()
5454

5555
let inputTextEditor = app.textViews["InputTextEditor"]
5656
inputTextEditor.await()

iOSClient/Account/NCAccount.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class NCAccount: NSObject {
194194
return
195195
}
196196

197-
await showErrorBanner(controller: controller, text: "_account_unauthorized_", errorCode: NCGlobal.shared.errorUnauthorized401)
197+
await showErrorBanner(controller: controller, text: String(format: NSLocalizedString("_account_unauthorized_", comment: ""), account), errorCode: NCGlobal.shared.errorUnauthorized401)
198198

199199
let resultsWipe = await NextcloudKit.shared.getRemoteWipeStatusAsync(serverUrl: tblAccount.urlBase, token: token, account: account) { task in
200200
Task {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-FileCopyrightText: Nextcloud GmbH
2+
// SPDX-FileCopyrightText: 2026 Milen Pivchev
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
import Foundation
6+
import SwiftUI
7+
import NextcloudKit
8+
9+
struct NCAssistantChatConversations: View {
10+
var conversationsModel: NCAssistantChatConversationsModel
11+
var selectedConversation: AssistantConversation?
12+
var onConversationSelected: (AssistantConversation?) -> Void
13+
14+
@Environment(\.dismiss) private var dismiss
15+
16+
var body: some View {
17+
Group {
18+
List(conversationsModel.conversations, id: \.id) { conversation in
19+
Button {
20+
onConversationSelected(conversation)
21+
dismiss()
22+
} label: {
23+
HStack {
24+
Text(conversation.validTitle)
25+
Spacer()
26+
if selectedConversation?.id == conversation.id {
27+
Image(systemName: "checkmark")
28+
.foregroundStyle(.blue)
29+
}
30+
}
31+
.contentShape(Rectangle())
32+
}
33+
}
34+
}
35+
.navigationTitle(NSLocalizedString("_conversations_", comment: ""))
36+
.toolbar {
37+
ToolbarItem(placement: .topBarTrailing) {
38+
Button("_new_conversation_", systemImage: "plus.message.fill") {
39+
Task {
40+
let session = await conversationsModel.createNewConversation()
41+
onConversationSelected(session)
42+
dismiss()
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
50+
#Preview {
51+
NCAssistantChatConversations(conversationsModel: NCAssistantChatConversationsModel(controller: nil), selectedConversation: nil, onConversationSelected: { _ in })
52+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-FileCopyrightText: Nextcloud GmbH
2+
// SPDX-FileCopyrightText: 2026 Milen Pivchev
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
import Foundation
6+
import NextcloudKit
7+
8+
@Observable class NCAssistantChatConversationsModel {
9+
var conversations: [AssistantConversation] = []
10+
var isLoading: Bool = false
11+
var hasError: Bool = false
12+
13+
private let ncSession: NCSession.Session
14+
15+
init(controller: NCMainTabBarController?) {
16+
self.ncSession = NCSession.shared.getSession(controller: controller)
17+
loadAllSessions()
18+
}
19+
20+
func loadAllSessions() {
21+
Task {
22+
let result = await NextcloudKit.shared.getAssistantChatConversations(account: ncSession.account)
23+
conversations = result.sessions ?? []
24+
}
25+
}
26+
27+
func createNewConversation(title: String? = nil) async -> AssistantConversation? {
28+
let timestamp = Int(Date().timeIntervalSince1970)
29+
let result = await NextcloudKit.shared.createAssistantChatConversation(title: title, timestamp: timestamp, account: ncSession.account)
30+
if result.error == .success, let newConversation = result.conversation?.conversation {
31+
conversations.insert(newConversation, at: 0)
32+
return newConversation
33+
} else {
34+
hasError = true
35+
return nil
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)