HuggingChat-Mac/Views/AttachmentView.swift (101 lines of code) (raw):
//
// AttachmentView.swift
// HuggingChat-Mac
//
// Created by Cyril Zakka on 9/26/24.
//
import AppKit
import SwiftUI
import UniformTypeIdentifiers
enum AttachmentContent {
case text(String)
case image(NSImage)
case pdf(Data)
}
struct LLMAttachment: Identifiable, Equatable {
var id = UUID()
var filename: String
var fileExtension: String
var url: URL?
var fileIcon: NSImage?
var fileType: UTType
var content: AttachmentContent
static func == (lhs: LLMAttachment, rhs: LLMAttachment) -> Bool {
lhs.filename == rhs.filename &&
lhs.fileExtension == rhs.fileExtension &&
lhs.url == rhs.url &&
lhs.fileType == rhs.fileType
}
}
struct AttachmentView: View {
@Binding var allAttachments: [LLMAttachment]
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 5) {
ForEach(allAttachments, id: \.id) { attachment in
AttachmentPill(allAttachments: $allAttachments, attachment: attachment)
}
}
}
.scrollIndicators(.never)
.scrollClipDisabled()
}
}
struct AttachmentPill: View {
@Binding var allAttachments: [LLMAttachment]
var attachment: LLMAttachment?
@State private var showRemoveButton: Bool = false
var body: some View {
if let attachment {
ZStack {
HStack(alignment: .center, spacing: 5) {
if let icon = attachment.fileIcon {
Image(nsImage: icon)
.resizable()
.scaledToFit()
.mask(RoundedRectangle(cornerRadius: 5))
.frame(width: 30, height: 30)
} else {
Image(nsImage: NSWorkspace.shared.icon(for: attachment.fileType))
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
Text("\(attachment.filename)")
.font(ThemingEngine.shared.currentTheme.markdownFont?.footnote ?? .footnote)
.lineLimit(2)
.frame(maxWidth: .infinity, alignment: .leading)
Button(action: {
DispatchQueue.main.async {
allAttachments.removeAll { $0.id == attachment.id }
}
}, label: {
Label("", systemImage: "xmark.circle.fill")
.labelStyle(.iconOnly)
})
.buttonStyle(.plain)
.foregroundStyle(.tertiary)
.opacity(showRemoveButton ? 1:0)
}
}
.padding(.horizontal, 5)
.frame(height: 45)
.frame(width: 160)
.background(.gray.opacity(0.3))
.clipShape(RoundedRectangle(cornerRadius: 8))
.fixedSize()
.onHover { state in
showRemoveButton = state
}
}
}
}
#Preview {
@Previewable @State var isTranscribing: Bool = false
InputView(isLocal: true, prompt: .constant(""), isSecondaryTextFieldVisible: .constant(false), animatablePrompt: .constant(""), isMainTextFieldVisible: .constant(true), allAttachments: .constant([LLMAttachment(
filename: "Sample Document.png",
fileExtension: "png",
url: URL(string: "file:///sample.png"),
fileIcon: NSImage(named: "huggy.bp")!,
fileType: .image,
content: .image(NSImage(named: "huggy.bp")!)
)]), startLoadingAnimation: .constant(true), isTranscribing: $isTranscribing)
.environment(ModelManager())
.environment(\.colorScheme, .dark)
.environment(ConversationViewModel())
.environment(AudioModelManager())
// AttachmentPill(allAttachments: .constant([]))
}