Skip to content

Instantly share code, notes, and snippets.

@dpavlin
Last active January 8, 2026 07:32
Show Gist options
  • Select an option

  • Save dpavlin/019de991fc7186390b786269c2f241cd to your computer and use it in GitHub Desktop.

Select an option

Save dpavlin/019de991fc7186390b786269c2f241cd to your computer and use it in GitHub Desktop.
load Google AI studio conversation.json from gdrive backup and display it as single html page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Full Conversation Report</title>
<!-- Using a lightweight and fast Markdown parser from a CDN -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--border-color: #dee2e6;
--card-bg: #ffffff;
--header-bg: #e9ecef;
--accent-color: #007bff;
--code-bg: #e9ecef;
--code-text: #333;
--user-header: #d1e7dd;
--model-header: #cff4fc;
--thought-header: #fff3cd;
}
body.dark-mode {
--bg-color: #212529;
--text-color: #dee2e6;
--border-color: #495057;
--card-bg: #343a40;
--header-bg: #495057;
--accent-color: #0d6efd;
--code-bg: #495057;
--code-text: #f8f9fa;
--user-header: #0f5132;
--model-header: #055160;
--thought-header: #664d03;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
}
.page-header {
padding: 1rem 2rem;
background-color: var(--card-bg);
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
}
.page-header h1 {
margin: 0;
font-size: 1.5rem;
}
.controls button, .controls label {
background-color: var(--accent-color);
color: white;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
margin-left: 0.5rem;
}
.controls input[type="file"] {
display: none;
}
#report-container {
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}
.turn-card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: 2rem;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
overflow: hidden;
}
.turn-header {
padding: 0.8rem 1.5rem;
font-weight: bold;
font-size: 1.1rem;
border-bottom: 1px solid var(--border-color);
}
.turn-header.user { background-color: var(--user-header); }
.turn-header.model { background-color: var(--model-header); }
.turn-header.thought { background-color: var(--thought-header); }
.turn-body {
padding: 0.5rem 1.5rem 1.5rem 1.5rem;
}
.turn-body h1, .turn-body h2, .turn-body h3 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.3em;
margin-top: 1.5em;
}
.turn-body table { border-collapse: collapse; width: 100%; margin: 1em 0; }
.turn-body th, .turn-body td { border: 1px solid var(--border-color); padding: 0.6rem; text-align: left; }
.turn-body th { background-color: var(--header-bg); }
.turn-body code { background-color: var(--code-bg); color: var(--code-text); padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; }
.turn-body pre { background-color: var(--code-bg); color: var(--code-text); padding: 1rem; border-radius: 5px; overflow-x: auto; }
.turn-body pre code { padding: 0; background: none; }
.metadata {
font-size: 0.9em;
padding: 1rem 1.5rem;
border-top: 1px dashed var(--border-color);
background: var(--header-bg);
}
.raw-json details {
margin-top: 1rem;
}
.raw-json summary {
cursor: pointer;
font-weight: bold;
color: var(--accent-color);
}
.placeholder {
text-align: center;
color: #6c757d;
font-size: 1.2rem;
padding: 4rem;
}
@media print {
body {
--bg-color: #ffffff;
--text-color: #000000;
--border-color: #cccccc;
--card-bg: #ffffff;
--user-header: #e9f5e9;
--model-header: #e1f5fe;
--thought-header: #fff8e1;
}
.page-header {
display: none;
}
#report-container {
padding: 0;
max-width: 100%;
box-shadow: none;
}
.turn-card {
box-shadow: none;
border: 1px solid #ccc;
page-break-inside: avoid;
}
}
</style>
</head>
<body>
<header class="page-header">
<h1>Full Conversation Report</h1>
<div class="controls">
<label for="file-input">Load conversation.json</label>
<input type="file" id="file-input" accept=".json">
<button id="print-button">Print Report</button>
<button id="dark-mode-toggle">Toggle Dark Mode</button>
</div>
</header>
<main id="report-container">
<div class="placeholder">Load a `conversation.json` file to generate the report.</div>
</main>
<!-- Template for a single conversation turn -->
<template id="turn-template">
<div class="turn-card">
<div class="turn-header"></div>
<div class="turn-body">
<div class="attachments"></div>
<div class="main-text"></div>
<div class="grounding"></div>
<div class="raw-json">
<details>
<summary>Show Raw JSON</summary>
<pre><code></code></pre>
</details>
</div>
</div>
</div>
</template>
<script>
document.addEventListener('DOMContentLoaded', () => {
const fileInput = document.getElementById('file-input');
const reportContainer = document.getElementById('report-container');
const turnTemplate = document.getElementById('turn-template');
document.getElementById('dark-mode-toggle').addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
});
document.getElementById('print-button').addEventListener('click', () => {
window.print();
});
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
generateReport(data);
} catch (error) {
alert(`Error reading or parsing file: ${error.message}`);
}
};
reader.readAsText(file);
});
function generateReport(data) {
const chunks = data?.chunkedPrompt?.chunks;
if (!chunks || !Array.isArray(chunks)) {
reportContainer.innerHTML = '<div class="placeholder">Invalid JSON format. Expected a `chunkedPrompt.chunks` array.</div>';
return;
}
reportContainer.innerHTML = ''; // Clear placeholder
chunks.forEach((chunk, index) => {
const card = turnTemplate.content.cloneNode(true);
const header = card.querySelector('.turn-header');
const mainText = card.querySelector('.main-text');
const attachmentsContainer = card.querySelector('.attachments');
const groundingContainer = card.querySelector('.grounding');
const rawJsonCode = card.querySelector('.raw-json pre code');
const role = chunk.role || 'unknown';
const isThought = chunk.isThought || false;
// --- Header ---
let headerText = `${index + 1}. ${role.toUpperCase()}`;
header.classList.add(role);
if (isThought) {
headerText += ' (Thought)';
header.classList.add('thought');
}
header.textContent = headerText;
// --- Attachments ---
let attachmentsHtml = '';
if (chunk.driveImage) {
attachmentsHtml += `<p><strong>Attachment (Image):</strong> <code>${chunk.driveImage.id}</code></p>`;
}
if (chunk.driveDocument) {
attachmentsHtml += `<p><strong>Attachment (Document):</strong> <code>${chunk.driveDocument.id}</code></p>`;
}
attachmentsContainer.innerHTML = attachmentsHtml;
// --- Main Text ---
if (chunk.text) {
mainText.innerHTML = marked.parse(chunk.text);
}
// --- Grounding ---
let groundingHtml = '';
if (chunk.grounding) {
groundingHtml += '<h4>Grounding Details</h4>';
const g = chunk.grounding;
if (g.webSearchQueries?.length) {
groundingHtml += `<p><strong>Search Queries:</strong> ${g.webSearchQueries.map(q => `<code>${q}</code>`).join(', ')}</p>`;
}
if (g.groundingSources?.length) {
groundingHtml += '<h5>Sources:</h5><ul>';
g.groundingSources.forEach(src => {
groundingHtml += `<li><a href="${src.uri}" target="_blank" rel="noopener noreferrer">${src.title || src.uri}</a></li>`;
});
groundingHtml += '</ul>';
}
}
groundingContainer.innerHTML = groundingHtml;
if (!groundingHtml) groundingContainer.style.display = 'none';
// --- Raw JSON ---
rawJsonCode.textContent = JSON.stringify(chunk, null, 2);
reportContainer.appendChild(card);
});
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment