Skip to content

Instantly share code, notes, and snippets.

@dreness
Created May 4, 2025 19:31
Show Gist options
  • Save dreness/7ec9799cb4fac068f7fe010dd3febf01 to your computer and use it in GitHub Desktop.
Save dreness/7ec9799cb4fac068f7fe010dd3febf01 to your computer and use it in GitHub Desktop.
WIP Roo Code transcript viewer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Roo Code Transcript Viewer</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<style>
.block { padding:1rem; border-radius:5px; }
.block-header { font-weight:600; text-transform:uppercase; font-size:.9rem; margin-bottom:.5rem; }
.block-content { white-space:pre-wrap; word-wrap:break-word; }
.user-block { background:#e7f3ff; border-left:4px solid #0d6efd; }
.user-block .block-header { color:#0d6efd; }
.assistant-block { background:#efffed; border-left:4px solid #198754; }
.assistant-block .block-header { color:#198754; }
.system-block { background:#f8f9fa; border-left:4px solid #6c757d; }
.system-block .block-header { color:#6c757d; }
.system-block .block-content { font-family:monospace; font-size:.95rem; }
.system-block h2,
.system-block h3,
.system-block h4 { font-size:1rem; margin:.5rem 0 .3rem; }
.task-block { background:#f3edff; border-left:4px solid #6f42c1; }
.task-block .block-header { color:#6f42c1; }
/* Error and file sub‑blocks */
.error-sub { background:#fdecea; border-left:4px solid #dc3545; padding:.5rem .75rem; margin:.5rem 0; border-radius:4px; }
.file-sub { background:#f5f5f5; border-left:4px solid #6c757d; padding:.5rem .75rem; margin:.5rem 0; border-radius:4px; font-family:monospace; white-space:pre; }
.thinking { font-style:italic; color:#6c757d; }
ol { margin:0 0 .75rem 1.25rem; }
pre, code { background:#f5f5f5; border-radius:4px; }
pre { padding:1rem; overflow:auto; }
code { padding:.15rem .35rem; }
</style>
</head>
<body>
<div class="container my-4">
<h1 class="mb-4">Roo Code Transcript Viewer</h1>
<div class="mb-3">
<label for="fileInput" class="form-label"><strong>Transcript file</strong></label>
<input type="file" id="fileInput" class="form-control" accept=".md">
</div>
<div class="mb-3">
<strong>Show / hide</strong> &nbsp;
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="toggleUser" checked>
<label class="form-check-label" for="toggleUser">User prompts</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="toggleAssistant" checked>
<label class="form-check-label" for="toggleAssistant">Assistant responses</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="toggleSystem" checked>
<label class="form-check-label" for="toggleSystem">System info</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="toggleTask" checked>
<label class="form-check-label" for="toggleTask">Tasks</label>
</div>
</div>
<div id="transcriptContainer"></div>
</div>
<script>
/* ---------- Helpers ---------- */
const esc=s=>s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
function cleanGarbage(t){return t.replace(/â/g,"'").replace(/âœ|â/g,'"')
.replace(/â/g,"–").replace(/â/g,"—").replace(/â¦/g,"…")
.replace(/Â /g,' ').replace(/Â/g,'').replace(/Ã/g,'').replace(/(?!\d)/g,'€');}
const roleEmoji={ask:'❓',orchestrator:'🪃',edit:'✏️',architect:'🏗️',code:'💻'};
const tagEmoji={'[new_task':'🆕','[read_file':'📖'};
/* ---------- Format content ---------- */
function formatContent(lines){
let html='',inCode=false,inList=false,inError=false,inFile=false;
const endList=()=>{if(inList){html+='</ol>';inList=false;}};
const openError=()=>{if(!inError){endList();html+='<div class="error-sub">';inError=true;}};
const closeError=()=>{if(inError){html+='</div>';inError=false;}};
const openFile=()=>{if(!inFile){endList();html+='<div class="file-sub"><pre><code>';inFile=true;}};
const closeFile=()=>{if(inFile){html+='</code></pre></div>';inFile=false;}};
for(let raw of lines){
raw=cleanGarbage(raw);
/* file block tags */
if(/^\s*<write_(to_)?file.*?>/i.test(raw)){openFile();continue;}
if(/^\s*<\/write_(to_)?file>/i.test(raw)){closeFile();continue;}
/* error tags */
if(/^\s*<error>/i.test(raw)){openError();continue;}
if(/^\s*<\/error>/i.test(raw)){closeError();continue;}
/* other structural tags hidden */
if(/^\s*<\/?(new_task|environment_details|message)[^>]*>/i.test(raw))continue;
if(inFile){html+=esc(raw)+'\n';continue;} /* raw inside file block */
raw=raw.replace(/\\n/g,'<br>');
const ltrim=raw.trimStart();
/* code fences */
if(ltrim.startsWith('```')){
if(!inCode){endList();html+='<pre><code>';inCode=true;}
else{html+='</code></pre>';inCode=false;}
continue;
}
if(inCode){html+=esc(raw)+'\n';continue;}
/* headings (#, ##, ###) */
if(/^#{1,3}\s+/.test(ltrim)){
endList();
const lvl=ltrim.match(/^#+/)[0].length;
const tag=['h2','h3','h4'][lvl-1];
html+=`<${tag}>${esc(ltrim.replace(/^#{1,3}\s+/,''))}</${tag}>`;
continue;
}
/* numbered lists */
if(/^\s*\d+\.\s+/.test(ltrim)){
if(!inList){html+='<ol>';inList=true;}
html+='<li>'+esc(ltrim.replace(/^\s*\d+\.\s+/,''))+'</li>';
continue;
}else{endList();}
/* bold **text** */
let lineHTML=esc(raw).replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');
/* emoji replacements for common tags */
for(const tag in tagEmoji){
lineHTML=lineHTML.replace(new RegExp('\\'+tag+'.*?\\]','g'),tagEmoji[tag]);
}
/* <mode>role</mode> emoji */
const modeMatch=ltrim.match(/^<mode>(.*?)<\/mode>$/i);
if(modeMatch){
const role=modeMatch[1].toLowerCase();
lineHTML=`<strong>${roleEmoji[role]||'🔧'} ${role.charAt(0).toUpperCase()+role.slice(1)}</strong>`;
}
html+=lineHTML+'\n';
}
endList(); closeError(); closeFile();
if(inCode)html+='</code></pre>';
return html.replace(/&lt;thinking&gt;/g,'<span class="thinking">')
.replace(/&lt;\/thinking&gt;/g,'</span>');
}
/* ---------- Parse ---------- */
function parseTranscript(text){
const lines=text.split(/\r?\n/),blocks=[];let i=0;
const gather=stop=>{const a=[];while(i<lines.length&&!stop(lines[i]))a.push(lines[i++]);return a;};
while(i<lines.length){
const ln=lines[i];
if(ln.trim()===''||ln.trim()==='---'){i++;continue;}
if(ln.startsWith('<environment_details>')){
const content=gather(l=>l.startsWith('</environment_details>'));if(i<lines.length)i++;
blocks.push({type:'system',content});continue;
}
if(ln.startsWith('<new_task')){
const content=gather(l=>l.startsWith('</new_task>'));if(i<lines.length)i++;
blocks.push({type:'task',content});continue;
}
if(ln.startsWith('**User:**')){
i++;const content=gather(l=>/^\*\*(User|Assistant):\*\*/.test(l)
||l.startsWith('<environment_details>')
||l.startsWith('<new_task')||l.trim()==='---');
blocks.push({type:'user',content});continue;
}
if(ln.startsWith('**Assistant:**')){
i++;const content=gather(l=>/^\*\*(User|Assistant):\*\*/.test(l)
||l.startsWith('<environment_details>')
||l.startsWith('<new_task')||l.trim()==='---');
blocks.push({type:'assistant',content});continue;
}
i++;
}
return blocks;
}
/* ---------- Render ---------- */
function renderTranscript(blocks){
const C=document.getElementById('transcriptContainer');C.innerHTML='';
blocks.forEach(b=>{
const wrap=document.createElement('div');
wrap.className=`block ${b.type}-block mb-4`;
const hdr=document.createElement('div');
hdr.className='block-header';
hdr.textContent={user:'User prompt',assistant:'Assistant response',
system:'System info',task:'Task'}[b.type]||'Block';
const cont=document.createElement('div');
cont.className='block-content';
cont.innerHTML=formatContent(b.content);
wrap.append(hdr,cont);C.appendChild(wrap);
});
}
/* ---------- File input ---------- */
document.getElementById('fileInput').addEventListener('change',e=>{
const f=e.target.files[0];if(!f)return;
const r=new FileReader();
r.onload=ev=>renderTranscript(parseTranscript(ev.target.result));
r.readAsText(f);
});
/* ---------- Toggles ---------- */
const toggleMap={toggleUser:'user-block',toggleAssistant:'assistant-block',
toggleSystem:'system-block',toggleTask:'task-block'};
for(const id in toggleMap){
document.getElementById(id).addEventListener('change',function(){
document.querySelectorAll('.'+toggleMap[id])
.forEach(el=>el.style.display=this.checked?'':'none');
});
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment