transcript-viewer

Pure Ruby renderer for LLM/agent transcript event hashes. It generates a self-contained HTML page with embedded CSS and JavaScript.

Usage

require "transcript_viewer"

html = TranscriptViewer.render(
  session_id: 103,
  events: [
    {
      id: "1",
      parent_id: nil,
      timestamp: Time.now.iso8601,
      type: "message",
      usage: nil,
      data: {
        role: "assistant",
        content: [{ type: "text", text: "Hello" }],
      },
    },
  ],
  export_href: "/admin/agent_session/103/export",
)

File.write("session.html", html)

Composable rendering

Use TranscriptViewer.page when the host application owns the surrounding page chrome. The page normalizes the transcript once and can render its stylesheet, interactive transcript application, and JavaScript separately:

page = TranscriptViewer.page(session_id: 103, events: events)

page.stylesheet # CSS string
page.app_html   # Sidebar and transcript content
page.javascript # Script tag for transcript interactions
page.render     # The complete standalone HTML document

Pass trusted host markup with before_html: and after_html: to place application controls above and below the transcript within app_html:

page = TranscriptViewer.page(
  session_id: 103,
  events: events,
  before_html: "<section>Run controls</section>",
  after_html: "<form>Continue</form>",
)

The host is responsible for placing the stylesheet in its document head and the JavaScript after app_html.

Custom tool templates

Consumers can register an ERB for an exact tool name. Registration is normally done once during application startup:

TranscriptViewer.configure do |config|
  config.register_tool_template(
    "fetch_pull_requests",
    File.expand_path("templates/tools/fetch_pull_requests.html.erb", __dir__),
  )
end

A tool template receives a tool local and can use renderer helpers such as h and pretty_json:

<section class="pull-request-tool" data-status="<%= h(tool.status) %>">
  <h2><%= h(tool.name) %></h2>
  <pre><%= h(pretty_json(tool.input)) %></pre>

  <% if tool.result_json %>
    <pre><%= h(pretty_json(tool.result_json)) %></pre>
  <% elsif tool.result_text != "" %>
    <pre><%= h(tool.result_text) %></pre>
  <% end %>
</section>

The tool execution interface includes:

  • name, id, input (also available as arguments)
  • result, result_content, result_text, result_json, and images
  • status, pending?, success?, and error?
  • call and result_entry for access to the normalized underlying hashes

Templates can also be supplied for one render. These override globally registered templates with the same tool name:

TranscriptViewer.render(
  session_id: 103,
  events: events,
  tool_templates: {
    "fetch_pull_requests" => "/path/to/fetch_pull_requests.html.erb",
  },
)

Unregistered tools retain the built-in display. Server tools also use their existing built-in display for now.

Rails adapter example

This gem does not depend on Rails or ActiveSupport. In a Rails app, keep database access in the app and pass plain hashes into the renderer:

session = AgentSession.find(params[:session_id])

events = session.agent_session_events.order(:position).map do |event|
  {
    id: event.event_id,
    parent_id: event.parent_id,
    timestamp: event.timestamp&.iso8601,
    type: event.event_type,
    usage: event.usage_json,
    data: event.data_json,
  }
end

html = TranscriptViewer.render(
  session_id: session.id,
  events: events,
  export_href: admin_export_agent_session_path(session.id),
  forked_transcript: params[:forked_transcript],
  ancestor_lookup: lambda do |event_id|
    event = AgentSessionEvent.find_by(event_id: event_id)
    next nil unless event

    {
      id: event.event_id,
      parent_id: event.parent_id,
      timestamp: event.timestamp&.iso8601,
      type: event.event_type,
      usage: event.usage_json,
      data: event.data_json,
    }
  end,
)

render html: html.html_safe, layout: false

Supported event types

  • message
  • reasoning_change
  • model_change
  • compaction

Message content supports text, thinking/reasoning, tool calls/results, and server tool use/results.