From 9137d72f005a0d06bcc1a6ffc7bbfd693b36a6d3 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Wed, 20 Nov 2024 13:34:35 +0100 Subject: [PATCH 01/10] Added AI typing event --- .../Events/AITypingEvents.swift | 46 +++++++++++++++++++ StreamChat.xcodeproj/project.pbxproj | 6 +++ 2 files changed, 52 insertions(+) create mode 100644 Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift new file mode 100644 index 00000000000..2d3a974e57b --- /dev/null +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -0,0 +1,46 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import Foundation + +public struct AITypingEvent: Event { + public let typingState: AITypingState + public let cid: ChannelId? +} + +class AITypingEventDTO: EventDTO { + let payload: EventPayload + let typingState: String + + init(from response: EventPayload, typingState: String) throws { + payload = response + self.typingState = typingState + } + + func toDomainEvent(session: DatabaseSession) -> Event? { + if let typingState = AITypingState(rawValue: typingState) { + return AITypingEvent(typingState: typingState, cid: payload.cid) + } else { + return nil + } + } +} + +public struct AITypingState: ExpressibleByStringLiteral, Hashable { + public var rawValue: String + + public init?(rawValue: String) { + self.rawValue = rawValue + } + + public init(stringLiteral value: String) { + rawValue = value + } + + public static let thinking: Self = "io.getstream.ai.thinking" + public static let checkingExternalSources: Self = "io.getstream.ai.external_sources" + public static let generating: Self = "io.getstream.ai.generating" + public static let clear: Self = "io.getstream.ai.clear" +} + \ No newline at end of file diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj index 0e1952ae500..f398f984aa3 100644 --- a/StreamChat.xcodeproj/project.pbxproj +++ b/StreamChat.xcodeproj/project.pbxproj @@ -756,6 +756,8 @@ 847E946E269C687300E31D0C /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E946D269C687300E31D0C /* EventsController.swift */; }; 847F3CEA2689FDEB00D240E0 /* ChatMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F3CE92689FDEB00D240E0 /* ChatMessageCell.swift */; }; 8486CAF926FA51EE00A9AD96 /* EventDTOConverterMiddleware_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486CAF826FA51EE00A9AD96 /* EventDTOConverterMiddleware_Tests.swift */; }; + 848849B62CEE01070010E7CA /* AITypingEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848849B52CEE01070010E7CA /* AITypingEvents.swift */; }; + 848849B72CEE01070010E7CA /* AITypingEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848849B52CEE01070010E7CA /* AITypingEvents.swift */; }; 849980F1277246DB00ABA58B /* UIScrollView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849980F0277246DB00ABA58B /* UIScrollView+Extensions.swift */; }; 849AE664270CB14000423A20 /* VideoAttachmentComposerPreview_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849AE663270CB14000423A20 /* VideoAttachmentComposerPreview_Tests.swift */; }; 849AE666270CB55F00423A20 /* VideoAttachmentGalleryCell_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849AE665270CB55F00423A20 /* VideoAttachmentGalleryCell_Tests.swift */; }; @@ -3661,6 +3663,7 @@ 847E946D269C687300E31D0C /* EventsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsController.swift; sourceTree = ""; }; 847F3CE92689FDEB00D240E0 /* ChatMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageCell.swift; sourceTree = ""; }; 8486CAF826FA51EE00A9AD96 /* EventDTOConverterMiddleware_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDTOConverterMiddleware_Tests.swift; sourceTree = ""; }; + 848849B52CEE01070010E7CA /* AITypingEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AITypingEvents.swift; sourceTree = ""; }; 849980F0277246DB00ABA58B /* UIScrollView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Extensions.swift"; sourceTree = ""; }; 849AE661270CB00000423A20 /* VideoLoader_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoLoader_Mock.swift; sourceTree = ""; }; 849AE663270CB14000423A20 /* VideoAttachmentComposerPreview_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAttachmentComposerPreview_Tests.swift; sourceTree = ""; }; @@ -5516,6 +5519,7 @@ 8A0C3BBB24C0947400CAFD19 /* UserEvents.swift */, 841BAA0F2BCEADAC000C73E4 /* PollsEvents.swift */, AD7BE1692C209888000A5756 /* ThreadEvents.swift */, + 848849B52CEE01070010E7CA /* AITypingEvents.swift */, ); path = Events; sourceTree = ""; @@ -11553,6 +11557,7 @@ 40789D1729F6AC500018C2BB /* AudioPlaybackContextAccessor.swift in Sources */, 79D5CDD427EA1BE300BE7D8B /* MessageTranslationsPayload.swift in Sources */, 88D85D97252F168000AE1030 /* MemberController+SwiftUI.swift in Sources */, + 848849B72CEE01070010E7CA /* AITypingEvents.swift in Sources */, 43D3F0FC28410A0200B74921 /* CreateCallRequestBody.swift in Sources */, 79877A0D2498E4BC00015F8B /* CurrentUser.swift in Sources */, 4042967D29FAC9DA0089126D /* AudioAnalysisContext.swift in Sources */, @@ -12499,6 +12504,7 @@ C121E8C2274544B100023E4C /* ChannelEventsController.swift in Sources */, C121E8C3274544B100023E4C /* ListChange.swift in Sources */, C15C8839286C7BF300E6A72C /* BackgroundListDatabaseObserver.swift in Sources */, + 848849B62CEE01070010E7CA /* AITypingEvents.swift in Sources */, ADB951B3291C3CE900800554 /* AnyAttachmentUpdater.swift in Sources */, 40789D3829F6AC500018C2BB /* AssetPropertyLoading.swift in Sources */, C121E8C4274544B100023E4C /* EntityChange.swift in Sources */, From 55f349b8bf4ceabe9aadf4b80479c839db4b9ce7 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Wed, 20 Nov 2024 13:54:41 +0100 Subject: [PATCH 02/10] Hooked the ai typing event --- .../WebSocketClient/Events/AITypingEvents.swift | 9 ++++----- .../StreamChat/WebSocketClient/Events/EventPayload.swift | 8 +++++++- .../StreamChat/WebSocketClient/Events/EventType.swift | 5 +++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 2d3a974e57b..c2f9f74f8b3 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -11,16 +11,15 @@ public struct AITypingEvent: Event { class AITypingEventDTO: EventDTO { let payload: EventPayload - let typingState: String - init(from response: EventPayload, typingState: String) throws { + init(from response: EventPayload) throws { payload = response - self.typingState = typingState } func toDomainEvent(session: DatabaseSession) -> Event? { - if let typingState = AITypingState(rawValue: typingState) { - return AITypingEvent(typingState: typingState, cid: payload.cid) + if let typingState = payload.typingState, + let aITypingState = AITypingState(rawValue: typingState) { + return AITypingEvent(typingState: aITypingState, cid: payload.cid) } else { return nil } diff --git a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift index d10d0bbf96c..312ac07ff5d 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift @@ -36,6 +36,7 @@ class EventPayload: Decodable { case thread case vote = "poll_vote" case poll + case typingState } let eventType: EventType @@ -68,6 +69,8 @@ class EventPayload: Decodable { /// Thread Data, it is stored in Result, to be easier to debug decoding errors let threadDetails: Result? let threadPartial: Result? + + let typingState: String? init( eventType: EventType, @@ -96,7 +99,8 @@ class EventPayload: Decodable { threadDetails: Result? = nil, threadPartial: Result? = nil, poll: PollPayload? = nil, - vote: PollVotePayload? = nil + vote: PollVotePayload? = nil, + typingState: String? = nil ) { self.eventType = eventType self.connectionId = connectionId @@ -125,6 +129,7 @@ class EventPayload: Decodable { self.threadDetails = threadDetails self.poll = poll self.vote = vote + self.typingState = typingState } required init(from decoder: Decoder) throws { @@ -158,6 +163,7 @@ class EventPayload: Decodable { threadPartial = container.decodeAsResultIfPresent(ThreadPartialPayload.self, forKey: .thread) vote = try container.decodeIfPresent(PollVotePayload.self, forKey: .vote) poll = try container.decodeIfPresent(PollPayload.self, forKey: .poll) + typingState = try container.decodeIfPresent(String.self, forKey: .typingState) } func event() throws -> Event { diff --git a/Sources/StreamChat/WebSocketClient/Events/EventType.swift b/Sources/StreamChat/WebSocketClient/Events/EventType.swift index 37124123aca..8d85ecef4d6 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventType.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventType.swift @@ -143,6 +143,10 @@ public extension EventType { /// When a thread has a new reply. static let threadMessageNew: Self = "notification.thread_message_new" + + // MARK: - AI + + static let aiTypingIndicatorChanged: Self = "ai.typing_indicator_changed" } extension EventType { @@ -208,6 +212,7 @@ extension EventType { case .pollVoteRemoved: return try PollVoteRemovedEventDTO(from: response) case .threadUpdated: return try ThreadUpdatedEventDTO(from: response) case .threadMessageNew: return try ThreadMessageNewEventDTO(from: response) + case .aiTypingIndicatorChanged: return try AITypingEventDTO(from: response) default: if response.cid == nil { throw ClientError.UnknownUserEvent(response.eventType) From c6727fee0e0e5b3ee8239c3b18683b201994a79a Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Wed, 20 Nov 2024 13:59:58 +0100 Subject: [PATCH 03/10] Updated the event name --- Sources/StreamChat/WebSocketClient/Events/EventType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/EventType.swift b/Sources/StreamChat/WebSocketClient/Events/EventType.swift index 8d85ecef4d6..3be2b65a3cd 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventType.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventType.swift @@ -146,7 +146,7 @@ public extension EventType { // MARK: - AI - static let aiTypingIndicatorChanged: Self = "ai.typing_indicator_changed" + static let aiTypingIndicatorChanged: Self = "ai_indicator_changed" } extension EventType { From 1336ad921e74238bb4c112ed04094afb067e22fd Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Thu, 21 Nov 2024 11:43:32 +0100 Subject: [PATCH 04/10] Updated the event --- .../WebSocketClient/Events/AITypingEvents.swift | 3 ++- .../StreamChat/WebSocketClient/Events/EventPayload.swift | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index c2f9f74f8b3..7d79bbd100a 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -7,6 +7,7 @@ import Foundation public struct AITypingEvent: Event { public let typingState: AITypingState public let cid: ChannelId? + public let messageId: MessageId? } class AITypingEventDTO: EventDTO { @@ -19,7 +20,7 @@ class AITypingEventDTO: EventDTO { func toDomainEvent(session: DatabaseSession) -> Event? { if let typingState = payload.typingState, let aITypingState = AITypingState(rawValue: typingState) { - return AITypingEvent(typingState: aITypingState, cid: payload.cid) + return AITypingEvent(typingState: aITypingState, cid: payload.cid, messageId: payload.messageId) } else { return nil } diff --git a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift index 312ac07ff5d..44c33a55a16 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift @@ -36,7 +36,8 @@ class EventPayload: Decodable { case thread case vote = "poll_vote" case poll - case typingState + case typingState = "typing_state" + case messageId = "message_id" } let eventType: EventType @@ -71,6 +72,7 @@ class EventPayload: Decodable { let threadPartial: Result? let typingState: String? + let messageId: String? init( eventType: EventType, @@ -100,7 +102,8 @@ class EventPayload: Decodable { threadPartial: Result? = nil, poll: PollPayload? = nil, vote: PollVotePayload? = nil, - typingState: String? = nil + typingState: String? = nil, + messageId: String? = nil ) { self.eventType = eventType self.connectionId = connectionId @@ -130,6 +133,7 @@ class EventPayload: Decodable { self.poll = poll self.vote = vote self.typingState = typingState + self.messageId = messageId } required init(from decoder: Decoder) throws { @@ -164,6 +168,7 @@ class EventPayload: Decodable { vote = try container.decodeIfPresent(PollVotePayload.self, forKey: .vote) poll = try container.decodeIfPresent(PollPayload.self, forKey: .poll) typingState = try container.decodeIfPresent(String.self, forKey: .typingState) + messageId = try container.decodeIfPresent(String.self, forKey: .messageId) } func event() throws -> Event { From 7a17434c65db2343f7ec88da10db2c6f66230232 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Fri, 22 Nov 2024 13:06:54 +0100 Subject: [PATCH 05/10] Introduced clear event --- .../Events/AITypingEvents.swift | 33 ++++++++++++++----- .../WebSocketClient/Events/EventPayload.swift | 10 +++--- .../WebSocketClient/Events/EventType.swift | 3 ++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 7d79bbd100a..261f5db890b 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -5,7 +5,7 @@ import Foundation public struct AITypingEvent: Event { - public let typingState: AITypingState + public let state: AITypingState public let cid: ChannelId? public let messageId: MessageId? } @@ -18,15 +18,32 @@ class AITypingEventDTO: EventDTO { } func toDomainEvent(session: DatabaseSession) -> Event? { - if let typingState = payload.typingState, - let aITypingState = AITypingState(rawValue: typingState) { - return AITypingEvent(typingState: aITypingState, cid: payload.cid, messageId: payload.messageId) + if let typingState = payload.state, + let aiTypingState = AITypingState(rawValue: typingState) { + return AITypingEvent(state: aiTypingState, cid: payload.cid, messageId: payload.messageId) } else { return nil } } } +public struct AIClearTypingEvent: Event { + public let cid: ChannelId? + public let messageId: MessageId? +} + +class AIClearTypingEventDTO: EventDTO { + let payload: EventPayload + + init(from response: EventPayload) throws { + payload = response + } + + func toDomainEvent(session: any DatabaseSession) -> (any Event)? { + AIClearTypingEvent(cid: payload.cid, messageId: payload.messageId) + } +} + public struct AITypingState: ExpressibleByStringLiteral, Hashable { public var rawValue: String @@ -38,9 +55,9 @@ public struct AITypingState: ExpressibleByStringLiteral, Hashable { rawValue = value } - public static let thinking: Self = "io.getstream.ai.thinking" - public static let checkingExternalSources: Self = "io.getstream.ai.external_sources" - public static let generating: Self = "io.getstream.ai.generating" - public static let clear: Self = "io.getstream.ai.clear" + public static let thinking: Self = "AI_STATE_THINKING" + public static let checkingExternalSources: Self = "AI_STATE_EXTERNAL_SOURCES" + public static let generating: Self = "AI_STATE_GENERATING" + public static let error: Self = "AI_STATE_ERROR" } \ No newline at end of file diff --git a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift index 44c33a55a16..1606ecc120f 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift @@ -36,7 +36,7 @@ class EventPayload: Decodable { case thread case vote = "poll_vote" case poll - case typingState = "typing_state" + case state case messageId = "message_id" } @@ -71,7 +71,7 @@ class EventPayload: Decodable { let threadDetails: Result? let threadPartial: Result? - let typingState: String? + let state: String? let messageId: String? init( @@ -102,7 +102,7 @@ class EventPayload: Decodable { threadPartial: Result? = nil, poll: PollPayload? = nil, vote: PollVotePayload? = nil, - typingState: String? = nil, + state: String? = nil, messageId: String? = nil ) { self.eventType = eventType @@ -132,7 +132,7 @@ class EventPayload: Decodable { self.threadDetails = threadDetails self.poll = poll self.vote = vote - self.typingState = typingState + self.state = state self.messageId = messageId } @@ -167,7 +167,7 @@ class EventPayload: Decodable { threadPartial = container.decodeAsResultIfPresent(ThreadPartialPayload.self, forKey: .thread) vote = try container.decodeIfPresent(PollVotePayload.self, forKey: .vote) poll = try container.decodeIfPresent(PollPayload.self, forKey: .poll) - typingState = try container.decodeIfPresent(String.self, forKey: .typingState) + state = try container.decodeIfPresent(String.self, forKey: .state) messageId = try container.decodeIfPresent(String.self, forKey: .messageId) } diff --git a/Sources/StreamChat/WebSocketClient/Events/EventType.swift b/Sources/StreamChat/WebSocketClient/Events/EventType.swift index 3be2b65a3cd..1bbd0cf92a8 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventType.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventType.swift @@ -147,6 +147,8 @@ public extension EventType { // MARK: - AI static let aiTypingIndicatorChanged: Self = "ai_indicator_changed" + + static let aiTypingIndicatorClear: Self = "ai_indicator_clear" } extension EventType { @@ -213,6 +215,7 @@ extension EventType { case .threadUpdated: return try ThreadUpdatedEventDTO(from: response) case .threadMessageNew: return try ThreadMessageNewEventDTO(from: response) case .aiTypingIndicatorChanged: return try AITypingEventDTO(from: response) + case .aiTypingIndicatorClear: return try AIClearTypingEventDTO(from: response) default: if response.cid == nil { throw ClientError.UnknownUserEvent(response.eventType) From cf65da539e708c4e6e145c49df1ca0312a4103cd Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Fri, 29 Nov 2024 17:08:44 +0100 Subject: [PATCH 06/10] Updated the AI events --- .../Events/AITypingEvents.swift | 60 +++++++++++++++---- .../WebSocketClient/Events/EventPayload.swift | 17 ++++-- .../WebSocketClient/Events/EventType.swift | 12 +++- StreamChat.xcodeproj/project.pbxproj | 24 ++++++++ .../Events/AIIndicator/AIIndicatorClear.json | 16 +++++ .../Events/AIIndicator/AIIndicatorStop.json | 16 +++++ .../Events/AIIndicator/AIIndicatorUpdate.json | 19 ++++++ .../Events/AIIndicatorEvents_Tests.swift | 42 +++++++++++++ 8 files changed, 185 insertions(+), 21 deletions(-) create mode 100644 TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json create mode 100644 TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json create mode 100644 TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json create mode 100644 Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 261f5db890b..633a52d7aaf 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -4,13 +4,19 @@ import Foundation -public struct AITypingEvent: Event { +/// An event that provides updates about the state of the AI typing indicator. +public struct AITypingUpdateEvent: Event { + /// The state of the AI typing indicator. public let state: AITypingState + /// The channel ID this event is related to. public let cid: ChannelId? + /// The message ID this event is related to. public let messageId: MessageId? + /// Optional server message, usually when an error occurs. + public let message: String? } -class AITypingEventDTO: EventDTO { +class AITypingUpdateEventDTO: EventDTO { let payload: EventPayload init(from response: EventPayload) throws { @@ -18,18 +24,24 @@ class AITypingEventDTO: EventDTO { } func toDomainEvent(session: DatabaseSession) -> Event? { - if let typingState = payload.state, + if let typingState = payload.aiState, let aiTypingState = AITypingState(rawValue: typingState) { - return AITypingEvent(state: aiTypingState, cid: payload.cid, messageId: payload.messageId) + return AITypingUpdateEvent( + state: aiTypingState, + cid: payload.cid, + messageId: payload.messageId, + message: payload.aiMessage + ) } else { return nil } } } +/// An event that clears the AI typing indicator. public struct AIClearTypingEvent: Event { + /// The channel ID this event is related to. public let cid: ChannelId? - public let messageId: MessageId? } class AIClearTypingEventDTO: EventDTO { @@ -40,10 +52,29 @@ class AIClearTypingEventDTO: EventDTO { } func toDomainEvent(session: any DatabaseSession) -> (any Event)? { - AIClearTypingEvent(cid: payload.cid, messageId: payload.messageId) + AIClearTypingEvent(cid: payload.cid) } } +/// An event that indicates the AI has stopped generating the message. +public struct AIStopTypingEvent: Event { + /// The channel ID this event is related to. + public let cid: ChannelId? +} + +class AIStopTypingEventDTO: EventDTO { + let payload: EventPayload + + init(from response: EventPayload) throws { + payload = response + } + + func toDomainEvent(session: any DatabaseSession) -> (any Event)? { + AIStopTypingEvent(cid: payload.cid) + } +} + +/// The state of the AI typing indicator. public struct AITypingState: ExpressibleByStringLiteral, Hashable { public var rawValue: String @@ -54,10 +85,15 @@ public struct AITypingState: ExpressibleByStringLiteral, Hashable { public init(stringLiteral value: String) { rawValue = value } - - public static let thinking: Self = "AI_STATE_THINKING" - public static let checkingExternalSources: Self = "AI_STATE_EXTERNAL_SOURCES" - public static let generating: Self = "AI_STATE_GENERATING" - public static let error: Self = "AI_STATE_ERROR" } - \ No newline at end of file + +public extension AITypingState { + /// The AI is thinking. + static let thinking: Self = "AI_STATE_THINKING" + /// The AI is checking external sources. + static let checkingExternalSources: Self = "AI_STATE_EXTERNAL_SOURCES" + /// The AI is generating the message. + static let generating: Self = "AI_STATE_GENERATING" + /// There's an error with the message generation. + static let error: Self = "AI_STATE_ERROR" +} diff --git a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift index 1606ecc120f..bcf74c68819 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift @@ -36,8 +36,9 @@ class EventPayload: Decodable { case thread case vote = "poll_vote" case poll - case state + case aiState = "ai_state" case messageId = "message_id" + case aiMessage = "ai_message" } let eventType: EventType @@ -71,8 +72,9 @@ class EventPayload: Decodable { let threadDetails: Result? let threadPartial: Result? - let state: String? + let aiState: String? let messageId: String? + let aiMessage: String? init( eventType: EventType, @@ -102,8 +104,9 @@ class EventPayload: Decodable { threadPartial: Result? = nil, poll: PollPayload? = nil, vote: PollVotePayload? = nil, - state: String? = nil, - messageId: String? = nil + aiState: String? = nil, + messageId: String? = nil, + aiMessage: String? = nil ) { self.eventType = eventType self.connectionId = connectionId @@ -132,8 +135,9 @@ class EventPayload: Decodable { self.threadDetails = threadDetails self.poll = poll self.vote = vote - self.state = state + self.aiState = aiState self.messageId = messageId + self.aiMessage = aiMessage } required init(from decoder: Decoder) throws { @@ -167,8 +171,9 @@ class EventPayload: Decodable { threadPartial = container.decodeAsResultIfPresent(ThreadPartialPayload.self, forKey: .thread) vote = try container.decodeIfPresent(PollVotePayload.self, forKey: .vote) poll = try container.decodeIfPresent(PollPayload.self, forKey: .poll) - state = try container.decodeIfPresent(String.self, forKey: .state) + aiState = try container.decodeIfPresent(String.self, forKey: .aiState) messageId = try container.decodeIfPresent(String.self, forKey: .messageId) + aiMessage = try container.decodeIfPresent(String.self, forKey: .aiMessage) } func event() throws -> Event { diff --git a/Sources/StreamChat/WebSocketClient/Events/EventType.swift b/Sources/StreamChat/WebSocketClient/Events/EventType.swift index 1bbd0cf92a8..f3187cfa2d3 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventType.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventType.swift @@ -146,9 +146,14 @@ public extension EventType { // MARK: - AI - static let aiTypingIndicatorChanged: Self = "ai_indicator_changed" + // When an AI typing indicator's state has changed. + static let aiTypingIndicatorChanged: Self = "ai_indicator.update" - static let aiTypingIndicatorClear: Self = "ai_indicator_clear" + // When an AI typing indicator has been cleared. + static let aiTypingIndicatorClear: Self = "ai_indicator.clear" + + // When an AI typing indicator has been stopped. + static let aiTypingIndicatorStop: Self = "ai_indicator.stop" } extension EventType { @@ -214,8 +219,9 @@ extension EventType { case .pollVoteRemoved: return try PollVoteRemovedEventDTO(from: response) case .threadUpdated: return try ThreadUpdatedEventDTO(from: response) case .threadMessageNew: return try ThreadMessageNewEventDTO(from: response) - case .aiTypingIndicatorChanged: return try AITypingEventDTO(from: response) + case .aiTypingIndicatorChanged: return try AITypingUpdateEventDTO(from: response) case .aiTypingIndicatorClear: return try AIClearTypingEventDTO(from: response) + case .aiTypingIndicatorStop: return try AIStopTypingEventDTO(from: response) default: if response.cid == nil { throw ClientError.UnknownUserEvent(response.eventType) diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj index af187f6b193..f05ba17591d 100644 --- a/StreamChat.xcodeproj/project.pbxproj +++ b/StreamChat.xcodeproj/project.pbxproj @@ -814,6 +814,10 @@ 84DCB851269F4D31006CDF32 /* EventsController+Combine_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DCB850269F4D31006CDF32 /* EventsController+Combine_Tests.swift */; }; 84DCB853269F569A006CDF32 /* EventsController+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DCB852269F569A006CDF32 /* EventsController+SwiftUI.swift */; }; 84DCB855269F56A7006CDF32 /* EventsController+SwiftUI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DCB854269F56A7006CDF32 /* EventsController+SwiftUI_Tests.swift */; }; + 84E46A372CFA1B8E000CBDDE /* AIIndicatorClear.json in Resources */ = {isa = PBXBuildFile; fileRef = 84E46A342CFA1B8E000CBDDE /* AIIndicatorClear.json */; }; + 84E46A382CFA1B8E000CBDDE /* AIIndicatorStop.json in Resources */ = {isa = PBXBuildFile; fileRef = 84E46A352CFA1B8E000CBDDE /* AIIndicatorStop.json */; }; + 84E46A392CFA1B8E000CBDDE /* AIIndicatorUpdate.json in Resources */ = {isa = PBXBuildFile; fileRef = 84E46A362CFA1B8E000CBDDE /* AIIndicatorUpdate.json */; }; + 84E46A3B2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46A3A2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift */; }; 84EB4E76276A012900E47E73 /* ClientError_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EB4E75276A012900E47E73 /* ClientError_Tests.swift */; }; 84EB4E78276A03DE00E47E73 /* ErrorPayload_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EB4E77276A03DE00E47E73 /* ErrorPayload_Tests.swift */; }; 84EE53B12BBC32AD00FD2A13 /* Chat_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE53B02BBC32AD00FD2A13 /* Chat_Mock.swift */; }; @@ -3714,6 +3718,10 @@ 84DCB852269F569A006CDF32 /* EventsController+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventsController+SwiftUI.swift"; sourceTree = ""; }; 84DCB854269F56A7006CDF32 /* EventsController+SwiftUI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventsController+SwiftUI_Tests.swift"; sourceTree = ""; }; 84E32EA4276C9AB200A27112 /* InternetConnection_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetConnection_Mock.swift; sourceTree = ""; }; + 84E46A342CFA1B8E000CBDDE /* AIIndicatorClear.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AIIndicatorClear.json; sourceTree = ""; }; + 84E46A352CFA1B8E000CBDDE /* AIIndicatorStop.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AIIndicatorStop.json; sourceTree = ""; }; + 84E46A362CFA1B8E000CBDDE /* AIIndicatorUpdate.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AIIndicatorUpdate.json; sourceTree = ""; }; + 84E46A3A2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIIndicatorEvents_Tests.swift; sourceTree = ""; }; 84EB4E732769F76500E47E73 /* BackgroundTaskScheduler_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskScheduler_Mock.swift; sourceTree = ""; }; 84EB4E75276A012900E47E73 /* ClientError_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError_Tests.swift; sourceTree = ""; }; 84EB4E77276A03DE00E47E73 /* ErrorPayload_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPayload_Tests.swift; sourceTree = ""; }; @@ -6251,6 +6259,16 @@ path = Cells; sourceTree = ""; }; + 84E46A332CFA1B73000CBDDE /* AIIndicator */ = { + isa = PBXGroup; + children = ( + 84E46A342CFA1B8E000CBDDE /* AIIndicatorClear.json */, + 84E46A352CFA1B8E000CBDDE /* AIIndicatorStop.json */, + 84E46A362CFA1B8E000CBDDE /* AIIndicatorUpdate.json */, + ); + path = AIIndicator; + sourceTree = ""; + }; 84EE53AF2BBC329300FD2A13 /* State */ = { isa = PBXGroup; children = ( @@ -6547,6 +6565,7 @@ 8A62705F24BE31B20040BFD6 /* Events */ = { isa = PBXGroup; children = ( + 84E46A332CFA1B73000CBDDE /* AIIndicator */, ADE57B802C3C5C4600DD6B88 /* Thread */, 8A0C3BCA24C1C38C00CAFD19 /* Channel */, E7DB9F2526329C0C0090D9C7 /* HealthCheck */, @@ -6965,6 +6984,7 @@ 8A62705B24BE2BC00040BFD6 /* TypingEvent_Tests.swift */, 8A0C3BC824C0BBAB00CAFD19 /* UserEvents_Tests.swift */, ADE57B872C3C60CB00DD6B88 /* ThreadEvents_Tests.swift */, + 84E46A3A2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift */, ); path = Events; sourceTree = ""; @@ -10217,6 +10237,9 @@ A311B40327E8B9AD00CFCF6D /* NotificationInviteAccepted.json in Resources */, A311B3D327E8B98C00CFCF6D /* Message.json in Resources */, A311B3E427E8B98C00CFCF6D /* MessagePayloadWithCustom.json in Resources */, + 84E46A372CFA1B8E000CBDDE /* AIIndicatorClear.json in Resources */, + 84E46A382CFA1B8E000CBDDE /* AIIndicatorStop.json in Resources */, + 84E46A392CFA1B8E000CBDDE /* AIIndicatorUpdate.json in Resources */, A3D9D68727EDE3B900725066 /* yoda.jpg in Resources */, A311B42727E8B9CE00CFCF6D /* MessageReactionPayload+CustomExtraData.json in Resources */, A311B3E527E8B98C00CFCF6D /* MessageWithBrokenAttachments.json in Resources */, @@ -11708,6 +11731,7 @@ 84EB4E76276A012900E47E73 /* ClientError_Tests.swift in Sources */, DA8407232525E871005A0F62 /* UserListPayload_Tests.swift in Sources */, 437FCA1926D906B20000223C /* ChatPushNotificationContent_Tests.swift in Sources */, + 84E46A3B2CFA1BB9000CBDDE /* AIIndicatorEvents_Tests.swift in Sources */, F61D7C3124FF9D1F00188A0E /* MessageEndpoints_Tests.swift in Sources */, 8459C9EE2BFB673E00F0D235 /* PollVoteListController+Combine_Tests.swift in Sources */, 8836FFC325408210009FDF73 /* FlagUserPayload_Tests.swift in Sources */, diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json new file mode 100644 index 00000000000..d1fde4915ed --- /dev/null +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorClear.json @@ -0,0 +1,16 @@ +{ + "type": "ai_indicator.clear", + "cid": "messaging:general-a4ea1bed-f233-4021-b9f8-f9519367cefd", + "channel_id": "general-a4ea1bed-f233-4021-b9f8-f9519367cefd", + "channel_type": "messaging", + "user": { + "id": "ai-b076753a-830e-40b0-816d-0929bb73d7ce", + "role": "user", + "created_at": "2024-11-27T15:57:45.157276Z", + "updated_at": "2024-11-27T15:57:45.157276Z", + "last_active": "2024-11-27T15:57:45.177077Z", + "banned": false, + "online": true + }, + "created_at": "2024-11-27T15:57:45.32967Z" +} diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json new file mode 100644 index 00000000000..d15a4ef8e0e --- /dev/null +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorStop.json @@ -0,0 +1,16 @@ +{ + "type": "ai_indicator.stop", + "cid": "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89", + "channel_id": "general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89", + "channel_type": "messaging", + "user": { + "id": "ai-1e9666df-9b9e-429e-b7e4-e2f54446d5ac", + "role": "user", + "created_at": "2024-11-27T15:51:55.649597Z", + "updated_at": "2024-11-27T15:51:55.649597Z", + "last_active": "2024-11-27T15:51:55.668787Z", + "banned": false, + "online": true + }, + "created_at": "2024-11-27T15:51:55.803339Z" +} diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json new file mode 100644 index 00000000000..3545d48cdfc --- /dev/null +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/AIIndicator/AIIndicatorUpdate.json @@ -0,0 +1,19 @@ +{ + "type": "ai_indicator.update", + "cid": "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89", + "channel_id": "general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89", + "channel_type": "messaging", + "message_id": "aba120c6-c845-4c5a-968d-31ed0429c31e", + "ai_state": "AI_STATE_ERROR", + "ai_message": "failure", + "user": { + "id": "ai-1e9666df-9b9e-429e-b7e4-e2f54446d5ac", + "role": "user", + "created_at": "2024-11-27T15:51:55.649597Z", + "updated_at": "2024-11-27T15:51:55.649597Z", + "last_active": "2024-11-27T15:51:55.668787Z", + "banned": false, + "online": true + }, + "created_at": "2024-11-27T15:51:55.757904Z" +} diff --git a/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift b/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift new file mode 100644 index 00000000000..a847921490c --- /dev/null +++ b/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift @@ -0,0 +1,42 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +@testable import StreamChat +@testable import StreamChatTestTools +import XCTest + +final class AIIndicatorEvents_Tests: XCTestCase { + var eventDecoder: EventDecoder! + + override func setUp() { + super.setUp() + eventDecoder = EventDecoder() + } + + override func tearDown() { + super.tearDown() + eventDecoder = nil + } + + func test_aiIndicatorUpdate() throws { + let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorUpdate") + let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AITypingUpdateEventDTO) + XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89") + XCTAssertEqual(event.payload.messageId, "aba120c6-c845-4c5a-968d-31ed0429c31e") + XCTAssertEqual(event.payload.aiState, "AI_STATE_ERROR") + XCTAssertEqual(event.payload.aiMessage, "failure") + } + + func test_aiIndicatorClear() throws { + let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorClear") + let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIClearTypingEventDTO) + XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-a4ea1bed-f233-4021-b9f8-f9519367cefd") + } + + func test_aiIndicatorStop() throws { + let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorStop") + let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIStopTypingEventDTO) + XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89") + } +} From 11b1c1bb492766ba05d4526b0657d3406e7634fd Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Fri, 29 Nov 2024 17:15:50 +0100 Subject: [PATCH 07/10] Renamed the events --- .../Events/AITypingEvents.swift | 18 +++++++++--------- .../WebSocketClient/Events/EventType.swift | 6 +++--- .../Events/AIIndicatorEvents_Tests.swift | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 633a52d7aaf..9089d5cc1a1 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -5,7 +5,7 @@ import Foundation /// An event that provides updates about the state of the AI typing indicator. -public struct AITypingUpdateEvent: Event { +public struct AIIndicatorUpdateEvent: Event { /// The state of the AI typing indicator. public let state: AITypingState /// The channel ID this event is related to. @@ -16,7 +16,7 @@ public struct AITypingUpdateEvent: Event { public let message: String? } -class AITypingUpdateEventDTO: EventDTO { +class AIIndicatorUpdateEventDTO: EventDTO { let payload: EventPayload init(from response: EventPayload) throws { @@ -26,7 +26,7 @@ class AITypingUpdateEventDTO: EventDTO { func toDomainEvent(session: DatabaseSession) -> Event? { if let typingState = payload.aiState, let aiTypingState = AITypingState(rawValue: typingState) { - return AITypingUpdateEvent( + return AIIndicatorUpdateEvent( state: aiTypingState, cid: payload.cid, messageId: payload.messageId, @@ -39,12 +39,12 @@ class AITypingUpdateEventDTO: EventDTO { } /// An event that clears the AI typing indicator. -public struct AIClearTypingEvent: Event { +public struct AIIndicatorClearEvent: Event { /// The channel ID this event is related to. public let cid: ChannelId? } -class AIClearTypingEventDTO: EventDTO { +class AIIndicatorClearEventDTO: EventDTO { let payload: EventPayload init(from response: EventPayload) throws { @@ -52,17 +52,17 @@ class AIClearTypingEventDTO: EventDTO { } func toDomainEvent(session: any DatabaseSession) -> (any Event)? { - AIClearTypingEvent(cid: payload.cid) + AIIndicatorClearEvent(cid: payload.cid) } } /// An event that indicates the AI has stopped generating the message. -public struct AIStopTypingEvent: Event { +public struct AIIndicatorStopEvent: Event { /// The channel ID this event is related to. public let cid: ChannelId? } -class AIStopTypingEventDTO: EventDTO { +class AIIndicatorStopEventDTO: EventDTO { let payload: EventPayload init(from response: EventPayload) throws { @@ -70,7 +70,7 @@ class AIStopTypingEventDTO: EventDTO { } func toDomainEvent(session: any DatabaseSession) -> (any Event)? { - AIStopTypingEvent(cid: payload.cid) + AIIndicatorStopEvent(cid: payload.cid) } } diff --git a/Sources/StreamChat/WebSocketClient/Events/EventType.swift b/Sources/StreamChat/WebSocketClient/Events/EventType.swift index f3187cfa2d3..084e4606b66 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventType.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventType.swift @@ -219,9 +219,9 @@ extension EventType { case .pollVoteRemoved: return try PollVoteRemovedEventDTO(from: response) case .threadUpdated: return try ThreadUpdatedEventDTO(from: response) case .threadMessageNew: return try ThreadMessageNewEventDTO(from: response) - case .aiTypingIndicatorChanged: return try AITypingUpdateEventDTO(from: response) - case .aiTypingIndicatorClear: return try AIClearTypingEventDTO(from: response) - case .aiTypingIndicatorStop: return try AIStopTypingEventDTO(from: response) + case .aiTypingIndicatorChanged: return try AIIndicatorUpdateEventDTO(from: response) + case .aiTypingIndicatorClear: return try AIIndicatorClearEventDTO(from: response) + case .aiTypingIndicatorStop: return try AIIndicatorStopEventDTO(from: response) default: if response.cid == nil { throw ClientError.UnknownUserEvent(response.eventType) diff --git a/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift b/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift index a847921490c..5974c15f3e2 100644 --- a/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift +++ b/Tests/StreamChatTests/WebSocketClient/Events/AIIndicatorEvents_Tests.swift @@ -21,7 +21,7 @@ final class AIIndicatorEvents_Tests: XCTestCase { func test_aiIndicatorUpdate() throws { let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorUpdate") - let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AITypingUpdateEventDTO) + let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIIndicatorUpdateEventDTO) XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89") XCTAssertEqual(event.payload.messageId, "aba120c6-c845-4c5a-968d-31ed0429c31e") XCTAssertEqual(event.payload.aiState, "AI_STATE_ERROR") @@ -30,13 +30,13 @@ final class AIIndicatorEvents_Tests: XCTestCase { func test_aiIndicatorClear() throws { let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorClear") - let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIClearTypingEventDTO) + let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIIndicatorClearEventDTO) XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-a4ea1bed-f233-4021-b9f8-f9519367cefd") } func test_aiIndicatorStop() throws { let json = XCTestCase.mockData(fromJSONFile: "AIIndicatorStop") - let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIStopTypingEventDTO) + let event = try XCTUnwrap(try eventDecoder.decode(from: json) as? AIIndicatorStopEventDTO) XCTAssertEqual(event.payload.cid?.rawValue, "messaging:general-3ac667a1-6113-4b16-b1e3-50dbff0ffb89") } } From 0c95d74b6dbf9d40f90be94514c0ad901999ac08 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Mon, 2 Dec 2024 17:19:13 +0100 Subject: [PATCH 08/10] Updated the message property name --- .../StreamChat/WebSocketClient/Events/AITypingEvents.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 9089d5cc1a1..3f228737ee6 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -13,7 +13,7 @@ public struct AIIndicatorUpdateEvent: Event { /// The message ID this event is related to. public let messageId: MessageId? /// Optional server message, usually when an error occurs. - public let message: String? + public let aiMessage: String? } class AIIndicatorUpdateEventDTO: EventDTO { @@ -30,7 +30,7 @@ class AIIndicatorUpdateEventDTO: EventDTO { state: aiTypingState, cid: payload.cid, messageId: payload.messageId, - message: payload.aiMessage + aiMessage: payload.aiMessage ) } else { return nil From 132764c6a505babd6bda157b5471b17ef6e9ae2b Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Mon, 2 Dec 2024 17:36:31 +0100 Subject: [PATCH 09/10] Added public init for the stop event --- .../StreamChat/WebSocketClient/Events/AITypingEvents.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 3f228737ee6..279ac443b4b 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -60,6 +60,10 @@ class AIIndicatorClearEventDTO: EventDTO { public struct AIIndicatorStopEvent: Event { /// The channel ID this event is related to. public let cid: ChannelId? + + public init(cid: ChannelId?) { + self.cid = cid + } } class AIIndicatorStopEventDTO: EventDTO { From e29408b804b10c0a56524f27e70855cb86db717b Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Mon, 2 Dec 2024 17:39:25 +0100 Subject: [PATCH 10/10] Updated the stop event --- .../StreamChat/WebSocketClient/Events/AITypingEvents.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 279ac443b4b..2721c682eae 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -57,7 +57,9 @@ class AIIndicatorClearEventDTO: EventDTO { } /// An event that indicates the AI has stopped generating the message. -public struct AIIndicatorStopEvent: Event { +public struct AIIndicatorStopEvent: CustomEventPayload, Event { + public static var eventType: EventType = .aiTypingIndicatorStop + /// The channel ID this event is related to. public let cid: ChannelId?