Skip to content

Commit 90eea5a

Browse files
committed
Add reference list to modification
1 parent 13e2ced commit 90eea5a

File tree

4 files changed

+226
-15
lines changed

4 files changed

+226
-15
lines changed

Core/Sources/SuggestionWidget/FeatureReducers/PromptToCodePanel.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AppKit
2+
import ChatBasic
23
import ComposableArchitecture
34
import CustomAsyncAlgorithms
45
import Dependencies
@@ -17,7 +18,7 @@ public struct PromptToCodePanel {
1718
public enum FocusField: Equatable {
1819
case textField
1920
}
20-
21+
2122
public enum ClickedButton: Equatable {
2223
case accept
2324
case acceptAndContinue
@@ -42,7 +43,7 @@ public struct PromptToCodePanel {
4243
public var generateDescriptionRequirement: Bool
4344

4445
public var clickedButton: ClickedButton?
45-
46+
4647
public var isActiveDocument: Bool = false
4748

4849
public var snippetPanels: IdentifiedArrayOf<PromptToCodeSnippetPanel.State> {
@@ -94,6 +95,7 @@ public struct PromptToCodePanel {
9495
case acceptAndContinueButtonTapped
9596
case revealFileButtonClicked
9697
case statusUpdated([String])
98+
case referencesUpdated([ChatMessage.Reference])
9799
case snippetPanel(IdentifiedActionOf<PromptToCodeSnippetPanel>)
98100
}
99101

@@ -129,18 +131,18 @@ public struct PromptToCodePanel {
129131
let copiedState = state
130132
let contextInputController = state.contextInputController
131133
state.promptToCodeState.isGenerating = true
132-
state.promptToCodeState
133-
.pushHistory(instruction: .init(
134-
attributedString: contextInputController
135-
.instruction
136-
))
134+
state.promptToCodeState.pushHistory(instruction: .init(
135+
attributedString: contextInputController.instruction
136+
))
137+
state.promptToCodeState.references = []
137138
let snippets = state.promptToCodeState.snippets
138139

139140
return .run { send in
140141
do {
141142
let context = await contextInputController.resolveContext(onStatusChange: {
142143
await send(.statusUpdated($0))
143144
})
145+
await send(.referencesUpdated(context.references))
144146
let agentFactory = context.agent ?? { SimpleModificationAgent() }
145147
_ = try await withThrowingTaskGroup(of: Void.self) { group in
146148
for (index, snippet) in snippets.enumerated() {
@@ -278,6 +280,10 @@ public struct PromptToCodePanel {
278280
case let .statusUpdated(status):
279281
state.promptToCodeState.status = status
280282
return .none
283+
284+
case let .referencesUpdated(references):
285+
state.promptToCodeState.references = references
286+
return .none
281287
}
282288
}
283289

Core/Sources/SuggestionWidget/SuggestionPanelContent/PromptToCodePanelView.swift

Lines changed: 201 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ChatBasic
12
import Cocoa
23
import ComposableArchitecture
34
import MarkdownUI
@@ -205,6 +206,7 @@ extension PromptToCodePanelView {
205206

206207
var body: some View {
207208
HStack {
209+
ReferencesButton(store: store)
208210
StopRespondingButton(store: store)
209211
ActionButtons(store: store)
210212
}
@@ -239,6 +241,43 @@ extension PromptToCodePanelView {
239241
}
240242
}
241243

244+
struct ReferencesButton: View {
245+
let store: StoreOf<PromptToCodePanel>
246+
@State var isReferencesPresented = false
247+
@State var isReferencesHovered = false
248+
249+
var body: some View {
250+
if !store.promptToCodeState.references.isEmpty {
251+
Button(action: {
252+
isReferencesPresented.toggle()
253+
}, label: {
254+
HStack(spacing: 4) {
255+
Image(systemName: "doc.text.magnifyingglass")
256+
Text("\(store.promptToCodeState.references.count)")
257+
}
258+
.padding(8)
259+
.background(
260+
.regularMaterial,
261+
in: RoundedRectangle(cornerRadius: 6, style: .continuous)
262+
)
263+
.overlay {
264+
RoundedRectangle(cornerRadius: 6, style: .continuous)
265+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
266+
}
267+
})
268+
.buttonStyle(.plain)
269+
.popover(isPresented: $isReferencesPresented, arrowEdge: .trailing) {
270+
ReferenceList(store: store)
271+
}
272+
.onHover { hovering in
273+
withAnimation {
274+
isReferencesHovered = hovering
275+
}
276+
}
277+
}
278+
}
279+
}
280+
242281
struct ActionButtons: View {
243282
@Perception.Bindable var store: StoreOf<PromptToCodePanel>
244283
@AppStorage(\.chatModels) var chatModels
@@ -361,10 +400,10 @@ extension PromptToCodePanelView {
361400
}
362401
}
363402
}
364-
403+
365404
struct RevealButton: View {
366405
let store: StoreOf<PromptToCodePanel>
367-
406+
368407
var body: some View {
369408
WithPerceptionTracking {
370409
Button(action: {
@@ -887,6 +926,157 @@ extension PromptToCodePanelView {
887926
}
888927
}
889928
}
929+
930+
struct ReferenceList: View {
931+
@Perception.Bindable var store: StoreOf<PromptToCodePanel>
932+
933+
var body: some View {
934+
WithPerceptionTracking {
935+
ScrollView {
936+
VStack(alignment: .leading, spacing: 8) {
937+
ForEach(
938+
0..<store.promptToCodeState.references.endIndex,
939+
id: \.self
940+
) { index in
941+
WithPerceptionTracking {
942+
let reference = store.promptToCodeState.references[index]
943+
ReferenceButton(reference: reference, isUsed: true, onClick: {})
944+
}
945+
}
946+
}
947+
.padding()
948+
}
949+
.frame(minWidth: 200, maxWidth: 500, maxHeight: 500)
950+
}
951+
}
952+
953+
struct ReferenceButton: View {
954+
let reference: ChatMessage.Reference
955+
let isUsed: Bool
956+
let onClick: () -> Void
957+
958+
var body: some View {
959+
Button(action: onClick) {
960+
VStack(alignment: .leading, spacing: 4) {
961+
HStack(spacing: 4) {
962+
ReferenceIcon(kind: reference.kind)
963+
.layoutPriority(2)
964+
Text(reference.title)
965+
.truncationMode(.middle)
966+
.lineLimit(1)
967+
.layoutPriority(1)
968+
.foregroundStyle(isUsed ? .primary : .secondary)
969+
}
970+
Text(reference.content)
971+
.lineLimit(3)
972+
.truncationMode(.tail)
973+
.foregroundStyle(.tertiary)
974+
.foregroundStyle(isUsed ? .secondary : .tertiary)
975+
}
976+
.padding(.vertical, 4)
977+
.padding(.leading, 4)
978+
.padding(.trailing)
979+
.frame(maxWidth: .infinity, alignment: .leading)
980+
.overlay {
981+
RoundedRectangle(cornerRadius: 4)
982+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
983+
}
984+
}
985+
.buttonStyle(.plain)
986+
}
987+
}
988+
}
989+
990+
struct ReferenceIcon: View {
991+
let kind: ChatMessage.Reference.Kind
992+
993+
var body: some View {
994+
RoundedRectangle(cornerRadius: 4)
995+
.fill({
996+
switch kind {
997+
case let .symbol(symbol, _, _, _):
998+
switch symbol {
999+
case .class:
1000+
Color.purple
1001+
case .struct:
1002+
Color.purple
1003+
case .enum:
1004+
Color.purple
1005+
case .actor:
1006+
Color.purple
1007+
case .protocol:
1008+
Color.purple
1009+
case .extension:
1010+
Color.indigo
1011+
case .case:
1012+
Color.green
1013+
case .property:
1014+
Color.teal
1015+
case .typealias:
1016+
Color.orange
1017+
case .function:
1018+
Color.teal
1019+
case .method:
1020+
Color.blue
1021+
}
1022+
case .text:
1023+
Color.gray
1024+
case .webpage:
1025+
Color.blue
1026+
case .textFile:
1027+
Color.gray
1028+
case .other:
1029+
Color.gray
1030+
case .error:
1031+
Color.red
1032+
}
1033+
}())
1034+
.frame(width: 26, height: 14)
1035+
.overlay(alignment: .center) {
1036+
Group {
1037+
switch kind {
1038+
case let .symbol(symbol, _, _, _):
1039+
switch symbol {
1040+
case .class:
1041+
Text("C")
1042+
case .struct:
1043+
Text("S")
1044+
case .enum:
1045+
Text("E")
1046+
case .actor:
1047+
Text("A")
1048+
case .protocol:
1049+
Text("Pr")
1050+
case .extension:
1051+
Text("Ex")
1052+
case .case:
1053+
Text("K")
1054+
case .property:
1055+
Text("P")
1056+
case .typealias:
1057+
Text("T")
1058+
case .function:
1059+
Text("𝑓")
1060+
case .method:
1061+
Text("M")
1062+
}
1063+
case .text:
1064+
Text("Txt")
1065+
case .webpage:
1066+
Text("Web")
1067+
case .other:
1068+
Text("*")
1069+
case .textFile:
1070+
Text("Txt")
1071+
case .error:
1072+
Text("Err")
1073+
}
1074+
}
1075+
.font(.system(size: 10).monospaced())
1076+
.foregroundColor(.white)
1077+
}
1078+
}
1079+
}
8901080
}
8911081

8921082
// MARK: - Previews
@@ -916,7 +1106,7 @@ extension PromptToCodePanelView {
9161106
end: .init(line: 12, character: 2)
9171107
)
9181108
),
919-
], instruction: .init("Previous instruction")),
1109+
], instruction: .init("Previous instruction"), references: []),
9201110
],
9211111
snippets: [
9221112
.init(
@@ -951,7 +1141,14 @@ extension PromptToCodePanelView {
9511141
),
9521142
],
9531143
extraSystemPrompt: "",
954-
isAttachedToTarget: true
1144+
isAttachedToTarget: true,
1145+
references: [
1146+
ChatMessage.Reference(
1147+
title: "Foo",
1148+
content: "struct Foo { var foo: Int }",
1149+
kind: .symbol(.struct, uri: "file:///path/to/file.txt", startLine: 13, endLine: 13)
1150+
),
1151+
],
9551152
)),
9561153
instruction: nil,
9571154
commandName: "Generate Code"

Tool/Sources/ModificationBasic/ModificationAgent.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,16 @@ public enum ModificationAttachedTarget: Equatable {
101101
public struct ModificationHistoryNode {
102102
public var snippets: IdentifiedArrayOf<ModificationSnippet>
103103
public var instruction: NSAttributedString
104+
public var references: [ChatMessage.Reference]
104105

105106
public init(
106107
snippets: IdentifiedArrayOf<ModificationSnippet>,
107-
instruction: NSAttributedString
108+
instruction: NSAttributedString,
109+
references: [ChatMessage.Reference]
108110
) {
109111
self.snippets = snippets
110112
self.instruction = instruction
113+
self.references = references
111114
}
112115
}
113116

Tool/Sources/ModificationBasic/ModificationState.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ChatBasic
12
import Foundation
23
import IdentifiedCollections
34
import SuggestionBasic
@@ -12,6 +13,7 @@ public struct ModificationState {
1213
public var extraSystemPrompt: String
1314
public var isAttachedToTarget: Bool = true
1415
public var status = [String]()
16+
public var references: [ChatMessage.Reference] = []
1517

1618
public init(
1719
source: Source,
@@ -20,7 +22,8 @@ public struct ModificationState {
2022
extraSystemPrompt: String,
2123
isAttachedToTarget: Bool,
2224
isGenerating: Bool = false,
23-
status: [String] = []
25+
status: [String] = [],
26+
references: [ChatMessage.Reference] = []
2427
) {
2528
self.history = history
2629
self.snippets = snippets
@@ -29,6 +32,7 @@ public struct ModificationState {
2932
self.extraSystemPrompt = extraSystemPrompt
3033
self.source = source
3134
self.status = status
35+
self.references = references
3236
}
3337

3438
public init(
@@ -58,16 +62,17 @@ public struct ModificationState {
5862
public mutating func popHistory() -> NSAttributedString? {
5963
if !history.isEmpty {
6064
let last = history.removeLast()
65+
references = last.references
6166
snippets = last.snippets
6267
let instruction = last.instruction
6368
return instruction
6469
}
65-
70+
6671
return nil
6772
}
6873

6974
public mutating func pushHistory(instruction: NSAttributedString) {
70-
history.append(.init(snippets: snippets, instruction: instruction))
75+
history.append(.init(snippets: snippets, instruction: instruction, references: references))
7176
let oldSnippets = snippets
7277
snippets = IdentifiedArrayOf()
7378
for var snippet in oldSnippets {

0 commit comments

Comments
 (0)