Skip to content

Commit f528d9a

Browse files
committed
Support changing default scopes in chat panel
1 parent 2b5d950 commit f528d9a

File tree

4 files changed

+110
-31
lines changed

4 files changed

+110
-31
lines changed

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/ChatPanel.swift

Lines changed: 18 additions & 13 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 {
@@ -266,18 +265,24 @@ private struct Instruction: View {
266265
)
267266
.modifier(InstructionModifier())
268267

269-
Markdown(
270-
"""
271-
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.
272272
273-
\(
274-
useCodeScopeByDefaultInChatContext
275-
? "Scope **`@code`** is enabled by default."
276-
: "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+
"""
277283
)
278-
"""
279-
)
280-
.modifier(InstructionModifier())
284+
.modifier(InstructionModifier())
285+
}
281286
}
282287
}
283288

Core/Sources/ChatService/ChatService.swift

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import OpenAIService
66
import Preferences
77

88
public final class ChatService: ObservableObject {
9+
public enum Scope: String, Equatable, CaseIterable {
10+
case file
11+
case code
12+
case sense
13+
case project
14+
case web
15+
}
16+
917
public let memory: ContextAwareAutoManagedChatGPTMemory
1018
public let configuration: OverridingChatGPTConfiguration
1119
public let chatGPTService: any ChatGPTServiceType
@@ -15,6 +23,7 @@ public final class ChatService: ObservableObject {
1523
@Published public internal(set) var systemPrompt = UserDefaults.shared
1624
.value(for: \.defaultChatSystemPrompt)
1725
@Published public internal(set) var extraSystemPrompt = ""
26+
@Published public var defaultScopes = Set<Scope>()
1827

1928
let pluginController: ChatPluginController
2029
var cancellable = Set<AnyCancellable>()
@@ -50,7 +59,7 @@ public final class ChatService: ObservableObject {
5059
functionProvider: memory.functionProvider
5160
)
5261
)
53-
62+
5463
resetDefaultScopes()
5564

5665
memory.chatService = self
@@ -60,38 +69,38 @@ public final class ChatService: ObservableObject {
6069
}
6170
}
6271
}
63-
72+
6473
public func resetDefaultScopes() {
65-
var scopes = Set<String>()
66-
74+
var scopes = Set<Scope>()
6775
if UserDefaults.shared.value(for: \.enableFileScopeByDefaultInChatContext) {
68-
scopes.insert("file")
76+
scopes.insert(.file)
6977
}
70-
78+
7179
if UserDefaults.shared.value(for: \.enableCodeScopeByDefaultInChatContext) {
72-
scopes.insert("code")
80+
scopes.insert(.code)
7381
}
74-
75-
if UserDefaults.shared.value(for: \.enableSenseScopeByDefaultInChatContext) {
76-
scopes.insert("sense")
77-
}
78-
82+
7983
if UserDefaults.shared.value(for: \.enableProjectScopeByDefaultInChatContext) {
80-
scopes.insert("project")
84+
scopes.insert(.project)
8185
}
82-
86+
87+
if UserDefaults.shared.value(for: \.enableSenseScopeByDefaultInChatContext) {
88+
scopes.insert(.sense)
89+
}
90+
8391
if UserDefaults.shared.value(for: \.enableWebScopeByDefaultInChatContext) {
84-
scopes.insert("web")
92+
scopes.insert(.web)
8593
}
8694

87-
memory.contextController.defaultScopes = scopes
95+
defaultScopes = scopes
8896
}
8997

9098
public func send(content: String) async throws {
99+
memory.contextController.defaultScopes = Set(defaultScopes.map(\.rawValue))
91100
guard !isReceivingMessage else { throw CancellationError() }
92101
let handledInPlugin = try await pluginController.handleContent(content)
93102
if handledInPlugin { return }
94-
103+
95104
let stream = try await chatGPTService.send(content: content, summary: nil)
96105
isReceivingMessage = true
97106
do {
@@ -133,7 +142,6 @@ public final class ChatService: ObservableObject {
133142
public func resetPrompt() async {
134143
systemPrompt = UserDefaults.shared.value(for: \.defaultChatSystemPrompt)
135144
extraSystemPrompt = ""
136-
resetDefaultScopes()
137145
}
138146

139147
public func deleteMessage(id: String) async {

0 commit comments

Comments
 (0)