Skip to content

Commit e973924

Browse files
committed
Support persisting tab order
1 parent 29240bc commit e973924

File tree

3 files changed

+138
-115
lines changed

3 files changed

+138
-115
lines changed

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 137 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

Core/Sources/SuggestionWidget/ChatWindowView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ struct ChatTabBar: View {
188188
}
189189
.id(info.id)
190190
.onDrag {
191-
print("dragging tab \(info.id)")
192191
draggingTabId = info.id
193192
return NSItemProvider(object: info.id as NSString)
194193
}

Pro

Submodule Pro updated from 71f5088 to d106b3b

0 commit comments

Comments
 (0)