import ComposableArchitecture import ChatService import Foundation import MarkdownUI import SharedUIComponents import SwiftUI import ConversationServiceProvider struct BotMessage: View { var r: Double { messageBubbleCornerRadius } let id: String let text: String let references: [ConversationReference] let followUp: ConversationFollowUp? let errorMessage: String? let chat: StoreOf @Environment(\.colorScheme) var colorScheme @AppStorage(\.chatFontSize) var chatFontSize @State var isReferencesPresented = false struct ResponseToolBar: View { let id: String let chat: StoreOf let text: String var body: some View { HStack(spacing: 4) { 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)) } Spacer() // Pushes the buttons to the left } } } struct ReferenceButton: View { var r: Double { messageBubbleCornerRadius } let references: [ConversationReference] let chat: StoreOf @Binding var isReferencesPresented: Bool @State var isReferencesHovered = false @AppStorage(\.chatFontSize) var chatFontSize func MakeReferenceTitle(references: [ConversationReference]) -> String { guard !references.isEmpty else { return "" } let count = references.count let title = count > 1 ? "Used \(count) references" : "Used \(count) reference" return title } var body: some View { VStack(alignment: .leading, spacing: 8) { Button(action: { isReferencesPresented.toggle() }, label: { HStack(spacing: 4) { Image(systemName: isReferencesPresented ? "chevron.down" : "chevron.right") Text(MakeReferenceTitle(references: references)) .font(.system(size: chatFontSize)) } .background { RoundedRectangle(cornerRadius: r - 4) .fill(isReferencesHovered ? Color.gray.opacity(0.1) : Color.clear) } .foregroundStyle(.secondary) }) .buttonStyle(HoverButtonStyle()) .accessibilityValue(isReferencesPresented ? "Collapse" : "Expand") if isReferencesPresented { ReferenceList(references: references, chat: chat) .background( RoundedRectangle(cornerRadius: 5) .stroke(Color.gray, lineWidth: 0.2) ) } } } } var body: some View { HStack { VStack(alignment: .leading, spacing: 8) { CopilotMessageHeader() .padding(.leading, 6) if !references.isEmpty { WithPerceptionTracking { ReferenceButton( references: references, chat: chat, isReferencesPresented: $isReferencesPresented ) } } ThemedMarkdownText(text: text, chat: chat) if errorMessage != nil { HStack(spacing: 4) { Image(systemName: "info.circle") Text(errorMessage!) .font(.system(size: chatFontSize)) } } ResponseToolBar(id: id, chat: chat, text: text) } .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)) } } } } } struct ReferenceList: View { let references: [ConversationReference] let chat: StoreOf private let maxVisibleItems: Int = 6 @State private var itemHeight: CGFloat = 16 @AppStorage(\.chatFontSize) var chatFontSize struct ReferenceView: View { let references: [ConversationReference] let chat: StoreOf @AppStorage(\.chatFontSize) var chatFontSize @Binding var itemHeight: CGFloat var body: some View { VStack(alignment: .leading, spacing: 0) { ForEach(0..