Skip to content

Commit d620115

Browse files
committed
Support Codeium tab open chat mode
1 parent 413b9ac commit d620115

File tree

6 files changed

+110
-96
lines changed

6 files changed

+110
-96
lines changed

Core/Sources/HostApp/FeatureSettings/Chat/ChatSettingsGeneralSectionView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,16 @@ struct ChatSettingsGeneralSectionView: View {
6565
Text("Open chat panel").tag(mode)
6666
case .browser:
6767
Text("Open web page in browser").tag(mode)
68+
case .codeiumChat:
69+
Text("Open Codeium chat tab (beta)").tag(mode)
6870
}
6971
}
7072
}
7173

7274
if settings.openChatMode == .browser {
7375
TextField(
7476
"Chat web page URL",
75-
text: $settings.openChatInBrowserURL,
77+
text: $settings.openChatInBrowserURL,
7678
prompt: Text("https://")
7779
)
7880
.textFieldStyle(.roundedBorder)

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ struct GUI {
5656
case start
5757
case openChatPanel(forceDetach: Bool)
5858
case createAndSwitchToChatGPTChatTabIfNeeded
59-
case createAndSwitchToBrowserTabIfNeeded(url: URL)
59+
case createAndSwitchToChatTabIfNeededMatching(
60+
check: (any ChatTab) -> Bool,
61+
kind: ChatTabKind?
62+
)
6063
case sendCustomCommandToActiveChat(CustomCommand)
6164
case toggleWidgetsHotkeyPressed
6265

@@ -147,77 +150,42 @@ struct GUI {
147150
}
148151

149152
case .createAndSwitchToChatGPTChatTabIfNeeded:
150-
if let selectedTabInfo = state.chatTabGroup.selectedTabInfo,
151-
chatTabPool.getTab(of: selectedTabInfo.id) is ChatGPTChatTab
152-
{
153-
// Already in ChatGPT tab
154-
return .none
155-
}
156-
157-
if let firstChatGPTTabInfo = state.chatTabGroup.tabInfo.first(where: {
158-
chatTabPool.getTab(of: $0.id) is ChatGPTChatTab
159-
}) {
160-
return .run { send in
161-
await send(.suggestionWidget(.chatPanel(.tabClicked(
162-
id: firstChatGPTTabInfo.id
163-
))))
164-
}
165-
}
166153
return .run { send in
167-
if let (_, chatTabInfo) = await chatTabPool.createTab(for: nil) {
168-
await send(
169-
.suggestionWidget(.chatPanel(.appendAndSelectTab(chatTabInfo)))
170-
)
171-
}
172-
}
173-
174-
case let .createAndSwitchToBrowserTabIfNeeded(url):
175-
#if canImport(BrowserChatTab)
176-
func match(_ tabURL: URL?) -> Bool {
177-
guard let tabURL else { return false }
178-
return tabURL == url
179-
|| tabURL.absoluteString.hasPrefix(url.absoluteString)
154+
await send(.createAndSwitchToChatTabIfNeededMatching(
155+
check: { $0 is ChatGPTChatTab },
156+
kind: nil
157+
))
180158
}
181159

160+
case let .createAndSwitchToChatTabIfNeededMatching(check, kind):
182161
if let selectedTabInfo = state.chatTabGroup.selectedTabInfo,
183-
let tab = chatTabPool.getTab(of: selectedTabInfo.id) as? BrowserChatTab,
184-
match(tab.url)
162+
let tab = chatTabPool.getTab(of: selectedTabInfo.id),
163+
check(tab)
185164
{
186-
// Already in the target Browser tab
165+
// Already in ChatGPT tab
187166
return .none
188167
}
189168

190169
if let firstChatGPTTabInfo = state.chatTabGroup.tabInfo.first(where: {
191-
guard let tab = chatTabPool.getTab(of: $0.id) as? BrowserChatTab,
192-
match(tab.url)
193-
else { return false }
194-
return true
170+
if let tab = chatTabPool.getTab(of: $0.id) {
171+
return check(tab)
172+
}
173+
return false
195174
}) {
196175
return .run { send in
197176
await send(.suggestionWidget(.chatPanel(.tabClicked(
198177
id: firstChatGPTTabInfo.id
199178
))))
200179
}
201180
}
202-
203181
return .run { send in
204-
if let (_, chatTabInfo) = await chatTabPool.createTab(
205-
for: .init(BrowserChatTab.urlChatBuilder(
206-
url: url,
207-
externalDependency: ChatTabFactory
208-
.externalDependenciesForBrowserChatTab()
209-
))
210-
) {
182+
if let (_, chatTabInfo) = await chatTabPool.createTab(for: kind) {
211183
await send(
212184
.suggestionWidget(.chatPanel(.appendAndSelectTab(chatTabInfo)))
213185
)
214186
}
215187
}
216188

217-
#else
218-
return .none
219-
#endif
220-
221189
case let .sendCustomCommandToActiveChat(command):
222190
@Sendable func stopAndHandleCommand(_ tab: ChatGPTChatTab) async {
223191
if tab.service.isReceivingMessage {

Core/Sources/Service/SuggestionCommandHandler/PseudoCommandHandler.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ActiveApplicationMonitor
22
import AppKit
3+
import CodeiumService
34
import Dependencies
45
import PlusFeatureFlag
56
import Preferences
@@ -11,6 +12,10 @@ import WorkspaceSuggestionService
1112
import XcodeInspector
1213
import XPCShared
1314

15+
#if canImport(BrowserChatTab)
16+
import BrowserChatTab
17+
#endif
18+
1419
/// It's used to run some commands without really triggering the menu bar item.
1520
///
1621
/// For example, we can use it to generate real-time suggestions without Apple Scripts.
@@ -342,17 +347,47 @@ struct PseudoCommandHandler {
342347
}
343348

344349
if openInApp {
350+
#if canImport(BrowserChatTab)
345351
let store = Service.shared.guiController.store
346352
Task { @MainActor in
347-
await store.send(.createAndSwitchToBrowserTabIfNeeded(url: url)).finish()
353+
await store.send(.createAndSwitchToChatTabIfNeededMatching(
354+
check: {
355+
func match(_ tabURL: URL?) -> Bool {
356+
guard let tabURL else { return false }
357+
return tabURL == url
358+
|| tabURL.absoluteString.hasPrefix(url.absoluteString)
359+
}
360+
361+
guard let tab = $0 as? BrowserChatTab,
362+
match(tab.url) else { return false }
363+
return true
364+
},
365+
kind: .init(BrowserChatTab.urlChatBuilder(
366+
url: url,
367+
externalDependency: ChatTabFactory
368+
.externalDependenciesForBrowserChatTab()
369+
))
370+
)).finish()
348371
store.send(.openChatPanel(forceDetach: forceDetach))
349372
}
373+
#endif
350374
} else {
351375
Task {
352376
@Dependency(\.openURL) var openURL
353377
await openURL(url)
354378
}
355379
}
380+
case .codeiumChat:
381+
let store = Service.shared.guiController.store
382+
Task { @MainActor in
383+
await store.send(
384+
.createAndSwitchToChatTabIfNeededMatching(
385+
check: { $0 is CodeiumChatTab },
386+
kind: .init(CodeiumChatTab.defaultChatBuilder())
387+
)
388+
).finish()
389+
store.send(.openChatPanel(forceDetach: forceDetach))
390+
}
356391
}
357392
}
358393
}

Core/Sources/SuggestionWidget/ChatWindowView.swift

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -174,34 +174,36 @@ struct ChatTabBar: View {
174174
ScrollView(.horizontal) {
175175
HStack(spacing: 0) {
176176
ForEach(tabInfo, id: \.id) { info in
177-
if let tab = chatTabPool.getTab(of: info.id) {
178-
ChatTabBarButton(
179-
store: store,
180-
info: info,
181-
content: { tab.tabItem },
182-
icon: { tab.icon },
183-
isSelected: info.id == selectedTabId
184-
)
185-
.contextMenu {
186-
tab.menu
187-
}
188-
.id(info.id)
189-
.onDrag {
190-
draggingTabId = info.id
191-
return NSItemProvider(object: info.id as NSString)
192-
}
193-
.onDrop(
194-
of: [.text],
195-
delegate: ChatTabBarDropDelegate(
177+
WithPerceptionTracking {
178+
if let tab = chatTabPool.getTab(of: info.id) {
179+
ChatTabBarButton(
196180
store: store,
197-
tabs: tabInfo,
198-
itemId: info.id,
199-
draggingTabId: $draggingTabId
181+
info: info,
182+
content: { tab.tabItem },
183+
icon: { tab.icon },
184+
isSelected: info.id == selectedTabId
200185
)
201-
)
202-
203-
} else {
204-
EmptyView()
186+
.contextMenu {
187+
tab.menu
188+
}
189+
.id(info.id)
190+
.onDrag {
191+
draggingTabId = info.id
192+
return NSItemProvider(object: info.id as NSString)
193+
}
194+
.onDrop(
195+
of: [.text],
196+
delegate: ChatTabBarDropDelegate(
197+
store: store,
198+
tabs: tabInfo,
199+
itemId: info.id,
200+
draggingTabId: $draggingTabId
201+
)
202+
)
203+
204+
} else {
205+
EmptyView()
206+
}
205207
}
206208
}
207209
}
@@ -225,26 +227,28 @@ struct ChatTabBar: View {
225227
let collection = store.chatTabGroup.tabCollection
226228
Menu {
227229
ForEach(0..<collection.endIndex, id: \.self) { index in
228-
switch collection[index] {
229-
case let .kind(kind):
230-
Button(action: {
231-
store.send(.createNewTapButtonClicked(kind: kind))
232-
}) {
233-
Text(kind.title)
234-
}.disabled(kind.builder is DisabledChatTabBuilder)
235-
case let .folder(title, list):
236-
Menu {
237-
ForEach(0..<list.endIndex, id: \.self) { index in
238-
Button(action: {
239-
store.send(
240-
.createNewTapButtonClicked(kind: list[index])
241-
)
242-
}) {
243-
Text(list[index].title)
230+
WithPerceptionTracking {
231+
switch collection[index] {
232+
case let .kind(kind):
233+
Button(action: {
234+
store.send(.createNewTapButtonClicked(kind: kind))
235+
}) {
236+
Text(kind.title)
237+
}.disabled(kind.builder is DisabledChatTabBuilder)
238+
case let .folder(title, list):
239+
Menu {
240+
ForEach(0..<list.endIndex, id: \.self) { index in
241+
Button(action: {
242+
store.send(
243+
.createNewTapButtonClicked(kind: list[index])
244+
)
245+
}) {
246+
Text(list[index].title)
247+
}
244248
}
249+
} label: {
250+
Text(title)
245251
}
246-
} label: {
247-
Text(title)
248252
}
249253
}
250254
}

Tool/Sources/CodeiumService/ChatTab/CodeiumChatTab.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ public class CodeiumChatTab: ChatTab {
142142
}
143143

144144
public static func chatBuilders(externalDependency: Void) -> [ChatTabBuilder] {
145-
[Builder(title: "Codeium Chat (Beta)")]
145+
[defaultChatBuilder()]
146+
}
147+
148+
public static func defaultChatBuilder() -> ChatTabBuilder {
149+
Builder(title: "Codeium Chat (Beta)")
146150
}
147151
}
148152

Tool/Sources/Preferences/Types/OpenChatMode.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import Foundation
33
public enum OpenChatMode: String, CaseIterable {
44
case chatPanel
55
case browser
6+
case codeiumChat
67
}

0 commit comments

Comments
 (0)