Skip to content

Commit e83ad9e

Browse files
committed
Truncate base64 data URIs in inspect/to_html output
Long base64-encoded images made console and HTML output unreadable. Now displays: "data:image/png;base64,iVBORw0K... (12345 chars)" - Add AI.truncate_data_uris helper to recursively truncate in nested structures - Apply truncation in Chat#inspectable_attributes and Message#inspect - Add specs for truncation behavior
1 parent 88b8fa8 commit e83ad9e

7 files changed

Lines changed: 91 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.5.3] - 2026-02-03
9+
10+
### Added
11+
12+
- **Truncate base64 data URIs in output**: Long base64-encoded images in messages are now truncated in `inspect` and `to_html` output for readability. Displays as `"data:image/png;base64,iVBORw0K... (12345 chars)"`.
13+
814
## [0.5.2] - 2026-02-03
915

1016
### Fixed

ai-chat.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Gem::Specification.new do |spec|
44
spec.name = "ai-chat"
5-
spec.version = "0.5.2"
5+
spec.version = "0.5.3"
66
spec.authors = ["Raghu Betina", "Jelani Woods"]
77
spec.email = ["raghu@firstdraft.com", "jelani@firstdraft.com"]
88
spec.homepage = "https://github.com/firstdraft/ai-chat"

lib/ai-chat.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ def self.wrap_html(content)
1313
def self.amazing_print(object, **options)
1414
AmazingPrint::Inspector.new(**options).awesome(object)
1515
end
16+
17+
# Recursively truncate base64 data URIs in nested structures for cleaner output
18+
def self.truncate_data_uris(obj)
19+
case obj
20+
when Hash
21+
obj.transform_values { |v| truncate_data_uris(v) }
22+
when Array
23+
obj.map { |v| truncate_data_uris(v) }
24+
when String
25+
truncate_data_uri(obj)
26+
else
27+
obj
28+
end
29+
end
30+
31+
def self.truncate_data_uri(str)
32+
return str unless str.is_a?(String) && str.start_with?("data:") && str.include?(";base64,")
33+
34+
match = str.match(/\A(data:[^;]+;base64,)(.+)\z/)
35+
return str unless match
36+
37+
prefix = match[1]
38+
data = match[2]
39+
"#{prefix}#{data[0, 20]}... (#{data.length} chars)"
40+
end
1641
end
1742

1843
require_relative "ai/message"

lib/ai/chat.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ def inspectable_attributes
208208
attrs << [:@conversation_id, @conversation_id]
209209
attrs << [:@last_response_id, @last_response_id] if @last_response_id
210210

211-
# 3. Messages (the main content, without response details)
212-
display_messages = @messages.map { |msg| msg.except(:response) }
211+
# 3. Messages (the main content, without response details, with truncated data URIs)
212+
display_messages = @messages.map { |msg| AI.truncate_data_uris(msg.except(:response)) }
213213
attrs << [:@messages, display_messages]
214214

215215
# 4. Optional features (only if enabled/changed from default)

lib/ai/message.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
module AI
44
class Message < Hash
55
def inspect
6-
AI.amazing_print(self, plain: !$stdout.tty?, index: false)
6+
AI.amazing_print(display_hash, plain: !$stdout.tty?, index: false)
77
end
88

99
def pretty_inspect
@@ -17,7 +17,13 @@ def pretty_print(q)
1717
end
1818

1919
def to_html
20-
AI.wrap_html(AI.amazing_print(self, html: true, index: false))
20+
AI.wrap_html(AI.amazing_print(display_hash, html: true, index: false))
21+
end
22+
23+
private
24+
25+
def display_hash
26+
AI.truncate_data_uris(to_h)
2127
end
2228
end
2329
end

spec/unit/chat_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,42 @@
168168
expect(attr_names).to include(:@web_search)
169169
expect(attr_names).to include(:@schema)
170170
end
171+
172+
it "truncates base64 data URIs in message content" do
173+
base64_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk"
174+
chat.add([
175+
{type: "input_text", text: "What is this?"},
176+
{type: "input_image", image_url: base64_image}
177+
], role: "user")
178+
179+
attrs = chat.inspectable_attributes
180+
messages_attr = attrs.find { |name, _| name == :@messages }
181+
display_messages = messages_attr[1]
182+
image_content = display_messages[0][:content][1]
183+
184+
expect(image_content[:image_url]).to eq("data:image/png;base64,iVBORw0KGgoAAAANSUhE... (60 chars)")
185+
end
186+
187+
it "does not truncate regular strings" do
188+
chat.add("Hello, this is a normal message", role: "user")
189+
190+
attrs = chat.inspectable_attributes
191+
messages_attr = attrs.find { |name, _| name == :@messages }
192+
display_messages = messages_attr[1]
193+
194+
expect(display_messages[0][:content]).to eq("Hello, this is a normal message")
195+
end
196+
197+
it "does not truncate non-base64 data URIs" do
198+
data_uri = "data:text/plain,Hello%20World"
199+
chat.add([{type: "input_text", text: data_uri}], role: "user")
200+
201+
attrs = chat.inspectable_attributes
202+
messages_attr = attrs.find { |name, _| name == :@messages }
203+
display_messages = messages_attr[1]
204+
205+
expect(display_messages[0][:content][0][:text]).to eq(data_uri)
206+
end
171207
end
172208

173209
describe "#inspect" do

spec/unit/message_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@
3636

3737
expect(message.inspect).not_to eq({role: "user", content: "Hello"}.inspect)
3838
end
39+
40+
it "truncates base64 data URIs in content" do
41+
base64_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk"
42+
message = AI::Message[
43+
role: "user",
44+
content: [
45+
{type: "input_text", text: "What is this?"},
46+
{type: "input_image", image_url: base64_image}
47+
]
48+
]
49+
50+
expect(message.inspect).to include("data:image/png;base64,iVBORw0KGgoAAAANSUhE... (60 chars)")
51+
end
3952
end
4053

4154
describe "#pretty_inspect" do

0 commit comments

Comments
 (0)