Skip to content

Commit 0eb92ef

Browse files
committed
Make the chat box work
1 parent 593bb36 commit 0eb92ef

File tree

10 files changed

+174
-65
lines changed

10 files changed

+174
-65
lines changed

Core/Sources/ActiveApplicationMonitor/ActiveApplicationMonitor.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import AppKit
22

33
public final class ActiveApplicationMonitor {
44
static let shared = ActiveApplicationMonitor()
5-
var activeApplication = NSWorkspace.shared.runningApplications.first(where: \.isActive)
5+
var latestXcode: NSRunningApplication?
6+
var activeApplication = NSWorkspace.shared.runningApplications.first(where: \.isActive) {
7+
didSet {
8+
if activeApplication?.isXcode ?? false {
9+
latestXcode = activeApplication
10+
}
11+
}
12+
}
613
private var continuations: [UUID: AsyncStream<NSRunningApplication?>.Continuation] = [:]
714

815
private init() {
@@ -28,11 +35,13 @@ public final class ActiveApplicationMonitor {
2835
public static var activeApplication: NSRunningApplication? { shared.activeApplication }
2936

3037
public static var activeXcode: NSRunningApplication? {
31-
if activeApplication?.bundleIdentifier == "com.apple.dt.Xcode" {
38+
if activeApplication?.isXcode ?? false {
3239
return activeApplication
3340
}
3441
return nil
3542
}
43+
44+
public static var latestXcode: NSRunningApplication? { shared.latestXcode }
3645

3746
public static func createStream() -> AsyncStream<NSRunningApplication?> {
3847
.init { continuation in
@@ -62,3 +71,8 @@ public final class ActiveApplicationMonitor {
6271
}
6372
}
6473
}
74+
75+
extension NSRunningApplication {
76+
public var isXcode: Bool { bundleIdentifier == "com.apple.dt.Xcode" }
77+
}
78+

Core/Sources/Environment/Environment.swift

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,33 +111,28 @@ public enum Environment {
111111
}
112112

113113
public static var triggerAction: (_ name: String) async throws -> Void = { name in
114-
var xcodes = [NSRunningApplication]()
115-
var retryCount = 0
116-
// Sometimes runningApplications returns 0 items.
117-
while xcodes.isEmpty, retryCount < 5 {
118-
xcodes = NSRunningApplication
119-
.runningApplications(withBundleIdentifier: "com.apple.dt.Xcode")
120-
if retryCount > 0 { try await Task.sleep(nanoseconds: 10_000_000) }
121-
retryCount += 1
122-
}
123-
124-
guard let activeXcode = xcodes.first(where: { $0.isActive }) else { return }
114+
guard let activeXcode = ActiveApplicationMonitor.activeXcode
115+
?? ActiveApplicationMonitor.latestXcode
116+
else { return }
125117
let bundleName = Bundle.main
126118
.object(forInfoDictionaryKey: "EXTENSION_BUNDLE_NAME") as! String
127119

128120
/// check if menu is open, if not, click the menu item.
129121
let appleScript = """
130122
tell application "System Events"
131-
set proc to item 1 of (processes whose unix id is \(activeXcode.processIdentifier))
132-
tell proc
133-
repeat with theMenu in menus of menu bar 1
134-
set theValue to value of attribute "AXVisibleChildren" of theMenu
135-
if theValue is not {} then
136-
return
137-
end if
138-
end repeat
139-
click menu item "\(name)" of menu 1 of menu item "\(bundleName)" of menu 1 of menu bar item "Editor" of menu bar 1
140-
end tell
123+
set theprocs to every process whose unix id is \(activeXcode.processIdentifier)
124+
repeat with proc in theprocs
125+
set the frontmost of proc to true
126+
tell proc
127+
repeat with theMenu in menus of menu bar 1
128+
set theValue to value of attribute "AXVisibleChildren" of theMenu
129+
if theValue is not {} then
130+
return
131+
end if
132+
end repeat
133+
click menu item "\(name)" of menu 1 of menu item "\(bundleName)" of menu 1 of menu bar item "Editor" of menu bar 1
134+
end tell
135+
end repeat
141136
end tell
142137
"""
143138

Core/Sources/Service/SuggestionCommandHandler/WindowBaseCommandHandler.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,20 +199,23 @@ struct WindowBaseCommandHandler: SuggestionCommandHandler {
199199

200200
let service = ChatGPTService(
201201
systemPrompt: """
202-
You are a code explanation engine, you can only explain the code, do not interpret or translate it. Reply in \(language.isEmpty ? language : "English")
202+
You are a code explanation engine, you can only explain the code concisely, do not interpret or translate it. Reply in \(
203+
language
204+
.isEmpty ? language : "English"
205+
)
203206
""",
204207
apiKey: UserDefaults.shared.value(for: \.openAIAPIKey),
205208
endpoint: endpoint.isEmpty ? nil : endpoint,
206209
model: model.isEmpty ? nil : model,
207-
temperature: 0.6,
210+
temperature: 1,
208211
maxToken: UserDefaults.shared.value(for: \.chatGPTMaxToken)
209212
)
210213

211214
let code = editor.selectedCode(in: selection)
212215
Task {
213216
try? await service.send(
214217
content: removeContinousSpaces(from: code),
215-
summary: "Explain selected code from \(selection.start.line):\(selection.start.character) to \(selection.end.line):\(selection.end.character)."
218+
summary: "Explain selected code from `\(selection.start.line + 1):\(selection.start.character + 1)` to `\(selection.end.line + 1):\(selection.end.character + 1)`."
216219
)
217220
}
218221

Core/Sources/SuggestionWidget/Styles.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ extension Color {
1818
return .white
1919
}))
2020
}
21-
21+
2222
static var userChatContentBackground: Color {
2323
Color(nsColor: NSColor(name: nil, dynamicProvider: { appearance in
2424
if appearance.isDarkMode {
25-
return #colorLiteral(red: 0.1777971793, green: 0.1670255649, blue: 0.2501594388, alpha: 1)
25+
return #colorLiteral(red: 0.2284317913, green: 0.2145925438, blue: 0.3214019983, alpha: 1)
2626
}
2727
return #colorLiteral(red: 0.896820749, green: 0.8709097223, blue: 0.9766687925, alpha: 1)
2828
}))
@@ -31,10 +31,25 @@ extension Color {
3131

3232
extension NSAppearance {
3333
var isDarkMode: Bool {
34-
if self.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua {
34+
if bestMatch(from: [.darkAqua, .aqua]) == .darkAqua {
3535
return true
3636
} else {
3737
return false
3838
}
3939
}
4040
}
41+
42+
extension View {
43+
func xcodeStyleFrame() -> some View {
44+
clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
45+
.overlay(
46+
RoundedRectangle(cornerRadius: 8, style: .continuous)
47+
.stroke(Color.black.opacity(0.3), style: .init(lineWidth: 1))
48+
)
49+
.overlay(
50+
RoundedRectangle(cornerRadius: 7, style: .continuous)
51+
.stroke(Color.white.opacity(0.2), style: .init(lineWidth: 1))
52+
.padding(1)
53+
)
54+
}
55+
}

Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,90 @@ import MarkdownUI
22
import SwiftUI
33

44
struct ChatPanel: View {
5-
@ObservedObject var viewModel: SuggestionPanelViewModel
5+
var viewModel: SuggestionPanelViewModel
66
@ObservedObject var chat: ChatRoom
7+
@State var typedMessage = ""
8+
@Namespace var inputAreaNamespace
79

810
var body: some View {
911
ZStack(alignment: .topTrailing) {
10-
ScrollView {
11-
LazyVStack {
12-
ForEach(chat.history.reversed(), id: \.id) { message in
13-
Markdown(message.text)
14-
.markdownTheme(.gitHub.text {
15-
BackgroundColor(Color.clear)
16-
})
17-
.frame(maxWidth: .infinity, alignment: .leading)
18-
.padding()
19-
.background(
20-
RoundedRectangle(cornerRadius: 9, style: .continuous)
21-
.fill(
22-
message.isUser
12+
VStack {
13+
ScrollView {
14+
LazyVStack() {
15+
if chat.isReceivingMessage {
16+
Button(action: {
17+
chat.stop()
18+
}) {
19+
HStack(spacing: 4) {
20+
Image(systemName: "stop.fill")
21+
Text("Stop Responding")
22+
}
23+
.rotationEffect(Angle(degrees: 180))
24+
.padding(8)
25+
.background(
26+
.regularMaterial,
27+
in: RoundedRectangle(cornerRadius: 8, style: .continuous)
28+
)
29+
}
30+
.buttonStyle(.plain)
31+
.xcodeStyleFrame()
32+
.matchedGeometryEffect(id: "input", in: inputAreaNamespace)
33+
}
34+
35+
ForEach(chat.history.reversed(), id: \.id) { message in
36+
let text = message.text.isEmpty && !message.isUser ? "..." : message.text
37+
38+
Markdown(text)
39+
.textSelection(.enabled)
40+
.markdownTheme(.gitHub.text {
41+
BackgroundColor(Color.clear)
42+
})
43+
.frame(maxWidth: .infinity, alignment: .leading)
44+
.padding()
45+
.background(
46+
RoundedRectangle(cornerRadius: 9, style: .continuous)
47+
.fill(
48+
message.isUser
2349
? Color.userChatContentBackground
2450
: Color.contentBackground
25-
)
51+
)
52+
)
53+
.xcodeStyleFrame()
54+
.rotationEffect(Angle(degrees: 180))
55+
}
56+
}
57+
}
58+
.rotationEffect(Angle(degrees: 180))
59+
60+
if !chat.isReceivingMessage {
61+
HStack {
62+
TextField("Type a message", text: $typedMessage)
63+
.textFieldStyle(.plain)
64+
.padding(8)
65+
.background(
66+
.regularMaterial,
67+
in: RoundedRectangle(cornerRadius: 8, style: .continuous)
2668
)
27-
.rotationEffect(Angle(degrees: 180))
69+
.xcodeStyleFrame()
70+
71+
Button(action: {
72+
if typedMessage.isEmpty { return }
73+
chat.send(typedMessage)
74+
typedMessage = ""
75+
}) {
76+
Image(systemName: "paperplane.fill")
77+
.padding(8)
78+
.background(
79+
.regularMaterial,
80+
in: RoundedRectangle(cornerRadius: 8, style: .continuous)
81+
)
82+
}
83+
.buttonStyle(.plain)
84+
.xcodeStyleFrame()
2885
}
86+
.matchedGeometryEffect(id: "input", in: inputAreaNamespace)
2987
}
3088
}
31-
.rotationEffect(Angle(degrees: 180))
3289

3390
// close button
3491
Button(action: {
@@ -39,11 +96,10 @@ struct ChatPanel: View {
3996
Image(systemName: "xmark")
4097
.padding([.leading, .bottom], 16)
4198
.padding([.top, .trailing], 8)
42-
.foregroundColor(.white)
99+
.foregroundStyle(.secondary)
43100
}
44101
.buttonStyle(.plain)
45102
}
46-
.colorScheme(viewModel.colorScheme)
47103
}
48104
}
49105

@@ -76,9 +132,12 @@ struct ChatPanel_Preview: PreviewProvider {
76132
"""#
77133
),
78134
],
79-
isReceivingMessage: false
135+
isReceivingMessage: true
80136
))
137+
.padding(8)
138+
.background(Color.contentBackground)
81139
.frame(width: 450, height: 500)
140+
.colorScheme(.dark)
82141
}
83142
}
84143

@@ -112,8 +171,11 @@ struct ChatPanel_Light_Preview: PreviewProvider {
112171
"""#
113172
),
114173
],
115-
isReceivingMessage: false
174+
isReceivingMessage: true
116175
))
176+
.padding(8)
177+
.background(Color.contentBackground)
117178
.frame(width: 450, height: 500)
179+
.colorScheme(.light)
118180
}
119181
}

Core/Sources/SuggestionWidget/SuggestionPanelContent/CodeBlockSuggestionPanel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,6 @@ struct CodeBlockSuggestionPanel: View {
8282

8383
ToolBar(viewModel: viewModel, suggestion: suggestion)
8484
}
85+
.xcodeStyleFrame()
8586
}
8687
}

Core/Sources/SuggestionWidget/SuggestionPanelContent/ErrorPanel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ struct ErrorPanel: View {
2525
}
2626
.buttonStyle(.plain)
2727
}
28+
.xcodeStyleFrame()
2829
}
2930
}

Core/Sources/SuggestionWidget/SuggestionPanelView.swift

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,6 @@ struct SuggestionPanelView: View {
7777
}
7878
.frame(maxWidth: .infinity, maxHeight: Style.panelHeight)
7979
.fixedSize(horizontal: false, vertical: true)
80-
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
81-
.overlay(
82-
RoundedRectangle(cornerRadius: 8, style: .continuous)
83-
.stroke(Color.black.opacity(0.3), style: .init(lineWidth: 1))
84-
)
85-
.overlay(
86-
RoundedRectangle(cornerRadius: 7, style: .continuous)
87-
.stroke(Color.white.opacity(0.2), style: .init(lineWidth: 1))
88-
.padding(1)
89-
)
9080
.allowsHitTesting(viewModel.isPanelDisplayed && viewModel.content != .empty)
9181
.preferredColorScheme(viewModel.colorScheme)
9282

@@ -267,3 +257,19 @@ struct SuggestionPanelView_Error_Preview: PreviewProvider {
267257
.frame(width: 450, height: 200)
268258
}
269259
}
260+
261+
struct SuggestionPanelView_Chat_Preview: PreviewProvider {
262+
static var previews: some View {
263+
SuggestionPanelView(viewModel: .init(
264+
content: .chat(.init(
265+
history: [
266+
.init(id: "1", isUser: true, text: "Hello"),
267+
.init(id: "2", isUser: false, text: "Hi"),
268+
.init(id: "3", isUser: true, text: "What's up?"),
269+
]
270+
)),
271+
isPanelDisplayed: true
272+
))
273+
.frame(width: 450, height: 200)
274+
}
275+
}

0 commit comments

Comments
 (0)