|
| 1 | +import ChatBasic |
1 | 2 | import Cocoa |
2 | 3 | import ComposableArchitecture |
3 | 4 | import MarkdownUI |
@@ -205,6 +206,7 @@ extension PromptToCodePanelView { |
205 | 206 |
|
206 | 207 | var body: some View { |
207 | 208 | HStack { |
| 209 | + ReferencesButton(store: store) |
208 | 210 | StopRespondingButton(store: store) |
209 | 211 | ActionButtons(store: store) |
210 | 212 | } |
@@ -239,6 +241,43 @@ extension PromptToCodePanelView { |
239 | 241 | } |
240 | 242 | } |
241 | 243 |
|
| 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 | + |
242 | 281 | struct ActionButtons: View { |
243 | 282 | @Perception.Bindable var store: StoreOf<PromptToCodePanel> |
244 | 283 | @AppStorage(\.chatModels) var chatModels |
@@ -361,10 +400,10 @@ extension PromptToCodePanelView { |
361 | 400 | } |
362 | 401 | } |
363 | 402 | } |
364 | | - |
| 403 | + |
365 | 404 | struct RevealButton: View { |
366 | 405 | let store: StoreOf<PromptToCodePanel> |
367 | | - |
| 406 | + |
368 | 407 | var body: some View { |
369 | 408 | WithPerceptionTracking { |
370 | 409 | Button(action: { |
@@ -887,6 +926,157 @@ extension PromptToCodePanelView { |
887 | 926 | } |
888 | 927 | } |
889 | 928 | } |
| 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 | + } |
890 | 1080 | } |
891 | 1081 |
|
892 | 1082 | // MARK: - Previews |
@@ -916,7 +1106,7 @@ extension PromptToCodePanelView { |
916 | 1106 | end: .init(line: 12, character: 2) |
917 | 1107 | ) |
918 | 1108 | ), |
919 | | - ], instruction: .init("Previous instruction")), |
| 1109 | + ], instruction: .init("Previous instruction"), references: []), |
920 | 1110 | ], |
921 | 1111 | snippets: [ |
922 | 1112 | .init( |
@@ -951,7 +1141,14 @@ extension PromptToCodePanelView { |
951 | 1141 | ), |
952 | 1142 | ], |
953 | 1143 | 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 | + ], |
955 | 1152 | )), |
956 | 1153 | instruction: nil, |
957 | 1154 | commandName: "Generate Code" |
|
0 commit comments