Skip to content

Instantly share code, notes, and snippets.

@alexjsteffen
Last active April 19, 2026 00:01
Show Gist options
  • Select an option

  • Save alexjsteffen/4af438766bea350ed4a524e6f7cc1717 to your computer and use it in GitHub Desktop.

Select an option

Save alexjsteffen/4af438766bea350ed4a524e6f7cc1717 to your computer and use it in GitHub Desktop.
A vibecoded python script to convert OpenRouter json output into pdf, odt, and a html file
import json
import subprocess
import os
import glob
import html
def clean_text_for_markdown(text):
"""Cleans up literal escape characters so LaTeX doesn't crash."""
if isinstance(text, str):
return text.replace('\\n', '\n')
return str(text)
def parse_json_to_markdown(json_filepath, md_filepath, data):
"""Reads the JSON log and formats it into a clean Markdown file."""
trace = data.get('trace', {})
output_data = trace.get('output', {})
observations = data.get('observations', [{}])[0] if data.get('observations') else {}
md_content = f"# OpenRouter Trace Log\n\n"
md_content += f"**Trace ID:** {trace.get('id', 'N/A')}\n\n"
md_content += f"**Timestamp:** {trace.get('timestamp', 'N/A')}\n\n"
md_content += f"**Model:** {observations.get('model', 'N/A')}\n\n"
md_content += "---\n\n"
md_content += "## Input Messages\n\n"
for msg in trace.get('input', {}).get('messages', []):
role = msg.get('role', 'unknown').capitalize()
raw_content = msg.get('content')
content_text = ""
# Robustly handle None, strings, and lists
if isinstance(raw_content, str):
content_text = raw_content + "\n"
elif isinstance(raw_content, list):
for block in raw_content:
if isinstance(block, dict) and 'text' in block:
content_text += block.get('text', '') + "\n"
elif isinstance(block, str):
content_text += block + "\n"
md_content += f"### {role} Prompt\n\n"
md_content += f"{clean_text_for_markdown(content_text.strip())}\n\n"
md_content += "---\n\n"
md_content += "## Model Output\n\n"
if 'reasoning' in output_data:
reasoning_clean = clean_text_for_markdown(output_data['reasoning'])
md_content += f"### Reasoning\n\n"
md_content += f"{reasoning_clean}\n\n"
completion = output_data.get('completion', 'No completion data')
if not isinstance(completion, str):
completion = json.dumps(completion, indent=2)
md_content += f"### Tool Call / Completion\n\n"
md_content += f"```json\n{clean_text_for_markdown(completion)}\n```\n\n"
md_content += "---\n\n"
md_content += "## Usage Metrics\n\n"
md_content += f"- **Total Tokens:** {observations.get('totalTokens', 'N/A')}\n"
md_content += f"- **Total Cost:** ${observations.get('totalCost', 'N/A')}\n"
with open(md_filepath, 'w', encoding='utf-8') as md_file:
md_file.write(md_content)
return True
def generate_html_block(filename, data):
"""Generates the side-by-side HTML block for a single JSON file."""
trace = data.get('trace', {})
output_data = trace.get('output', {})
observations = data.get('observations', [{}])[0] if data.get('observations') else {}
# Left Side: Raw JSON
raw_json = html.escape(json.dumps(data, indent=2))
# Right Side: Formatted Content
right_html = f"<h2>{html.escape(filename)}</h2>"
right_html += f"<p><strong>Trace ID:</strong> {html.escape(str(trace.get('id', 'N/A')))}<br>"
right_html += f"<strong>Model:</strong> {html.escape(str(observations.get('model', 'N/A')))}</p><hr>"
right_html += "<h3>Input Messages</h3>"
for msg in trace.get('input', {}).get('messages', []):
role = msg.get('role', 'unknown').capitalize()
raw_content = msg.get('content')
content_text = ""
# Robustly handle None, strings, and lists for HTML output too
if isinstance(raw_content, str):
content_text = raw_content + "\n"
elif isinstance(raw_content, list):
for block in raw_content:
if isinstance(block, dict) and 'text' in block:
content_text += block.get('text', '') + "\n"
elif isinstance(block, str):
content_text += block + "\n"
right_html += f"<h4>{html.escape(role)}</h4>"
right_html += f"<pre class='content-block'>{html.escape(content_text.strip())}</pre>"
right_html += "<hr><h3>Model Output</h3>"
if 'reasoning' in output_data:
right_html += "<h4>Reasoning</h4>"
right_html += f"<pre class='content-block'>{html.escape(str(output_data['reasoning']))}</pre>"
completion = output_data.get('completion', 'No completion data')
if not isinstance(completion, str):
completion = json.dumps(completion, indent=2)
right_html += "<h4>Completion</h4>"
right_html += f"<pre class='content-block'>{html.escape(completion)}</pre>"
return f"""
<div class="log-entry">
<div class="left-pane">
<pre><code>{raw_json}</code></pre>
</div>
<div class="right-pane">
{right_html}
</div>
</div>
"""
def run_pandoc(md_filepath, output_pdf, output_odt):
"""Uses subprocess to call Pandoc and convert the Markdown file."""
print(f" -> Generating {output_odt}...")
try:
subprocess.run(['pandoc', md_filepath, '-o', output_odt, '--quiet'], check=True)
except subprocess.CalledProcessError as e:
print(f" -> Error creating ODT: {e}")
print(f" -> Generating {output_pdf}...")
try:
subprocess.run([
'pandoc', md_filepath,
'-o', output_pdf,
'--pdf-engine=xelatex',
'--quiet',
'-V', 'geometry:margin=1in',
'-V', 'papersize:letter'
], check=True)
except subprocess.CalledProcessError as e:
print(f" -> Error creating PDF. (XeLaTeX might have hit another unescaped character): {e}")
if __name__ == "__main__":
target_folder = "."
search_pattern = os.path.join(target_folder, '*.json')
json_files = glob.glob(search_pattern)
html_entries = []
if not json_files:
print(f"No JSON files found in the directory: '{target_folder}'")
else:
print(f"Found {len(json_files)} JSON file(s). Starting conversion...\n")
for json_path in json_files:
base_filename = os.path.splitext(os.path.basename(json_path))[0]
filename_only = os.path.basename(json_path)
temp_markdown_file = os.path.join(target_folder, f"{base_filename}_temp.md")
final_pdf = os.path.join(target_folder, f"{base_filename}.pdf")
final_odt = os.path.join(target_folder, f"{base_filename}.odt")
print(f"Processing: {filename_only}")
try:
with open(json_path, 'r', encoding='utf-8') as file:
data = json.load(file)
except Exception as e:
print(f" -> Error loading {filename_only}: {e}")
continue
if parse_json_to_markdown(json_path, temp_markdown_file, data):
run_pandoc(temp_markdown_file, final_pdf, final_odt)
if os.path.exists(temp_markdown_file):
os.remove(temp_markdown_file)
html_entries.append(generate_html_block(filename_only, data))
print("-" * 30)
dashboard_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON Trace Dashboard</title>
<style>
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f9fafb; margin: 0; padding: 20px; }}
h1 {{ text-align: center; color: #333; }}
.search-container {{ display: flex; justify-content: center; margin-bottom: 30px; position: sticky; top: 10px; z-index: 100; }}
#searchInput {{ width: 60%; padding: 15px; font-size: 16px; border: 2px solid #cbd5e1; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); outline: none; }}
#searchInput:focus {{ border-color: #3b82f6; }}
.log-entry {{ display: flex; background: white; margin-bottom: 30px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); overflow: hidden; height: 80vh; }}
.left-pane, .right-pane {{ width: 50%; padding: 20px; overflow-y: auto; box-sizing: border-box; }}
.left-pane {{ background-color: #1e1e1e; color: #d4d4d4; border-right: 2px solid #e2e8f0; }}
.right-pane {{ background-color: #ffffff; color: #333; }}
pre {{ white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', Courier, monospace; font-size: 13px; }}
.content-block {{ background-color: #f1f5f9; padding: 15px; border-radius: 6px; border: 1px solid #e2e8f0; }}
h2, h3, h4 {{ margin-top: 0; color: #1e293b; }}
</style>
</head>
<body>
<h1>Log Trace Dashboard</h1>
<div class="search-container">
<input type="text" id="searchInput" onkeyup="filterEntries()" placeholder="Search through logs, traces, or reasoning...">
</div>
<div id="logContainer">
{"".join(html_entries)}
</div>
<script>
function filterEntries() {{
let input = document.getElementById('searchInput').value.toLowerCase();
let entries = document.getElementsByClassName('log-entry');
for (let i = 0; i < entries.length; i++) {{
let text = entries[i].innerText.toLowerCase();
entries[i].style.display = text.includes(input) ? "flex" : "none";
}}
}}
</script>
</body>
</html>
"""
html_filename = "all_traces_dashboard.html"
with open(html_filename, 'w', encoding='utf-8') as f:
f.write(dashboard_html)
print(f"All conversions complete! Dashboard generated at: {html_filename}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment