Files
openclaw/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift
Vincent Koc cd7e3df1ea fix(macos): drop Textual from chat packaging
* fix(macos): drop Textual from chat packaging

* fix(macos): declare concurrency extras dependency
2026-06-24 08:31:05 +08:00

73 lines
2.2 KiB
Swift

import Foundation
import SwiftUI
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) {
Text(self.markdownText(processed.cleaned))
.font(self.font)
.foregroundStyle(self.textColor)
.tint(self.linkColor)
.textSelection(.enabled)
.lineSpacing(self.variant == .compact ? 2 : 4)
if !processed.images.isEmpty {
InlineImageList(images: processed.images)
}
}
}
private var linkColor: Color {
self.context == .user ? self.textColor : OpenClawChatTheme.accent
}
private func markdownText(_ markdown: String) -> AttributedString {
let options = AttributedString.MarkdownParsingOptions(
interpretedSyntax: .full,
failurePolicy: .returnPartiallyParsedIfPossible)
return (try? AttributedString(markdown: markdown, options: options)) ?? AttributedString(markdown)
}
}
@MainActor
private struct InlineImageList: View {
let images: [ChatMarkdownPreprocessor.InlineImage]
var body: some View {
ForEach(self.images, id: \.id) { item in
if let img = item.image {
OpenClawPlatformImageFactory.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)
}
}
}
}