Skip to content

Commit ba592c2

Browse files
committed
Support displaying references in chat panel
1 parent 68ea73b commit ba592c2

File tree

2 files changed

+163
-57
lines changed

2 files changed

+163
-57
lines changed

Core/Sources/ChatGPTChatTab/Chat.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,42 @@ import OpenAIService
55
import Preferences
66

77
public struct DisplayedChatMessage: Equatable {
8-
public enum Role {
8+
public enum Role: Equatable {
99
case user
1010
case assistant
1111
case function
1212
case ignored
1313
}
14+
15+
public struct Reference: Equatable {
16+
public var title: String
17+
public var subtitle: String
18+
public var uri: String
19+
20+
public init(title: String, subtitle: String, uri: String) {
21+
self.title = title
22+
self.subtitle = subtitle
23+
self.uri = uri
24+
}
25+
}
1426

1527
public var id: String
1628
public var role: Role
1729
public var text: String
18-
public var references: [ChatMessage.Reference] = []
30+
public var references: [Reference] = []
1931

20-
public init(id: String, role: Role, text: String, references: [ChatMessage.Reference] = []) {
32+
public init(id: String, role: Role, text: String, references: [Reference]) {
2133
self.id = id
2234
self.role = role
2335
self.text = text
2436
self.references = references
2537
}
2638
}
2739

40+
private var isPreview: Bool {
41+
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
42+
}
43+
2844
struct Chat: ReducerProtocol {
2945
public typealias MessageID = String
3046

@@ -92,6 +108,7 @@ struct Chat: ReducerProtocol {
92108
switch action {
93109
case .appear:
94110
return .run { send in
111+
if isPreview { return }
95112
await send(.observeChatService)
96113
await send(.historyChanged)
97114
await send(.isReceivingMessageChanged)
@@ -250,7 +267,9 @@ struct Chat: ReducerProtocol {
250267
}
251268
}(),
252269
text: message.summary ?? message.content ?? "",
253-
references: message.references
270+
references: message.references.map {
271+
.init(title: $0.title, subtitle: $0.subTitle, uri: $0.uri)
272+
}
254273
)
255274
}
256275

Core/Sources/ChatGPTChatTab/ChatPanel.swift

Lines changed: 140 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,19 @@ struct ChatHistory: View {
245245
))
246246
.padding(.vertical, 4)
247247
case .assistant:
248-
BotMessage(id: message.id, text: text, chat: chat)
249-
.listRowInsets(EdgeInsets(
250-
top: 0,
251-
leading: -8,
252-
bottom: 0,
253-
trailing: -8
254-
))
255-
.padding(.vertical, 4)
248+
BotMessage(
249+
id: message.id,
250+
text: text,
251+
references: message.references,
252+
chat: chat
253+
)
254+
.listRowInsets(EdgeInsets(
255+
top: 0,
256+
leading: -8,
257+
bottom: 0,
258+
trailing: -8
259+
))
260+
.padding(.vertical, 4)
256261
case .function:
257262
FunctionMessage(id: message.id, text: text)
258263
case .ignored:
@@ -427,50 +432,82 @@ private struct UserMessage: View {
427432
private struct BotMessage: View {
428433
let id: String
429434
let text: String
435+
let references: [DisplayedChatMessage.Reference]
430436
let chat: StoreOf<Chat>
431437
@Environment(\.colorScheme) var colorScheme
432438
@AppStorage(\.chatFontSize) var chatFontSize
433439
@AppStorage(\.chatCodeFontSize) var chatCodeFontSize
434440

441+
@State var isReferencesPresented = false
442+
@State var isReferencesHovered = false
443+
435444
var body: some View {
436445
HStack(alignment: .bottom, spacing: 2) {
437-
Markdown(text)
438-
.textSelection(.enabled)
439-
.markdownTheme(.custom(fontSize: chatFontSize))
440-
.markdownCodeSyntaxHighlighter(
441-
ChatCodeSyntaxHighlighter(
442-
brightMode: colorScheme != .dark,
443-
fontSize: chatCodeFontSize
444-
)
445-
)
446-
.frame(alignment: .trailing)
447-
.padding()
448-
.background {
449-
RoundedCorners(tl: r, tr: r, bl: 0, br: r)
450-
.fill(Color.contentBackground)
446+
VStack(alignment: .leading, spacing: 16) {
447+
if !references.isEmpty {
448+
Button(action: {
449+
isReferencesPresented.toggle()
450+
}, label: {
451+
HStack(spacing: 4) {
452+
Image(systemName: "plus.circle")
453+
Text("Used \(references.count) references")
454+
}
455+
.padding(8)
456+
.background {
457+
RoundedRectangle(cornerRadius: r - 4)
458+
.foregroundStyle(Color(isReferencesHovered ? .black : .clear))
459+
460+
}
461+
.overlay {
462+
RoundedRectangle(cornerRadius: r - 4)
463+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
464+
}
465+
.foregroundStyle(.secondary)
466+
})
467+
.buttonStyle(.plain)
468+
.popover(isPresented: $isReferencesPresented, arrowEdge: .trailing) {
469+
ReferenceList(references: references)
470+
}
451471
}
452-
.overlay {
453-
RoundedCorners(tl: r, tr: r, bl: 0, br: r)
454-
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
472+
473+
Markdown(text)
474+
.textSelection(.enabled)
475+
.markdownTheme(.custom(fontSize: chatFontSize))
476+
.markdownCodeSyntaxHighlighter(
477+
ChatCodeSyntaxHighlighter(
478+
brightMode: colorScheme != .dark,
479+
fontSize: chatCodeFontSize
480+
)
481+
)
482+
}
483+
.frame(alignment: .trailing)
484+
.padding()
485+
.background {
486+
RoundedCorners(tl: r, tr: r, bl: 0, br: r)
487+
.fill(Color.contentBackground)
488+
}
489+
.overlay {
490+
RoundedCorners(tl: r, tr: r, bl: 0, br: r)
491+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
492+
}
493+
.padding(.leading, 8)
494+
.shadow(color: .black.opacity(0.1), radius: 2)
495+
.contextMenu {
496+
Button("Copy") {
497+
NSPasteboard.general.clearContents()
498+
NSPasteboard.general.setString(text, forType: .string)
455499
}
456-
.padding(.leading, 8)
457-
.shadow(color: .black.opacity(0.1), radius: 2)
458-
.contextMenu {
459-
Button("Copy") {
460-
NSPasteboard.general.clearContents()
461-
NSPasteboard.general.setString(text, forType: .string)
462-
}
463500

464-
Button("Set as Extra System Prompt") {
465-
chat.send(.setAsExtraPromptButtonTapped(id))
466-
}
501+
Button("Set as Extra System Prompt") {
502+
chat.send(.setAsExtraPromptButtonTapped(id))
503+
}
467504

468-
Divider()
505+
Divider()
469506

470-
Button("Delete") {
471-
chat.send(.deleteMessageButtonTapped(id))
472-
}
507+
Button("Delete") {
508+
chat.send(.deleteMessageButtonTapped(id))
473509
}
510+
}
474511

475512
CopyButton {
476513
NSPasteboard.general.clearContents()
@@ -482,6 +519,26 @@ private struct BotMessage: View {
482519
}
483520
}
484521

522+
struct ReferenceList: View {
523+
let references: [DisplayedChatMessage.Reference]
524+
var body: some View {
525+
ScrollView {
526+
VStack {
527+
ForEach(0..<references.endIndex, id: \.self) { index in
528+
let reference = references[index]
529+
HStack(spacing: 8) {
530+
Text(reference.title)
531+
Text(reference.subtitle)
532+
.foregroundStyle(.secondary)
533+
}
534+
}
535+
}
536+
.padding()
537+
.frame(maxHeight: 500)
538+
}
539+
}
540+
}
541+
485542
struct FunctionMessage: View {
486543
let id: String
487544
let text: String
@@ -539,7 +596,8 @@ struct ChatPanelInputArea: View {
539596
chat,
540597
removeDuplicates: {
541598
$0.typedMessage == $1.typedMessage && $0.focusedField == $1.focusedField
542-
}) { viewStore in
599+
}
600+
) { viewStore in
543601
AutoresizingCustomTextEditor(
544602
text: viewStore.$typedMessage,
545603
font: .systemFont(ofSize: 14),
@@ -680,7 +738,8 @@ struct ChatPanel_Preview: PreviewProvider {
680738
.init(
681739
id: "1",
682740
role: .user,
683-
text: "**Hello**"
741+
text: "**Hello**",
742+
references: []
684743
),
685744
.init(
686745
id: "2",
@@ -690,18 +749,45 @@ struct ChatPanel_Preview: PreviewProvider {
690749
func foo() {}
691750
```
692751
**Hey**! What can I do for you?**Hey**! What can I do for you?**Hey**! What can I do for you?**Hey**! What can I do for you?
693-
"""
752+
""",
753+
references: [
754+
.init(
755+
title: "Hello Hello Hello Hello",
756+
subtitle: "Hi Hi Hi Hi",
757+
uri: "https://google.com"
758+
),
759+
]
760+
),
761+
.init(
762+
id: "7",
763+
role: .ignored,
764+
text: "Ignored",
765+
references: []
766+
),
767+
.init(
768+
id: "6",
769+
role: .function,
770+
text: """
771+
Searching for something...
772+
- abc
773+
- [def](https://1.com)
774+
> hello
775+
> hi
776+
""",
777+
references: []
778+
),
779+
.init(
780+
id: "5",
781+
role: .assistant,
782+
text: "Yooo",
783+
references: []
784+
),
785+
.init(
786+
id: "4",
787+
role: .user,
788+
text: "Yeeeehh",
789+
references: []
694790
),
695-
.init(id: "7", role: .ignored, text: "Ignored"),
696-
.init(id: "6", role: .function, text: """
697-
Searching for something...
698-
- abc
699-
- [def](https://1.com)
700-
> hello
701-
> hi
702-
"""),
703-
.init(id: "5", role: .assistant, text: "Yooo"),
704-
.init(id: "4", role: .user, text: "Yeeeehh"),
705791
.init(
706792
id: "3",
707793
role: .user,
@@ -718,7 +804,8 @@ struct ChatPanel_Preview: PreviewProvider {
718804
```objectivec
719805
- (void)bar {}
720806
```
721-
"""#
807+
"""#,
808+
references: []
722809
),
723810
]
724811

0 commit comments

Comments
 (0)