Skip to content

Commit 99d564f

Browse files
authored
Assistant improvements (#209)
* 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> * Use native decodable instead of SwiftyJson 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> * Refactor Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> --------- Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>
1 parent 37493a9 commit 99d564f

5 files changed

Lines changed: 676 additions & 305 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
7+
// MARK: - ChatMessage
8+
9+
public struct AssistantChatMessage: Codable, Identifiable, Equatable {
10+
public let id: Int
11+
public let sessionId: Int
12+
public let role: String
13+
public let content: String
14+
public let timestamp: Int
15+
16+
public var isFromHuman: Bool {
17+
role == "human"
18+
}
19+
20+
public init(id: Int, sessionId: Int, role: String, content: String, timestamp: Int) {
21+
self.id = id
22+
self.sessionId = sessionId
23+
self.role = role
24+
self.content = content
25+
self.timestamp = timestamp
26+
}
27+
28+
enum CodingKeys: String, CodingKey {
29+
case id
30+
case sessionId = "session_id"
31+
case role
32+
case content
33+
case timestamp
34+
}
35+
}
36+
37+
// MARK: - ChatMessageRequest
38+
39+
public struct AssistantChatMessageRequest: Encodable {
40+
public let sessionId: Int
41+
public let role: String
42+
public let content: String
43+
public let timestamp: Int
44+
public let firstHumanMessage: Bool
45+
46+
public init(sessionId: Int, role: String, content: String, timestamp: Int, firstHumanMessage: Bool) {
47+
self.sessionId = sessionId
48+
self.role = role
49+
self.content = content
50+
self.timestamp = timestamp
51+
self.firstHumanMessage = firstHumanMessage
52+
}
53+
54+
var bodyMap: [String: Any] {
55+
return [
56+
"sessionId": sessionId,
57+
"role": role,
58+
"content": content,
59+
"timestamp": timestamp
60+
]
61+
}
62+
63+
enum CodingKeys: String, CodingKey {
64+
case sessionId
65+
case role
66+
case content
67+
case timestamp
68+
case firstHumanMessage
69+
}
70+
}
71+
72+
// MARK: - Session
73+
74+
public struct AssistantConversation: Codable, Equatable, Hashable {
75+
public let id: Int
76+
public let userId: String?
77+
private let title: String?
78+
public let timestamp: Int
79+
80+
enum CodingKeys: String, CodingKey {
81+
case id
82+
case userId = "user_id"
83+
case title
84+
case timestamp
85+
}
86+
87+
public var validTitle: String {
88+
return title ?? createTitle()
89+
90+
func createTitle() -> String {
91+
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
92+
let formatter = DateFormatter()
93+
formatter.locale = .current
94+
formatter.timeZone = .current
95+
formatter.dateFormat = "MMM d yyyy, HH:mm"
96+
return formatter.string(from: date)
97+
}
98+
}
99+
}
100+
101+
// MARK: - CreateConversation
102+
103+
public struct AssistantCreatedConversation: Codable, Equatable {
104+
public let conversation: AssistantConversation
105+
106+
enum CodingKeys: String, CodingKey {
107+
case conversation = "session"
108+
}
109+
}
110+
111+
// MARK: - Session
112+
113+
public struct AssistantSession: Codable, Equatable {
114+
public let messageTaskId: Int?
115+
public let titleTaskId: Int?
116+
public let sessionTitle: String?
117+
public let sessionAgencyPendingActions: String?
118+
public let taskId: Int?
119+
120+
enum CodingKeys: String, CodingKey {
121+
case messageTaskId
122+
case titleTaskId
123+
case sessionTitle
124+
case sessionAgencyPendingActions
125+
case taskId
126+
}
127+
}
128+
129+
// MARK: - SessionTask
130+
131+
public struct AssistantSessionTask: Codable, Equatable {
132+
public let taskId: Int
133+
134+
enum CodingKeys: String, CodingKey {
135+
case taskId
136+
}
137+
}

Sources/NextcloudKit/Models/Assistant/v2/TaskList.swift

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,41 @@
22
// SPDX-FileCopyrightText: 2025 Milen Pivchev
33
// SPDX-License-Identifier: GPL-3.0-or-later
44

5-
import SwiftyJSON
5+
import Foundation
6+
7+
// MARK: - OCS Response Wrappers
8+
9+
public struct OCSTaskListResponse: Codable {
10+
public let ocs: OCSTaskListOCS
11+
12+
public struct OCSTaskListOCS: Codable {
13+
public let data: OCSTaskListData
14+
}
15+
16+
public struct OCSTaskListData: Codable {
17+
public let tasks: [AssistantTask]
18+
}
19+
}
20+
21+
public struct OCSTaskResponse: Codable {
22+
public let ocs: OCSTaskOCS
23+
24+
public struct OCSTaskOCS: Codable {
25+
public let data: OCSTaskData
26+
}
27+
28+
public struct OCSTaskData: Codable {
29+
public let task: AssistantTask
30+
}
31+
}
32+
33+
// MARK: - Task Models
634

735
public struct TaskList: Codable {
836
public var tasks: [AssistantTask]
937

10-
static func deserialize(from data: JSON) -> TaskList? {
11-
let tasks = data.arrayValue.map { taskJson in
12-
AssistantTask(
13-
id: taskJson["id"].int64Value,
14-
type: taskJson["type"].string,
15-
status: taskJson["status"].string,
16-
userId: taskJson["userId"].string,
17-
appId: taskJson["appId"].string,
18-
input: TaskInput(input: taskJson["input"]["input"].string),
19-
output: TaskOutput(output: taskJson["output"]["output"].string),
20-
completionExpectedAt: taskJson["completionExpectedAt"].int,
21-
progress: taskJson["progress"].int,
22-
lastUpdated: taskJson["lastUpdated"].int,
23-
scheduledAt: taskJson["scheduledAt"].int,
24-
endedAt: taskJson["endedAt"].int
25-
)
26-
}
27-
28-
return TaskList(tasks: tasks)
38+
public init(tasks: [AssistantTask]) {
39+
self.tasks = tasks
2940
}
3041
}
3142

@@ -57,25 +68,6 @@ public struct AssistantTask: Codable {
5768
self.scheduledAt = scheduledAt
5869
self.endedAt = endedAt
5970
}
60-
61-
static func deserialize(from data: JSON) -> AssistantTask? {
62-
let task = AssistantTask(
63-
id: data["id"].int64Value,
64-
type: data["type"].string,
65-
status: data["status"].string,
66-
userId: data["userId"].string,
67-
appId: data["appId"].string,
68-
input: TaskInput(input: data["input"]["input"].string),
69-
output: TaskOutput(output: data["output"]["output"].string),
70-
completionExpectedAt: data["completionExpectedAt"].int,
71-
progress: data["progress"].int,
72-
lastUpdated: data["lastUpdated"].int,
73-
scheduledAt: data["scheduledAt"].int,
74-
endedAt: data["endedAt"].int
75-
)
76-
77-
return task
78-
}
7971
}
8072

8173
public struct TaskInput: Codable {
@@ -93,5 +85,3 @@ public struct TaskOutput: Codable {
9385
self.output = output
9486
}
9587
}
96-
97-

Sources/NextcloudKit/Models/Assistant/v2/TaskTypes.swift

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,34 @@
22
// SPDX-FileCopyrightText: 2025 Milen Pivchev
33
// SPDX-License-Identifier: GPL-3.0-or-later
44

5-
import SwiftyJSON
5+
import Foundation
6+
7+
// MARK: - OCS Response Wrapper
8+
9+
public struct OCSTaskTypesResponse: Codable {
10+
public let ocs: OCSTaskTypesOCS
11+
12+
public struct OCSTaskTypesOCS: Codable {
13+
public let data: OCSTaskTypesData
14+
}
15+
16+
public struct OCSTaskTypesData: Codable {
17+
public let types: [String: TaskTypeData]
18+
}
19+
}
20+
21+
// MARK: - Task Type Models
622

723
public struct TaskTypes: Codable {
824
public let types: [TaskTypeData]
925

10-
static func deserialize(from data: JSON) -> TaskTypes? {
11-
var taskTypes: [TaskTypeData] = []
12-
13-
for (key, subJson) in data {
14-
let taskTypeData = TaskTypeData(
15-
id: key,
16-
name: subJson["name"].string,
17-
description: subJson["description"].string,
18-
inputShape: subJson["inputShape"].dictionary != nil ? TaskInputShape(
19-
input: subJson["inputShape"]["input"].dictionary != nil ? Shape(
20-
name: subJson["inputShape"]["input"]["name"].stringValue,
21-
description: subJson["inputShape"]["input"]["description"].stringValue,
22-
type: subJson["inputShape"]["input"]["type"].stringValue
23-
) : nil
24-
) : nil,
25-
outputShape: subJson["outputShape"].dictionary != nil ? TaskOutputShape(
26-
output: subJson["outputShape"]["output"].dictionary != nil ? Shape(
27-
name: subJson["outputShape"]["output"]["name"].stringValue,
28-
description: subJson["outputShape"]["output"]["description"].stringValue,
29-
type: subJson["outputShape"]["output"]["type"].stringValue
30-
) : nil
31-
) : nil
32-
)
33-
34-
taskTypes.append(taskTypeData)
35-
}
36-
37-
return TaskTypes(types: taskTypes)
26+
public init(types: [TaskTypeData]) {
27+
self.types = types
3828
}
3929
}
4030

4131
public struct TaskTypeData: Codable {
42-
public let id: String?
32+
public var id: String?
4333
public let name: String?
4434
public let description: String?
4535
public let inputShape: TaskInputShape?
@@ -81,9 +71,3 @@ public struct Shape: Codable {
8171
self.type = type
8272
}
8373
}
84-
85-
86-
87-
88-
89-

Sources/NextcloudKit/NKInterceptor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ final class NKInterceptor: RequestInterceptor, Sendable {
1212
self.nkCommonInstance = nkCommonInstance
1313
}
1414

15-
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
15+
func adapt(_ urlRequest: URLRequest, for session: AssistantSession, completion: @escaping (Result<URLRequest, Error>) -> Void) {
1616
// Log request URL in verbose mode
1717
if NKLogFileManager.shared.logLevel == .verbose,
1818
let url = urlRequest.url?.absoluteString {

0 commit comments

Comments
 (0)