@@ -31,12 +31,17 @@ struct GUI: ReducerProtocol {
3131 }
3232
3333 #if canImport(ChatTabPersistent)
34+ var isChatTabRestoreFinished : Bool = false
3435 var persistentState : ChatTabPersistent . State {
3536 get {
36- . init( chatTabInfo: chatTabGroup. tabInfo)
37+ . init(
38+ chatTabInfo: chatTabGroup. tabInfo,
39+ isRestoreFinished: isChatTabRestoreFinished
40+ )
3741 }
3842 set {
3943 chatTabGroup. tabInfo = newValue. chatTabInfo
44+ isChatTabRestoreFinished = newValue. isRestoreFinished
4045 }
4146 }
4247 #endif
@@ -60,148 +65,167 @@ struct GUI: ReducerProtocol {
6065 }
6166
6267 @Dependency ( \. chatTabPool) var chatTabPool : ChatTabPool
68+
69+ public enum Debounce : Hashable {
70+ case updateChatTabOrder
71+ }
6372
6473 var body : some ReducerProtocol < State , Action > {
65- Scope ( state: \. suggestionWidgetState, action: / Action. suggestionWidget) {
66- WidgetFeature ( )
67- }
74+ CombineReducers {
75+ Scope ( state: \. suggestionWidgetState, action: / Action. suggestionWidget) {
76+ WidgetFeature ( )
77+ }
6878
69- Scope (
70- state: \. chatTabGroup,
71- action: / Action. suggestionWidget . . / WidgetFeature . Action. chatPanel
72- ) {
73- Reduce { _, action in
74- switch action {
75- case let . createNewTapButtonClicked( kind) :
76- return . run { send in
77- if let ( _, chatTabInfo) = await chatTabPool. createTab ( for: kind) {
78- await send ( . appendAndSelectTab( chatTabInfo) )
79+ Scope (
80+ state: \. chatTabGroup,
81+ action: / Action. suggestionWidget . . / WidgetFeature . Action. chatPanel
82+ ) {
83+ Reduce { _, action in
84+ switch action {
85+ case let . createNewTapButtonClicked( kind) :
86+ return . run { send in
87+ if let ( _, chatTabInfo) = await chatTabPool. createTab ( for: kind) {
88+ await send ( . appendAndSelectTab( chatTabInfo) )
89+ }
7990 }
80- }
8191
82- case let . closeTabButtonClicked( id) :
83- return . run { _ in
84- chatTabPool. removeTab ( of: id)
85- }
92+ case let . closeTabButtonClicked( id) :
93+ return . run { _ in
94+ chatTabPool. removeTab ( of: id)
95+ }
8696
87- case let . chatTab( _, . openNewTab( builder) ) :
88- return . run { send in
89- if let ( _, chatTabInfo) = await chatTabPool
90- . createTab ( from: builder. chatTabBuilder)
91- {
92- await send ( . appendAndSelectTab( chatTabInfo) )
97+ case let . chatTab( _, . openNewTab( builder) ) :
98+ return . run { send in
99+ if let ( _, chatTabInfo) = await chatTabPool
100+ . createTab ( from: builder. chatTabBuilder)
101+ {
102+ await send ( . appendAndSelectTab( chatTabInfo) )
103+ }
93104 }
94- }
95105
96- default :
97- return . none
106+ default :
107+ return . none
108+ }
98109 }
99110 }
100- }
101111
102- #if canImport(ChatTabPersistent)
103- Scope ( state: \. persistentState, action: / Action. persistent) {
104- ChatTabPersistent ( )
105- }
106- #endif
112+ #if canImport(ChatTabPersistent)
113+ Scope ( state: \. persistentState, action: / Action. persistent) {
114+ ChatTabPersistent ( )
115+ }
116+ #endif
107117
108- Reduce { state, action in
109- switch action {
110- case . start:
111- #if canImport(ChatTabPersistent)
112- return . run { send in
113- await send ( . persistent( . restoreChatTabs) )
114- }
115- #else
116- return . none
117- #endif
118+ Reduce { state, action in
119+ switch action {
120+ case . start:
121+ #if canImport(ChatTabPersistent)
122+ return . run { send in
123+ await send ( . persistent( . restoreChatTabs) )
124+ }
125+ #else
126+ return . none
127+ #endif
118128
119- case let . openChatPanel( forceDetach) :
120- return . run { send in
121- await send (
122- . suggestionWidget( . chatPanel( . presentChatPanel( forceDetach: forceDetach) ) )
123- )
124- }
129+ case let . openChatPanel( forceDetach) :
130+ return . run { send in
131+ await send (
132+ . suggestionWidget(
133+ . chatPanel( . presentChatPanel( forceDetach: forceDetach) )
134+ )
135+ )
136+ }
125137
126- case . createChatGPTChatTabIfNeeded:
127- if state. chatTabGroup. tabInfo. contains ( where: {
128- chatTabPool. getTab ( of: $0. id) is ChatGPTChatTab
129- } ) {
130- return . none
131- }
132- return . run { send in
133- if let ( _, chatTabInfo) = await chatTabPool. createTab ( for: nil ) {
134- await send ( . suggestionWidget( . chatPanel( . appendAndSelectTab( chatTabInfo) ) ) )
138+ case . createChatGPTChatTabIfNeeded:
139+ if state. chatTabGroup. tabInfo. contains ( where: {
140+ chatTabPool. getTab ( of: $0. id) is ChatGPTChatTab
141+ } ) {
142+ return . none
143+ }
144+ return . run { send in
145+ if let ( _, chatTabInfo) = await chatTabPool. createTab ( for: nil ) {
146+ await send (
147+ . suggestionWidget( . chatPanel( . appendAndSelectTab( chatTabInfo) ) )
148+ )
149+ }
135150 }
136- }
137151
138- case let . sendCustomCommandToActiveChat( command) :
139- @Sendable func stopAndHandleCommand( _ tab: ChatGPTChatTab ) async {
140- if tab. service. isReceivingMessage {
141- await tab. service. stopReceivingMessage ( )
152+ case let . sendCustomCommandToActiveChat( command) :
153+ @Sendable func stopAndHandleCommand( _ tab: ChatGPTChatTab ) async {
154+ if tab. service. isReceivingMessage {
155+ await tab. service. stopReceivingMessage ( )
156+ }
157+ try ? await tab. service. handleCustomCommand ( command)
142158 }
143- try ? await tab. service. handleCustomCommand ( command)
144- }
145159
146- if let info = state. chatTabGroup. selectedTabInfo,
147- let activeTab = chatTabPool. getTab ( of: info. id) as? ChatGPTChatTab
148- {
149- return . run { send in
150- await send ( . openChatPanel( forceDetach: false ) )
151- await stopAndHandleCommand ( activeTab)
160+ if let info = state. chatTabGroup. selectedTabInfo,
161+ let activeTab = chatTabPool. getTab ( of: info. id) as? ChatGPTChatTab
162+ {
163+ return . run { send in
164+ await send ( . openChatPanel( forceDetach: false ) )
165+ await stopAndHandleCommand ( activeTab)
166+ }
167+ }
168+
169+ if let info = state. chatTabGroup. tabInfo. first ( where: {
170+ chatTabPool. getTab ( of: $0. id) is ChatGPTChatTab
171+ } ) ,
172+ let chatTab = chatTabPool. getTab ( of: info. id) as? ChatGPTChatTab
173+ {
174+ state. chatTabGroup. selectedTabId = chatTab. id
175+ return . run { send in
176+ await send ( . openChatPanel( forceDetach: false ) )
177+ await stopAndHandleCommand ( chatTab)
178+ }
152179 }
153- }
154180
155- if let info = state. chatTabGroup. tabInfo. first ( where: {
156- chatTabPool. getTab ( of: $0. id) is ChatGPTChatTab
157- } ) ,
158- let chatTab = chatTabPool. getTab ( of: info. id) as? ChatGPTChatTab
159- {
160- state. chatTabGroup. selectedTabId = chatTab. id
161181 return . run { send in
182+ guard let ( chatTab, chatTabInfo) = await chatTabPool. createTab ( for: nil )
183+ else {
184+ return
185+ }
186+ await send ( . suggestionWidget( . chatPanel( . appendAndSelectTab( chatTabInfo) ) ) )
162187 await send ( . openChatPanel( forceDetach: false ) )
163- await stopAndHandleCommand ( chatTab)
188+ if let chatTab = chatTab as? ChatGPTChatTab {
189+ await stopAndHandleCommand ( chatTab)
190+ }
164191 }
165- }
166192
167- return . run { send in
168- guard let ( chatTab, chatTabInfo) = await chatTabPool. createTab ( for: nil ) else {
169- return
193+ case let . suggestionWidget( . chatPanel( . chatTab( id, . tabContentUpdated) ) ) :
194+ #if canImport(ChatTabPersistent)
195+ // when a tab is updated, persist it.
196+ return . run { send in
197+ await send ( . persistent( . chatTabUpdated( id: id) ) )
170198 }
171- await send ( . suggestionWidget( . chatPanel( . appendAndSelectTab( chatTabInfo) ) ) )
172- await send ( . openChatPanel( forceDetach: false ) )
173- if let chatTab = chatTab as? ChatGPTChatTab {
174- await stopAndHandleCommand ( chatTab)
199+ #else
200+ return . none
201+ #endif
202+
203+ case let . suggestionWidget( . chatPanel( . closeTabButtonClicked( id) ) ) :
204+ #if canImport(ChatTabPersistent)
205+ // when a tab is closed, remove it from persistence.
206+ return . run { send in
207+ await send ( . persistent( . chatTabClosed( id: id) ) )
175208 }
176- }
209+ #else
210+ return . none
211+ #endif
177212
178- case let . suggestionWidget( . chatPanel( . chatTab( id, . tabContentUpdated) ) ) :
179- #if canImport(ChatTabPersistent)
180- // when a tab is updated, persist it.
181- return . run { send in
182- await send ( . persistent( . chatTabUpdated( id: id) ) )
183- }
184- #else
185- return . none
186- #endif
213+ case . suggestionWidget:
214+ return . none
187215
188- case let . suggestionWidget( . chatPanel( . closeTabButtonClicked( id) ) ) :
189216 #if canImport(ChatTabPersistent)
190- // when a tab is closed, remove it from persistence.
191- return . run { send in
192- await send ( . persistent( . chatTabClosed( id: id) ) )
193- }
194- #else
195- return . none
217+ case . persistent:
218+ return . none
196219 #endif
197-
198- case . suggestionWidget:
199- return . none
200-
201- #if canImport(ChatTabPersistent)
202- case . persistent:
203- return . none
204- #endif
220+ }
221+ }
222+ } . onChange ( of: \. chatTabGroup. tabInfo) { _, _ in
223+ Reduce { _, _ in
224+ . run { send in
225+ #if canImport(ChatTabPersistent)
226+ await send ( . persistent( . chatOrderChanged) )
227+ #endif
228+ } . debounce ( id: Debounce . updateChatTabOrder, for: 1 , scheduler: DispatchQueue . main)
205229 }
206230 }
207231 }
0 commit comments