@@ -13,6 +13,8 @@ enum ChatMarkdownPreprocessor {
1313 " Chat history since last reply (untrusted, for context): " ,
1414 ]
1515
16+ private static let markdownImagePattern = #"!\[([^\]]*)\]\(([^)]+)\)"#
17+
1618 struct InlineImage : Identifiable {
1719 let id = UUID ( )
1820 let label : String
@@ -27,8 +29,7 @@ enum ChatMarkdownPreprocessor {
2729 static func preprocess( markdown raw: String ) -> Result {
2830 let withoutContextBlocks = self . stripInboundContextBlocks ( raw)
2931 let withoutTimestamps = self . stripPrefixedTimestamps ( withoutContextBlocks)
30- let pattern = #"!\[([^\]]*)\]\((data:image\/[^;]+;base64,[^)]+)\)"#
31- guard let re = try ? NSRegularExpression ( pattern: pattern) else {
32+ guard let re = try ? NSRegularExpression ( pattern: self . markdownImagePattern) else {
3233 return Result ( cleaned: self . normalize ( withoutTimestamps) , images: [ ] )
3334 }
3435
@@ -44,24 +45,41 @@ enum ChatMarkdownPreprocessor {
4445 for match in matches. reversed ( ) {
4546 guard match. numberOfRanges >= 3 else { continue }
4647 let label = ns. substring ( with: match. range ( at: 1 ) )
47- let dataURL = ns. substring ( with: match. range ( at: 2 ) )
48-
49- let image : OpenClawPlatformImage ? = {
50- guard let comma = dataURL. firstIndex ( of: " , " ) else { return nil }
51- let b64 = String ( dataURL [ dataURL. index ( after: comma) ... ] )
52- guard let data = Data ( base64Encoded: b64) else { return nil }
53- return OpenClawPlatformImage ( data: data)
54- } ( )
55- images. append ( InlineImage ( label: label, image: image) )
48+ let source = ns. substring ( with: match. range ( at: 2 ) )
5649
5750 let start = cleaned. index ( cleaned. startIndex, offsetBy: match. range. location)
5851 let end = cleaned. index ( start, offsetBy: match. range. length)
59- cleaned. replaceSubrange ( start..< end, with: " " )
52+ if let inlineImage = self . inlineImage ( label: label, source: source) {
53+ images. append ( inlineImage)
54+ cleaned. replaceSubrange ( start..< end, with: " " )
55+ } else {
56+ cleaned. replaceSubrange ( start..< end, with: self . fallbackImageLabel ( label) )
57+ }
6058 }
6159
6260 return Result ( cleaned: self . normalize ( cleaned) , images: images. reversed ( ) )
6361 }
6462
63+ private static func inlineImage( label: String , source: String ) -> InlineImage ? {
64+ let trimmed = source. trimmingCharacters ( in: . whitespacesAndNewlines)
65+ guard let comma = trimmed. firstIndex ( of: " , " ) ,
66+ trimmed [ ..< comma] . range (
67+ of: #"^data:image\/[^;]+;base64$"# ,
68+ options: [ . regularExpression, . caseInsensitive] ) != nil
69+ else {
70+ return nil
71+ }
72+
73+ let b64 = String ( trimmed [ trimmed. index ( after: comma) ... ] )
74+ let image = Data ( base64Encoded: b64) . flatMap ( OpenClawPlatformImage . init ( data: ) )
75+ return InlineImage ( label: label, image: image)
76+ }
77+
78+ private static func fallbackImageLabel( _ label: String ) -> String {
79+ let trimmed = label. trimmingCharacters ( in: . whitespacesAndNewlines)
80+ return trimmed. isEmpty ? " image " : trimmed
81+ }
82+
6583 private static func stripInboundContextBlocks( _ raw: String ) -> String {
6684 guard self . inboundContextHeaders. contains ( where: raw. contains) else {
6785 return raw
0 commit comments