Skip to content

Commit ddbfece

Browse files
committed
Add settings for scopes
1 parent 5bdaf24 commit ddbfece

4 files changed

Lines changed: 286 additions & 19 deletions

File tree

Core/Sources/HostApp/FeatureSettings/ChatSettingsView.swift

Lines changed: 205 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import Preferences
2+
import SharedUIComponents
23
import SwiftUI
34

5+
#if canImport(ProHostApp)
6+
import ProHostApp
7+
#endif
8+
49
struct ChatSettingsView: View {
510
class Settings: ObservableObject {
611
static let availableLocalizedLocales = Locale.availableLocalizedLocales
@@ -11,8 +16,6 @@ struct ChatSettingsView: View {
1116
@AppStorage(\.chatCodeFontSize) var chatCodeFontSize
1217
@AppStorage(\.maxFocusedCodeLineCount)
1318
var maxFocusedCodeLineCount
14-
@AppStorage(\.useCodeScopeByDefaultInChatContext)
15-
var useCodeScopeByDefaultInChatContext
1619
@AppStorage(\.defaultChatFeatureChatModelId) var defaultChatFeatureChatModelId
1720
@AppStorage(\.defaultChatSystemPrompt) var defaultChatSystemPrompt
1821
@AppStorage(\.chatSearchPluginMaxIterations) var chatSearchPluginMaxIterations
@@ -32,12 +35,13 @@ struct ChatSettingsView: View {
3235
var body: some View {
3336
VStack {
3437
chatSettingsForm
35-
Divider()
38+
SettingsDivider("UI")
3639
uiForm
37-
Divider()
40+
SettingsDivider("Context")
3841
contextForm
39-
Divider()
42+
SettingsDivider("Plugin")
4043
pluginForm
44+
ScopeForm()
4145
}
4246
}
4347

@@ -160,7 +164,7 @@ struct ChatSettingsView: View {
160164

161165
Text("pt")
162166
}
163-
167+
164168
Toggle(isOn: $settings.wrapCodeInCodeBlock) {
165169
Text("Wrap code in code block")
166170
}
@@ -170,10 +174,6 @@ struct ChatSettingsView: View {
170174
@ViewBuilder
171175
var contextForm: some View {
172176
Form {
173-
Toggle(isOn: $settings.useCodeScopeByDefaultInChatContext) {
174-
Text("Use @code scope by default in chat context.")
175-
}
176-
177177
HStack {
178178
TextField(text: .init(get: {
179179
"\(Int(settings.maxFocusedCodeLineCount))"
@@ -235,13 +235,206 @@ struct ChatSettingsView: View {
235235
)
236236
}
237237
}
238+
239+
struct ScopeForm: View {
240+
class Settings: ObservableObject {
241+
@AppStorage(\.enableFileScopeByDefaultInChatContext)
242+
var enableFileScopeByDefaultInChatContext: Bool
243+
@AppStorage(\.enableCodeScopeByDefaultInChatContext)
244+
var enableCodeScopeByDefaultInChatContext: Bool
245+
@AppStorage(\.enableSenseScopeByDefaultInChatContext)
246+
var enableSenseScopeByDefaultInChatContext: Bool
247+
@AppStorage(\.enableProjectScopeByDefaultInChatContext)
248+
var enableProjectScopeByDefaultInChatContext: Bool
249+
@AppStorage(\.enableWebScopeByDefaultInChatContext)
250+
var enableWebScopeByDefaultInChatContext: Bool
251+
@AppStorage(\.preferredChatModelIdForSenseScope)
252+
var preferredChatModelIdForSenseScope: String
253+
@AppStorage(\.preferredChatModelIdForProjectScope)
254+
var preferredChatModelIdForProjectScope: String
255+
@AppStorage(\.preferredChatModelIdForWebScope)
256+
var preferredChatModelIdForWebScope: String
257+
@AppStorage(\.chatModels) var chatModels
258+
259+
init() {}
260+
}
261+
262+
@StateObject var settings = Settings()
263+
264+
var body: some View {
265+
VStack {
266+
Scope(
267+
title: Text("File Scope"),
268+
description: "Enable the bot to read the metadata of the editing file."
269+
) {
270+
Form {
271+
Toggle(isOn: $settings.enableFileScopeByDefaultInChatContext) {
272+
Text("Enable @file scope by default in chat context.")
273+
}
274+
}
275+
}
276+
277+
Scope(
278+
title: Text("Code Scope"),
279+
description: "Enable the bot to read the code and metadata in the editing file."
280+
) {
281+
Form {
282+
Toggle(isOn: $settings.enableCodeScopeByDefaultInChatContext) {
283+
Text("Enable @code scope by default in chat context.")
284+
}
285+
}
286+
}
287+
288+
#if canImport(ProHostApp)
289+
290+
Scope(
291+
title: WithFeatureEnabled(\.projectScopeInChat) { Text("Sense Scope") },
292+
description: "Experimental. Enable the bot to read the relevant code of the editing file in the project, third party packages and the SDK."
293+
) {
294+
WithFeatureEnabled(\.projectScopeInChat, alignment: .hidden) {
295+
Form {
296+
Toggle(isOn: $settings.enableSenseScopeByDefaultInChatContext) {
297+
Text("Enable @sense scope by default in chat context.")
298+
}
299+
300+
Picker(
301+
"Preferred Chat Model",
302+
selection: $settings.preferredChatModelIdForSenseScope
303+
) {
304+
Text("None").tag("")
305+
306+
if !settings.chatModels
307+
.contains(where: {
308+
$0.id == settings.preferredChatModelIdForSenseScope
309+
}),
310+
!settings.preferredChatModelIdForSenseScope.isEmpty
311+
{
312+
Text(
313+
(settings.chatModels.first?.name).map { "\($0) (Default)" }
314+
?? "No Model Found"
315+
)
316+
.tag(settings.preferredChatModelIdForSenseScope)
317+
}
318+
319+
ForEach(settings.chatModels, id: \.id) { chatModel in
320+
Text(chatModel.name).tag(chatModel.id)
321+
}
322+
}
323+
}
324+
}
325+
.padding(.top, 20)
326+
}
327+
328+
Scope(
329+
title: WithFeatureEnabled(\.projectScopeInChat) { Text("Project Scope") },
330+
description: "Experimental. Enable the bot to search code symbols in the project, third party packages and the SDK."
331+
) {
332+
WithFeatureEnabled(\.projectScopeInChat, alignment: .hidden) {
333+
Form {
334+
Toggle(isOn: $settings.enableProjectScopeByDefaultInChatContext) {
335+
Text("Enable @project scope by default in chat context.")
336+
}
337+
338+
Picker(
339+
"Preferred Chat Model",
340+
selection: $settings.preferredChatModelIdForProjectScope
341+
) {
342+
Text("None").tag("")
343+
344+
if !settings.chatModels
345+
.contains(where: {
346+
$0.id == settings.preferredChatModelIdForProjectScope
347+
}),
348+
!settings.preferredChatModelIdForProjectScope.isEmpty
349+
{
350+
Text(
351+
(settings.chatModels.first?.name).map { "\($0) (Default)" }
352+
?? "No Model Found"
353+
)
354+
.tag(settings.preferredChatModelIdForProjectScope)
355+
}
356+
357+
ForEach(settings.chatModels, id: \.id) { chatModel in
358+
Text(chatModel.name).tag(chatModel.id)
359+
}
360+
}
361+
}
362+
}
363+
}
364+
365+
#endif
366+
367+
Scope(
368+
title: Text("Web Scope"),
369+
description: "Allow the bot to search on Bing or read a web page."
370+
) {
371+
Form {
372+
Toggle(isOn: $settings.enableWebScopeByDefaultInChatContext) {
373+
Text("Enable @web scope by default in chat context.")
374+
}
375+
376+
Picker(
377+
"Preferred Chat Model",
378+
selection: $settings.preferredChatModelIdForWebScope
379+
) {
380+
Text("None").tag("")
381+
382+
if !settings.chatModels
383+
.contains(where: {
384+
$0.id == settings.preferredChatModelIdForWebScope
385+
}),
386+
!settings.preferredChatModelIdForWebScope.isEmpty
387+
{
388+
Text(
389+
(settings.chatModels.first?.name).map { "\($0) (Default)" }
390+
?? "No Model Found"
391+
)
392+
.tag(settings.preferredChatModelIdForWebScope)
393+
}
394+
395+
ForEach(settings.chatModels, id: \.id) { chatModel in
396+
Text(chatModel.name).tag(chatModel.id)
397+
}
398+
}
399+
}
400+
}
401+
}
402+
}
403+
404+
struct Scope<Title: View, Content: View>: View {
405+
let title: Title
406+
let description: String
407+
let content: () -> Content
408+
409+
var body: some View {
410+
SettingsDivider(title)
411+
VStack {
412+
Text(description)
413+
.multilineTextAlignment(.center)
414+
.foregroundStyle(.secondary)
415+
.padding(8)
416+
.background {
417+
RoundedRectangle(cornerRadius: 8)
418+
.fill(Color.secondary.opacity(0.1))
419+
}
420+
.frame(maxWidth: 500)
421+
Form {
422+
content()
423+
}
424+
}
425+
}
426+
}
427+
}
238428
}
239429

240430
// MARK: - Preview
241431

242-
struct ChatSettingsView_Previews: PreviewProvider {
243-
static var previews: some View {
432+
#Preview {
433+
ScrollView {
244434
ChatSettingsView()
435+
.padding()
245436
}
437+
.frame(height: 800)
438+
.environment(\.overrideFeatureFlag, \.never)
246439
}
247440

Pro

Submodule Pro updated from 7fe0d03 to e6d0cac

Tool/Sources/Preferences/Keys.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public struct UserDefaultPreferenceKeys {
8888
defaultValue: false,
8989
key: "HideCircularWidget"
9090
)
91-
91+
9292
public let showHideWidgetShortcutGlobally = PreferenceKey(
9393
defaultValue: false,
9494
key: "ShowHideWidgetShortcutGlobally"
@@ -269,11 +269,11 @@ public extension UserDefaultPreferenceKeys {
269269
var promptToCodeGenerateDescriptionInUserPreferredLanguage: PreferenceKey<Bool> {
270270
.init(defaultValue: true, key: "PromptToCodeGenerateDescriptionInUserPreferredLanguage")
271271
}
272-
272+
273273
var promptToCodeChatModelId: PreferenceKey<String> {
274274
.init(defaultValue: "", key: "PromptToCodeChatModelId")
275275
}
276-
276+
277277
var promptToCodeEmbeddingModelId: PreferenceKey<String> {
278278
.init(defaultValue: "", key: "PromptToCodeEmbeddingModelId")
279279
}
@@ -366,7 +366,7 @@ public extension UserDefaultPreferenceKeys {
366366
.init(defaultValue: 100, key: "MaxEmbeddableFileInChatContextLineCount")
367367
}
368368

369-
var useCodeScopeByDefaultInChatContext: PreferenceKey<Bool> {
369+
var useCodeScopeByDefaultInChatContext: DeprecatedPreferenceKey<Bool> {
370370
.init(defaultValue: true, key: "UseSelectionScopeByDefaultInChatContext")
371371
}
372372

@@ -389,10 +389,42 @@ public extension UserDefaultPreferenceKeys {
389389
var chatSearchPluginMaxIterations: PreferenceKey<Int> {
390390
.init(defaultValue: 3, key: "ChatSearchPluginMaxIterations")
391391
}
392-
392+
393393
var wrapCodeInChatCodeBlock: PreferenceKey<Bool> {
394394
.init(defaultValue: true, key: "WrapCodeInChatCodeBlock")
395395
}
396+
397+
var enableFileScopeByDefaultInChatContext: PreferenceKey<Bool> {
398+
.init(defaultValue: true, key: "EnableFileScopeByDefaultInChatContext")
399+
}
400+
401+
var enableCodeScopeByDefaultInChatContext: PreferenceKey<Bool> {
402+
.init(defaultValue: true, key: "UseSelectionScopeByDefaultInChatContext")
403+
}
404+
405+
var enableSenseScopeByDefaultInChatContext: PreferenceKey<Bool> {
406+
.init(defaultValue: false, key: "EnableSenseScopeByDefaultInChatContext")
407+
}
408+
409+
var enableProjectScopeByDefaultInChatContext: PreferenceKey<Bool> {
410+
.init(defaultValue: false, key: "EnableProjectScopeByDefaultInChatContext")
411+
}
412+
413+
var enableWebScopeByDefaultInChatContext: PreferenceKey<Bool> {
414+
.init(defaultValue: false, key: "EnableWebScopeByDefaultInChatContext")
415+
}
416+
417+
var preferredChatModelIdForSenseScope: PreferenceKey<String> {
418+
.init(defaultValue: "", key: "PreferredChatModelIdForSenseScope")
419+
}
420+
421+
var preferredChatModelIdForProjectScope: PreferenceKey<String> {
422+
.init(defaultValue: "", key: "PreferredChatModelIdForProjectScope")
423+
}
424+
425+
var preferredChatModelIdForWebScope: PreferenceKey<String> {
426+
.init(defaultValue: "", key: "PreferredChatModelIdForWebScope")
427+
}
396428
}
397429

398430
// MARK: - Bing Search
@@ -508,7 +540,7 @@ public extension UserDefaultPreferenceKeys {
508540
key: "FeatureFlag-DisableGitHubCopilotSettingsAutoRefreshOnAppear"
509541
)
510542
}
511-
543+
512544
var disableEnhancedWorkspace: FeatureFlag {
513545
.init(
514546
defaultValue: false,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import SwiftUI
2+
3+
public struct SettingsDivider<Title: View>: View {
4+
let title: Title?
5+
6+
public init(_ title: Title) {
7+
self.title = title
8+
}
9+
10+
public var body: some View {
11+
if let title {
12+
HStack {
13+
VStack {
14+
Divider()
15+
}
16+
title
17+
.foregroundStyle(.secondary)
18+
.font(.subheadline)
19+
.zIndex(2)
20+
VStack {
21+
Divider()
22+
}
23+
}
24+
.padding(.vertical, 8)
25+
} else {
26+
Divider()
27+
.padding(.vertical, 8)
28+
}
29+
}
30+
}
31+
32+
extension SettingsDivider where Title == Text {
33+
public init(_ title: String) {
34+
self.title = Text(title)
35+
}
36+
}
37+
38+
extension SettingsDivider where Title == EmptyView {
39+
public init() {
40+
self.title = nil
41+
}
42+
}

0 commit comments

Comments
 (0)