import ComposableArchitecture import ChatService import Foundation import MarkdownUI import SharedUIComponents import SwiftUI struct BotMessage: View { var r: Double { messageBubbleCornerRadius } let id: String let text: String let references: [DisplayedChatMessage.Reference] let chat: StoreOf @Environment(\.colorScheme) var colorScheme @State var isReferencesPresented = false @State var isReferencesHovered = false var body: some View { HStack(alignment: .bottom) { VStack(alignment: .leading, spacing: 0) { HStack(spacing: 0) { Spacer() // Pushes the buttons to the right UpvoteButton { rating in chat.send(.upvote(id, rating)) } DownvoteButton { rating in chat.send(.downvote(id, rating)) } CopyButton { NSPasteboard.general.clearContents() NSPasteboard.general.setString(text, forType: .string) chat.send(.copyCode(id)) } } if !references.isEmpty { Button(action: { isReferencesPresented.toggle() }, label: { HStack(spacing: 4) { Image(systemName: "plus.circle") Text("Used \(references.count) references") } .padding(8) .background { RoundedRectangle(cornerRadius: r - 4) .foregroundStyle(Color(isReferencesHovered ? .black : .clear)) } .overlay { RoundedRectangle(cornerRadius: r - 4) .stroke(Color(nsColor: .separatorColor), lineWidth: 1) } .foregroundStyle(.secondary) }) .buttonStyle(.plain) .popover(isPresented: $isReferencesPresented, arrowEdge: .trailing) { ReferenceList(references: references, chat: chat) } } ThemedMarkdownText(text) } .frame(alignment: .trailing) .padding() .background { RoundedCorners(tl: r, tr: r, bl: 0, br: r) .fill(Color.contentBackground) } .overlay { RoundedCorners(tl: r, tr: r, bl: 0, br: r) .stroke(Color(nsColor: .separatorColor), lineWidth: 1) } .padding(.leading, 8) .shadow(color: .black.opacity(0.05), radius: 6) .contextMenu { Button("Copy") { NSPasteboard.general.clearContents() NSPasteboard.general.setString(text, forType: .string) } Button("Set as Extra System Prompt") { chat.send(.setAsExtraPromptButtonTapped(id)) } Divider() Button("Delete") { chat.send(.deleteMessageButtonTapped(id)) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.trailing, 2) } } struct ReferenceList: View { let references: [DisplayedChatMessage.Reference] let chat: StoreOf var body: some View { WithPerceptionTracking { ScrollView { VStack(alignment: .leading, spacing: 8) { ForEach(0..