Skip to content

Commit 5a3db20

Browse files
committed
Support activating selecting chat tab in various situations
1 parent 4b1271a commit 5a3db20

7 files changed

Lines changed: 57 additions & 14 deletions

File tree

Core/Sources/ChatGPTChatTab/Chat.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ struct Chat: ReducerProtocol {
3232
var history: [ChatMessage] = []
3333
@BindingState var isReceivingMessage = false
3434
var chatMenu = ChatMenu.State()
35+
@BindingState var focusedField: Field? = .textField
36+
37+
enum Field: String, Hashable {
38+
case textField
39+
}
3540
}
3641

3742
enum Action: Equatable, BindableAction {
@@ -45,6 +50,7 @@ struct Chat: ReducerProtocol {
4550
case deleteMessageButtonTapped(MessageID)
4651
case resendMessageButtonTapped(MessageID)
4752
case setAsExtraPromptButtonTapped(MessageID)
53+
case focusOnTextField
4854

4955
case observeChatService
5056
case observeHistoryChange
@@ -127,6 +133,10 @@ struct Chat: ReducerProtocol {
127133
return .run { _ in
128134
await service.setMessageAsExtraPrompt(id: id)
129135
}
136+
137+
case .focusOnTextField:
138+
state.focusedField = .textField
139+
return .none
130140

131141
case .observeChatService:
132142
return .run { send in

Core/Sources/ChatGPTChatTab/ChatGPTChatTab.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ public class ChatGPTChatTab: ChatTab {
116116
public func start() {
117117
chatTabViewStore.send(.updateTitle("Chat"))
118118

119+
chatTabViewStore.publisher.focusTrigger.removeDuplicates().sink { [weak self] _ in
120+
Task { @MainActor [weak self] in
121+
self?.viewStore.send(.focusOnTextField)
122+
}
123+
}.store(in: &cancellable)
124+
119125
service.$systemPrompt.removeDuplicates().sink { _ in
120126
Task { @MainActor [weak self] in
121127
self?.chatTabViewStore.send(.tabContentUpdated)

Core/Sources/ChatGPTChatTab/ChatPanel.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -498,16 +498,13 @@ struct FunctionMessage: View {
498498

499499
struct ChatPanelInputArea: View {
500500
let chat: StoreOf<Chat>
501-
@FocusState var isInputAreaFocused: Bool
501+
@FocusState var focusedField: Chat.State.Field?
502502

503503
var body: some View {
504504
HStack {
505505
clearButton
506506
textEditor
507507
}
508-
.onAppear {
509-
isInputAreaFocused = true
510-
}
511508
.padding(8)
512509
.background(.ultraThickMaterial)
513510
}
@@ -538,8 +535,11 @@ struct ChatPanelInputArea: View {
538535
@MainActor
539536
var textEditor: some View {
540537
HStack(spacing: 0) {
541-
WithViewStore(chat, removeDuplicates: { $0.typedMessage == $1.typedMessage }) {
542-
viewStore in
538+
WithViewStore(
539+
chat,
540+
removeDuplicates: {
541+
$0.typedMessage == $1.typedMessage && $0.focusedField == $1.focusedField
542+
}) { viewStore in
543543
ZStack(alignment: .center) {
544544
// a hack to support dynamic height of TextEditor
545545
Text(
@@ -560,7 +560,8 @@ struct ChatPanelInputArea: View {
560560
.padding(.top, 1)
561561
.padding(.bottom, -1)
562562
}
563-
.focused($isInputAreaFocused)
563+
.focused($focusedField, equals: .textField)
564+
.bind(viewStore.$focusedField, to: $focusedField)
564565
.padding(8)
565566
.fixedSize(horizontal: false, vertical: true)
566567
}
@@ -595,7 +596,7 @@ struct ChatPanelInputArea: View {
595596
.keyboardShortcut(KeyEquivalent.return, modifiers: [.shift])
596597

597598
Button(action: {
598-
isInputAreaFocused = true
599+
focusedField = .textField
599600
}) {
600601
EmptyView()
601602
}

Core/Sources/SuggestionWidget/FeatureReducers/ChatPanelFeature.swift

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public struct ChatPanelFeature: ReducerProtocol {
7070
case switchToNextTab
7171
case switchToPreviousTab
7272
case moveChatTab(from: Int, to: Int)
73+
case focusActiveChatTab
7374

7475
case chatTab(id: String, action: ChatTabItem.Action)
7576
}
@@ -117,8 +118,9 @@ public struct ChatPanelFeature: ReducerProtocol {
117118
state.chatPanelInASeparateWindow = true
118119
}
119120
state.isPanelDisplayed = true
120-
return .run { _ in
121+
return .run { send in
121122
await activateExtensionService()
123+
await send(.focusActiveChatTab)
122124
}
123125

124126
case let .updateChatTabInfo(chatTabInfo):
@@ -172,14 +174,18 @@ public struct ChatPanelFeature: ReducerProtocol {
172174
return .none
173175
}
174176
state.chatTabGroup.selectedTabId = id
175-
return .none
177+
return .run { send in
178+
await send(.focusActiveChatTab)
179+
}
176180

177181
case let .appendAndSelectTab(tab):
178182
guard !state.chatTabGroup.tabInfo.contains(where: { $0.id == tab.id })
179183
else { return .none }
180184
state.chatTabGroup.tabInfo.append(tab)
181185
state.chatTabGroup.selectedTabId = tab.id
182-
return .none
186+
return .run { send in
187+
await send(.focusActiveChatTab)
188+
}
183189

184190
case .switchToNextTab:
185191
let selectedId = state.chatTabGroup.selectedTabId
@@ -192,7 +198,9 @@ public struct ChatPanelFeature: ReducerProtocol {
192198
}
193199
let targetId = state.chatTabGroup.tabInfo[nextIndex].id
194200
state.chatTabGroup.selectedTabId = targetId
195-
return .none
201+
return .run { send in
202+
await send(.focusActiveChatTab)
203+
}
196204

197205
case .switchToPreviousTab:
198206
let selectedId = state.chatTabGroup.selectedTabId
@@ -205,7 +213,9 @@ public struct ChatPanelFeature: ReducerProtocol {
205213
}
206214
let targetId = state.chatTabGroup.tabInfo[previousIndex].id
207215
state.chatTabGroup.selectedTabId = targetId
208-
return .none
216+
return .run { send in
217+
await send(.focusActiveChatTab)
218+
}
209219

210220
case let .moveChatTab(from, to):
211221
guard from >= 0, from < state.chatTabGroup.tabInfo.endIndex, to >= 0,
@@ -217,6 +227,13 @@ public struct ChatPanelFeature: ReducerProtocol {
217227
state.chatTabGroup.tabInfo.remove(at: from)
218228
state.chatTabGroup.tabInfo.insert(tab, at: to)
219229
return .none
230+
231+
case .focusActiveChatTab:
232+
let id = state.chatTabGroup.selectedTabInfo?.id
233+
guard let id else { return .none }
234+
return .run { send in
235+
await send(.chatTab(id: id, action: .focus))
236+
}
220237

221238
case let .chatTab(id, .close):
222239
return .run { send in

Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,11 @@ public struct WidgetFeature: ReducerProtocol {
164164
state.chatPanelState.isPanelDisplayed = true
165165
}
166166
let isDisplayingContent = state._circularWidgetState.isDisplayingContent
167-
return .run { _ in
167+
return .run { send in
168+
if isDisplayingContent {
169+
await send(.chatPanel(.focusActiveChatTab))
170+
}
171+
168172
if isDisplayingContent, !(await NSApplication.shared.isActive) {
169173
try await Task.sleep(nanoseconds: 50_000_000)
170174
await NSApplication.shared.activate(ignoringOtherApps: true)

Tool/Sources/ChatTab/ChatTab.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import SwiftUI
66
public struct ChatTabInfo: Identifiable, Equatable {
77
public var id: String
88
public var title: String
9+
public var focusTrigger: Int = 0
910

1011
public init(id: String, title: String) {
1112
self.id = id

Tool/Sources/ChatTab/ChatTabItem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public struct ChatTabItem: ReducerProtocol {
2121
case openNewTab(AnyChatTabBuilder)
2222
case tabContentUpdated
2323
case close
24+
case focus
2425
}
2526

2627
public init() {}
@@ -37,6 +38,9 @@ public struct ChatTabItem: ReducerProtocol {
3738
return .none
3839
case .close:
3940
return .none
41+
case .focus:
42+
state.focusTrigger += 1
43+
return .none
4044
}
4145
}
4246
}

0 commit comments

Comments
 (0)