Skip to content

Commit 67a9eb0

Browse files
committed
Merge branch 'feature/search-scope' into develop
2 parents a9f3f48 + 5f71a97 commit 67a9eb0

24 files changed

Lines changed: 615 additions & 208 deletions

Core/Sources/ChatContextCollectors/SystemInfoChatContextCollector/SystemInfoChatContextCollector.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public final class SystemInfoChatContextCollector: ChatContextCollector {
1313

1414
public func generateContext(
1515
history: [ChatMessage],
16-
scopes: Set<String>,
16+
scopes: Set<ChatContext.Scope>,
1717
content: String,
1818
configuration: ChatGPTConfiguration
1919
) -> ChatContext {

Core/Sources/ChatContextCollectors/WebChatContextCollector/WebChatContextCollector.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ public final class WebChatContextCollector: ChatContextCollector {
99

1010
public func generateContext(
1111
history: [ChatMessage],
12-
scopes: Set<String>,
12+
scopes: Set<ChatContext.Scope>,
1313
content: String,
1414
configuration: ChatGPTConfiguration
1515
) -> ChatContext {
16-
guard scopes.contains("web") || scopes.contains("w") else { return .empty }
16+
guard scopes.contains(.web) else { return .empty }
1717
let links = Self.detectLinks(from: history) + Self.detectLinks(from: content)
1818
let functions: [(any ChatGPTFunction)?] = [
1919
SearchFunction(maxTokens: configuration.maxTokens),

Core/Sources/ChatGPTChatTab/Chat.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ struct Chat: ReducerProtocol {
5151
case observeIsReceivingMessageChange
5252
case observeSystemPromptChange
5353
case observeExtraSystemPromptChange
54+
case observeDefaultScopesChange
5455

5556
case historyChanged
5657
case isReceivingMessageChanged
5758
case systemPromptChanged
5859
case extraSystemPromptChanged
60+
case defaultScopesChanged
5961

6062
case chatMenu(ChatMenu.Action)
6163
}
@@ -68,6 +70,7 @@ struct Chat: ReducerProtocol {
6870
case observeIsReceivingMessageChange(UUID)
6971
case observeSystemPromptChange(UUID)
7072
case observeExtraSystemPromptChange(UUID)
73+
case observeDefaultScopesChange(UUID)
7174
}
7275

7376
var body: some ReducerProtocol<State, Action> {
@@ -131,6 +134,7 @@ struct Chat: ReducerProtocol {
131134
await send(.observeIsReceivingMessageChange)
132135
await send(.observeSystemPromptChange)
133136
await send(.observeExtraSystemPromptChange)
137+
await send(.observeDefaultScopesChange)
134138
}
135139

136140
case .observeHistoryChange:
@@ -198,6 +202,22 @@ struct Chat: ReducerProtocol {
198202
}
199203
}.cancellable(id: CancelID.observeExtraSystemPromptChange(id), cancelInFlight: true)
200204

205+
case .observeDefaultScopesChange:
206+
return .run { send in
207+
let stream = AsyncStream<Void> { continuation in
208+
let cancellable = service.$defaultScopes
209+
.sink { _ in
210+
continuation.yield()
211+
}
212+
continuation.onTermination = { _ in
213+
cancellable.cancel()
214+
}
215+
}
216+
for await _ in stream {
217+
await send(.defaultScopesChanged)
218+
}
219+
}.cancellable(id: CancelID.observeDefaultScopesChange(id), cancelInFlight: true)
220+
201221
case .historyChanged:
202222
state.history = service.chatHistory.map { message in
203223
.init(
@@ -250,6 +270,10 @@ struct Chat: ReducerProtocol {
250270
state.chatMenu.extraSystemPrompt = service.extraSystemPrompt
251271
return .none
252272

273+
case .defaultScopesChanged:
274+
state.chatMenu.defaultScopes = service.defaultScopes
275+
return .none
276+
253277
case .binding:
254278
return .none
255279

@@ -266,6 +290,7 @@ struct ChatMenu: ReducerProtocol {
266290
var extraSystemPrompt: String = ""
267291
var temperatureOverride: Double? = nil
268292
var chatModelIdOverride: String? = nil
293+
var defaultScopes: Set<ChatService.Scope> = []
269294
}
270295

271296
enum Action: Equatable {
@@ -274,6 +299,8 @@ struct ChatMenu: ReducerProtocol {
274299
case temperatureOverrideSelected(Double?)
275300
case chatModelIdOverrideSelected(String?)
276301
case customCommandButtonTapped(CustomCommand)
302+
case resetDefaultScopesButtonTapped
303+
case toggleScope(ChatService.Scope)
277304
}
278305

279306
let service: ChatService
@@ -304,6 +331,15 @@ struct ChatMenu: ReducerProtocol {
304331
return .run { _ in
305332
try await service.handleCustomCommand(command)
306333
}
334+
335+
case .resetDefaultScopesButtonTapped:
336+
return .run { _ in
337+
service.resetDefaultScopes()
338+
}
339+
case let .toggleScope(scope):
340+
return .run { _ in
341+
service.defaultScopes.formSymmetricDifference([scope])
342+
}
307343
}
308344
}
309345
}

Core/Sources/ChatGPTChatTab/ChatContextMenu.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AppKit
2+
import ChatService
23
import ComposableArchitecture
34
import SharedUIComponents
45
import SwiftUI
@@ -30,6 +31,7 @@ struct ChatContextMenu: View {
3031

3132
chatModel
3233
temperature
34+
defaultScopes
3335

3436
Divider()
3537

@@ -153,6 +155,34 @@ struct ChatContextMenu: View {
153155
}
154156
}
155157

158+
@ViewBuilder
159+
var defaultScopes: some View {
160+
Menu("Default Scopes") {
161+
WithViewStore(store, observe: \.defaultScopes) { viewStore in
162+
Button(action: {
163+
store.send(.resetDefaultScopesButtonTapped)
164+
}) {
165+
Text("Reset Default Scopes")
166+
}
167+
168+
Divider()
169+
170+
ForEach(ChatService.Scope.allCases, id: \.rawValue) { value in
171+
Button(action: {
172+
viewStore.send(.toggleScope(value))
173+
}) {
174+
HStack {
175+
Text("@" + value.rawValue)
176+
if viewStore.state.contains(value) {
177+
Image(systemName: "checkmark")
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
}
185+
156186
var customCommandMenu: some View {
157187
Menu("Custom Commands") {
158188
ForEach(

Core/Sources/ChatGPTChatTab/ChatGPTChatTab.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import ChatContextCollector
12
import ChatService
23
import ChatTab
4+
import CodableWrappers
35
import Combine
46
import ComposableArchitecture
57
import Foundation
@@ -21,6 +23,7 @@ public class ChatGPTChatTab: ChatTab {
2123
var configuration: OverridingChatGPTConfiguration.Overriding
2224
var systemPrompt: String
2325
var extraSystemPrompt: String
26+
var defaultScopes: Set<ChatContext.Scope>?
2427
}
2528

2629
struct Builder: ChatTabBuilder {
@@ -65,7 +68,8 @@ public class ChatGPTChatTab: ChatTab {
6568
history: await service.memory.history,
6669
configuration: service.configuration.overriding,
6770
systemPrompt: service.systemPrompt,
68-
extraSystemPrompt: service.extraSystemPrompt
71+
extraSystemPrompt: service.extraSystemPrompt,
72+
defaultScopes: service.defaultScopes
6973
)
7074
return (try? JSONEncoder().encode(state)) ?? Data()
7175
}
@@ -79,6 +83,9 @@ public class ChatGPTChatTab: ChatTab {
7983
tab.service.configuration.overriding = state.configuration
8084
tab.service.mutateSystemPrompt(state.systemPrompt)
8185
tab.service.mutateExtraSystemPrompt(state.extraSystemPrompt)
86+
if let scopes = state.defaultScopes {
87+
tab.service.defaultScopes = scopes
88+
}
8289
await tab.service.memory.mutateHistory { history in
8390
history = state.history
8491
}

Core/Sources/ChatGPTChatTab/ChatPanel.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ struct ChatPanelMessages: View {
5454
Group {
5555
Spacer(minLength: 12)
5656

57-
Instruction()
57+
Instruction(chat: chat)
5858

5959
ChatHistory(chat: chat)
6060
.listItemTint(.clear)
@@ -225,8 +225,7 @@ private struct StopRespondingButton: View {
225225
}
226226

227227
private struct Instruction: View {
228-
@AppStorage(\.useCodeScopeByDefaultInChatContext)
229-
var useCodeScopeByDefaultInChatContext
228+
let chat: StoreOf<Chat>
230229

231230
var body: some View {
232231
Group {
@@ -255,8 +254,9 @@ private struct Instruction: View {
255254
| --- | --- |
256255
| `@file` | Read the metadata of the editing file |
257256
| `@code` | Read the code and metadata in the editing file |
258-
| `@web` (beta) | Search on Bing or query from a web page |
257+
| `@sense`| Experimental. Read the relevant code of the focused editor |
259258
| `@project` | Experimental. Access content of the project |
259+
| `@web` (beta) | Search on Bing or query from a web page |
260260
261261
To use scopes, you can prefix a message with `@code`.
262262
@@ -265,18 +265,24 @@ private struct Instruction: View {
265265
)
266266
.modifier(InstructionModifier())
267267

268-
Markdown(
269-
"""
270-
Hello, I am your AI programming assistant. I can identify issues, explain and even improve code.
268+
WithViewStore(chat, observe: \.chatMenu.defaultScopes) { viewStore in
269+
Markdown(
270+
"""
271+
Hello, I am your AI programming assistant. I can identify issues, explain and even improve code.
271272
272-
\(
273-
useCodeScopeByDefaultInChatContext
274-
? "Scope **`@code`** is enabled by default."
275-
: "Scope **`@file`** is enabled by default."
273+
\({
274+
if viewStore.state.isEmpty {
275+
return "No scope is enabled by default"
276+
} else {
277+
let scopes = viewStore.state.map(\.rawValue).sorted()
278+
.joined(separator: ", ")
279+
return "Default scopes: `\(scopes)`"
280+
}
281+
}())
282+
"""
276283
)
277-
"""
278-
)
279-
.modifier(InstructionModifier())
284+
.modifier(InstructionModifier())
285+
}
280286
}
281287
}
282288

Core/Sources/ChatService/ChatService.swift

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import OpenAIService
66
import Preferences
77

88
public final class ChatService: ObservableObject {
9+
public typealias Scope = ChatContext.Scope
10+
911
public let memory: ContextAwareAutoManagedChatGPTMemory
1012
public let configuration: OverridingChatGPTConfiguration
1113
public let chatGPTService: any ChatGPTServiceType
@@ -15,6 +17,7 @@ public final class ChatService: ObservableObject {
1517
@Published public internal(set) var systemPrompt = UserDefaults.shared
1618
.value(for: \.defaultChatSystemPrompt)
1719
@Published public internal(set) var extraSystemPrompt = ""
20+
@Published public var defaultScopes = Set<Scope>()
1821

1922
let pluginController: ChatPluginController
2023
var cancellable = Set<AnyCancellable>()
@@ -37,20 +40,22 @@ public final class ChatService: ObservableObject {
3740

3841
public convenience init() {
3942
let configuration = UserPreferenceChatGPTConfiguration().overriding()
43+
/// Used by context collector
44+
let extraConfiguration = configuration.overriding()
4045
let memory = ContextAwareAutoManagedChatGPTMemory(
41-
configuration: configuration,
46+
configuration: extraConfiguration,
4247
functionProvider: ChatFunctionProvider()
4348
)
4449
self.init(
4550
memory: memory,
4651
configuration: configuration,
4752
chatGPTService: ChatGPTService(
4853
memory: memory,
49-
configuration: configuration,
54+
configuration: extraConfiguration,
5055
functionProvider: memory.functionProvider
5156
)
5257
)
53-
58+
5459
resetDefaultScopes()
5560

5661
memory.chatService = self
@@ -60,20 +65,38 @@ public final class ChatService: ObservableObject {
6065
}
6166
}
6267
}
63-
68+
6469
public func resetDefaultScopes() {
65-
if UserDefaults.shared.value(for: \.useCodeScopeByDefaultInChatContext) {
66-
memory.contextController.defaultScopes = ["code"]
67-
} else {
68-
memory.contextController.defaultScopes = ["file"]
70+
var scopes = Set<Scope>()
71+
if UserDefaults.shared.value(for: \.enableFileScopeByDefaultInChatContext) {
72+
scopes.insert(.file)
73+
}
74+
75+
if UserDefaults.shared.value(for: \.enableCodeScopeByDefaultInChatContext) {
76+
scopes.insert(.code)
6977
}
78+
79+
if UserDefaults.shared.value(for: \.enableProjectScopeByDefaultInChatContext) {
80+
scopes.insert(.project)
81+
}
82+
83+
if UserDefaults.shared.value(for: \.enableSenseScopeByDefaultInChatContext) {
84+
scopes.insert(.sense)
85+
}
86+
87+
if UserDefaults.shared.value(for: \.enableWebScopeByDefaultInChatContext) {
88+
scopes.insert(.web)
89+
}
90+
91+
defaultScopes = scopes
7092
}
7193

7294
public func send(content: String) async throws {
95+
memory.contextController.defaultScopes = defaultScopes
7396
guard !isReceivingMessage else { throw CancellationError() }
7497
let handledInPlugin = try await pluginController.handleContent(content)
7598
if handledInPlugin { return }
76-
99+
77100
let stream = try await chatGPTService.send(content: content, summary: nil)
78101
isReceivingMessage = true
79102
do {
@@ -115,7 +138,6 @@ public final class ChatService: ObservableObject {
115138
public func resetPrompt() async {
116139
systemPrompt = UserDefaults.shared.value(for: \.defaultChatSystemPrompt)
117140
extraSystemPrompt = ""
118-
resetDefaultScopes()
119141
}
120142

121143
public func deleteMessage(id: String) async {

0 commit comments

Comments
 (0)