import SwiftUI import Textual public enum ChatMarkdownVariant: String, CaseIterable, Sendable { case standard case compact } @MainActor struct ChatMarkdownRenderer: View { enum Context { case user case assistant } let text: String let context: Context let variant: ChatMarkdownVariant let font: Font let textColor: Color var body: some View { let processed = ChatMarkdownPreprocessor.preprocess(markdown: self.text) VStack(alignment: .leading, spacing: 10) { StructuredText(markdown: processed.cleaned) .modifier(ChatMarkdownStyle( variant: self.variant, context: self.context, font: self.font, textColor: self.textColor)) if !processed.images.isEmpty { InlineImageList(images: processed.images) } } } } private struct ChatMarkdownStyle: ViewModifier { let variant: ChatMarkdownVariant let context: ChatMarkdownRenderer.Context let font: Font let textColor: Color func body(content: Content) -> some View { Group { if self.variant == .compact { content.textual.structuredTextStyle(.default) } else { content.textual.structuredTextStyle(.gitHub) } } .font(self.font) .foregroundStyle(self.textColor) .textual.inlineStyle(self.inlineStyle) .textual.textSelection(.enabled) } private var inlineStyle: InlineStyle { let linkColor: Color = self.context == .user ? self.textColor : .accentColor let codeScale: CGFloat = self.variant == .compact ? 0.85 : 0.9 return InlineStyle() .code(.monospaced, .fontScale(codeScale)) .link(.foregroundColor(linkColor)) } } @MainActor private struct InlineImageList: View { let images: [ChatMarkdownPreprocessor.InlineImage] var body: some View { ForEach(images, id: \.id) { item in if let img = item.image { ClawdbotPlatformImageFactory.image(img) .resizable() .scaledToFit() .frame(maxHeight: 260) .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) .strokeBorder(Color.white.opacity(0.12), lineWidth: 1)) } else { Text(item.label.isEmpty ? "Image" : item.label) .font(.footnote) .foregroundStyle(.secondary) } } } }