Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit c8fef6e

Browse files
authored
AI Chat message policy debug (#3810)
Task/Issue URL: https://app.asana.com/0/1204167627774280/1209160773739107/f **Description**: Add a debug settings in the debug UI for people to be able to allow a custom domain in the message policy
1 parent 943c99d commit c8fef6e

File tree

7 files changed

+183
-14
lines changed

7 files changed

+183
-14
lines changed

DuckDuckGo.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@
142142
311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1AC2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift */; };
143143
311BD1AF2836BB4200AEF6C1 /* AutofillItemsLockedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1AE2836BB4200AEF6C1 /* AutofillItemsLockedView.swift */; };
144144
311BD1B12836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */; };
145+
31206F702D3804E800A95D76 /* AIChatDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31206F6F2D3804E800A95D76 /* AIChatDebugView.swift */; };
146+
31206F722D38072100A95D76 /* AIChatDebugSettingsHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31206F712D38071000A95D76 /* AIChatDebugSettingsHandling.swift */; };
145147
312E5746283BB04A00C18FA0 /* AutofillEmptySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312E5745283BB04A00C18FA0 /* AutofillEmptySearchView.swift */; };
146148
3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3132FA2527A0784600DD7A12 /* FilePreviewHelper.swift */; };
147149
3132FA2827A0788400DD7A12 /* PassKitPreviewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3132FA2727A0788400DD7A12 /* PassKitPreviewHelper.swift */; };
@@ -1558,6 +1560,8 @@
15581560
311BD1AC2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillItemsEmptyView.swift; sourceTree = "<group>"; };
15591561
311BD1AE2836BB4200AEF6C1 /* AutofillItemsLockedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillItemsLockedView.swift; sourceTree = "<group>"; };
15601562
311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListAuthenticator.swift; sourceTree = "<group>"; };
1563+
31206F6F2D3804E800A95D76 /* AIChatDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatDebugView.swift; sourceTree = "<group>"; };
1564+
31206F712D38071000A95D76 /* AIChatDebugSettingsHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatDebugSettingsHandling.swift; sourceTree = "<group>"; };
15611565
312E5745283BB04A00C18FA0 /* AutofillEmptySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillEmptySearchView.swift; sourceTree = "<group>"; };
15621566
3132FA2527A0784600DD7A12 /* FilePreviewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewHelper.swift; sourceTree = "<group>"; };
15631567
3132FA2727A0788400DD7A12 /* PassKitPreviewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassKitPreviewHelper.swift; sourceTree = "<group>"; };
@@ -3927,6 +3931,7 @@
39273931
31C314A32D2EF9DF009A412A /* UserScript */ = {
39283932
isa = PBXGroup;
39293933
children = (
3934+
31206F712D38071000A95D76 /* AIChatDebugSettingsHandling.swift */,
39303935
318C5B482D3022FE00DAA5FC /* AIChatPayloadHandling.swift */,
39313936
31C3149B2D2EEB44009A412A /* AIChatScriptUserValues.swift */,
39323937
31C3149C2D2EEB44009A412A /* AIChatUserScript.swift */,
@@ -4758,6 +4763,7 @@
47584763
858566E7252E4F56007501B8 /* Debug.storyboard */,
47594764
D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */,
47604765
8590CB602684D0600089F6BF /* CookieDebugViewController.swift */,
4766+
31206F6F2D3804E800A95D76 /* AIChatDebugView.swift */,
47614767
4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */,
47624768
858566FA252E55D6007501B8 /* ImageCacheDebugViewController.swift */,
47634769
8590CB66268A2E520089F6BF /* RootDebugViewController.swift */,
@@ -8182,6 +8188,7 @@
81828188
7B4F87E72D0734090010B18F /* ControlCenterWidget.swift in Sources */,
81838189
9F7CFF762C86BB8F0012833E /* OnboardingView+AppIconPickerContent.swift in Sources */,
81848190
D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */,
8191+
31206F702D3804E800A95D76 /* AIChatDebugView.swift in Sources */,
81858192
BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */,
81868193
BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */,
81878194
850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */,
@@ -8294,6 +8301,7 @@
82948301
1DEAADFF2BA7832F00E25A97 /* EmailProtectionView.swift in Sources */,
82958302
31C314A22D2EF614009A412A /* AIChatViewControllerManager.swift in Sources */,
82968303
988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */,
8304+
31206F722D38072100A95D76 /* AIChatDebugSettingsHandling.swift in Sources */,
82978305
D6FEB8B12B7498A300C3615F /* HeadlessWebView.swift in Sources */,
82988306
9FDEC7BC2C91204900C7A692 /* AppIconPickerViewModel.swift in Sources */,
82998307
F1FDC9352BF51E41006B1435 /* VPNSettings+Environment.swift in Sources */,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// AIChatDebugSettingsHandling.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
22+
protocol AIChatDebugSettingsHandling {
23+
var messagePolicyHostname: String? { get set }
24+
}
25+
26+
struct AIChatDebugSettings: AIChatDebugSettingsHandling {
27+
private let userDefaultsKey = "aichat.debug.messagePolicyHostname"
28+
private let userDefault: UserDefaults
29+
30+
init(userDefault: UserDefaults = .standard) {
31+
self.userDefault = userDefault
32+
}
33+
34+
var messagePolicyHostname: String? {
35+
get {
36+
let value = userDefault.string(forKey: userDefaultsKey)
37+
return value?.isEmpty == true ? nil : value
38+
}
39+
set {
40+
if let newValue = newValue, !newValue.isEmpty {
41+
userDefault.set(newValue, forKey: userDefaultsKey)
42+
} else {
43+
userDefault.removeObject(forKey: userDefaultsKey)
44+
}
45+
}
46+
}
47+
}

DuckDuckGo/AIChat/UserScript/AIChatUserScript.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ final class AIChatUserScript: NSObject, Subfeature {
3434
weak var broker: UserScriptMessageBroker?
3535
private(set) var messageOriginPolicy: MessageOriginPolicy
3636

37-
init(handler: AIChatUserScriptHandling) {
37+
init(handler: AIChatUserScriptHandling, debugSettings: AIChatDebugSettingsHandling) {
3838
self.handler = handler
3939
var rules = [HostnameMatchingRule]()
4040

@@ -43,6 +43,10 @@ final class AIChatUserScript: NSObject, Subfeature {
4343
rules.append(.exact(hostname: ddgDomain))
4444
}
4545

46+
if let debugHostname = debugSettings.messagePolicyHostname {
47+
rules.append(.exact(hostname: debugHostname))
48+
}
49+
4650
self.messageOriginPolicy = .only(rules: rules)
4751
}
4852

DuckDuckGo/AIChatDebugView.swift

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//
2+
// AIChatDebugView.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
21+
import SwiftUI
22+
import Combine
23+
24+
struct AIChatDebugView: View {
25+
@StateObject private var viewModel = AIChatDebugViewModel()
26+
27+
var body: some View {
28+
List {
29+
Section(footer: Text("Stored Hostname: \(viewModel.enteredHostname)")) {
30+
NavigationLink(destination: AIChatDebugHostnameEntryView(viewModel: viewModel)) {
31+
Text("Message policy hostname")
32+
}
33+
}
34+
}
35+
.navigationTitle("AI Chat")
36+
}
37+
}
38+
39+
private final class AIChatDebugViewModel: ObservableObject {
40+
private var debugSettings = AIChatDebugSettings()
41+
42+
@Published var enteredHostname: String {
43+
didSet {
44+
debugSettings.messagePolicyHostname = enteredHostname
45+
}
46+
}
47+
48+
init() {
49+
self.enteredHostname = debugSettings.messagePolicyHostname ?? ""
50+
}
51+
52+
func resetHostname() {
53+
enteredHostname = ""
54+
}
55+
}
56+
57+
private struct AIChatDebugHostnameEntryView: View {
58+
@ObservedObject var viewModel: AIChatDebugViewModel
59+
@State private var policyHostname: String = ""
60+
@Environment(\.presentationMode) var presentationMode
61+
62+
var body: some View {
63+
Form {
64+
Section {
65+
TextField("Hostname", text: $policyHostname)
66+
.autocorrectionDisabled(true)
67+
.textInputAutocapitalization(.never)
68+
}
69+
Button {
70+
viewModel.enteredHostname = policyHostname
71+
presentationMode.wrappedValue.dismiss()
72+
} label: {
73+
Text("Confirm")
74+
}
75+
76+
Button {
77+
viewModel.resetHostname()
78+
policyHostname = ""
79+
presentationMode.wrappedValue.dismiss()
80+
} label: {
81+
Text("Reset")
82+
}
83+
}
84+
.navigationTitle("Edit Hostname")
85+
.onAppear {
86+
policyHostname = viewModel.enteredHostname
87+
}
88+
}
89+
}
90+
91+
#Preview {
92+
AIChatDebugView()
93+
}

0 commit comments

Comments
 (0)