mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 07:03:25 +00:00
Improves the HTML export (#853)
- Added jsonl download button Jump to last message on click Fix missing labels
This commit is contained in:
parent
3e68744cae
commit
98fb9f378c
2 changed files with 89 additions and 5 deletions
|
|
@ -221,6 +221,25 @@
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--warning);
|
color: var(--warning);
|
||||||
margin-bottom: var(--line-height);
|
margin-bottom: var(--line-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-json-btn {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--container-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-json-btn:hover {
|
||||||
|
background: var(--hover);
|
||||||
|
border-color: var(--borderAccent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label lookup (entryId -> label string)
|
// Label lookup (entryId -> label string)
|
||||||
// Labels are stored in 'label' entries that reference their target via parentId
|
// Labels are stored in 'label' entries that reference their target via targetId
|
||||||
const labelMap = new Map();
|
const labelMap = new Map();
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.type === 'label' && entry.parentId && entry.label) {
|
if (entry.type === 'label' && entry.targetId && entry.label) {
|
||||||
labelMap.set(entry.parentId, entry.label);
|
labelMap.set(entry.targetId, entry.label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,6 +144,37 @@
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tree node lookup for finding leaves
|
||||||
|
let treeNodeMap = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the newest leaf node reachable from a given node.
|
||||||
|
* This allows clicking any node in a branch to show the full branch.
|
||||||
|
* Children are sorted by timestamp, so the newest is always last.
|
||||||
|
*/
|
||||||
|
function findNewestLeaf(nodeId) {
|
||||||
|
// Build tree node map lazily
|
||||||
|
if (!treeNodeMap) {
|
||||||
|
treeNodeMap = new Map();
|
||||||
|
const tree = buildTree();
|
||||||
|
function mapNodes(node) {
|
||||||
|
treeNodeMap.set(node.entry.id, node);
|
||||||
|
node.children.forEach(mapNodes);
|
||||||
|
}
|
||||||
|
tree.forEach(mapNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = treeNodeMap.get(nodeId);
|
||||||
|
if (!node) return nodeId;
|
||||||
|
|
||||||
|
// Follow the newest (last) child at each level
|
||||||
|
let current = node;
|
||||||
|
while (current.children.length > 0) {
|
||||||
|
current = current.children[current.children.length - 1];
|
||||||
|
}
|
||||||
|
return current.entry.id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flatten tree into list with indentation and connector info.
|
* Flatten tree into list with indentation and connector info.
|
||||||
* Returns array of { node, indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots }.
|
* Returns array of { node, indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots }.
|
||||||
|
|
@ -674,7 +705,11 @@
|
||||||
div.appendChild(prefixSpan);
|
div.appendChild(prefixSpan);
|
||||||
div.appendChild(marker);
|
div.appendChild(marker);
|
||||||
div.appendChild(content);
|
div.appendChild(content);
|
||||||
div.addEventListener('click', () => navigateTo(entry.id));
|
// Navigate to the newest leaf through this node, but scroll to the clicked node
|
||||||
|
div.addEventListener('click', () => {
|
||||||
|
const leafId = findNewestLeaf(entry.id);
|
||||||
|
navigateTo(leafId, 'target', entry.id);
|
||||||
|
});
|
||||||
|
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
@ -954,6 +989,33 @@
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the session data as a JSONL file.
|
||||||
|
* Reconstructs the original format: header line + entry lines.
|
||||||
|
*/
|
||||||
|
window.downloadSessionJson = function() {
|
||||||
|
// Build JSONL content: header first, then all entries
|
||||||
|
const lines = [];
|
||||||
|
if (header) {
|
||||||
|
lines.push(JSON.stringify({ type: 'header', ...header }));
|
||||||
|
}
|
||||||
|
for (const entry of entries) {
|
||||||
|
lines.push(JSON.stringify(entry));
|
||||||
|
}
|
||||||
|
const jsonlContent = lines.join('\n');
|
||||||
|
|
||||||
|
// Create download
|
||||||
|
const blob = new Blob([jsonlContent], { type: 'application/x-ndjson' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${header?.id || 'session'}.jsonl`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a shareable URL for a specific message.
|
* Build a shareable URL for a specific message.
|
||||||
* URL format: base?gistId&leafId=<leafId>&targetId=<entryId>
|
* URL format: base?gistId&leafId=<leafId>&targetId=<entryId>
|
||||||
|
|
@ -1212,7 +1274,10 @@
|
||||||
let html = `
|
let html = `
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>Session: ${escapeHtml(header?.id || 'unknown')}</h1>
|
<h1>Session: ${escapeHtml(header?.id || 'unknown')}</h1>
|
||||||
<div class="help-bar">Ctrl+T toggle thinking · Ctrl+O toggle tools</div>
|
<div class="help-bar">
|
||||||
|
<span>Ctrl+T toggle thinking · Ctrl+O toggle tools</span>
|
||||||
|
<button class="download-json-btn" onclick="downloadSessionJson()" title="Download session as JSONL">↓ JSONL</button>
|
||||||
|
</div>
|
||||||
<div class="header-info">
|
<div class="header-info">
|
||||||
<div class="info-item"><span class="info-label">Date:</span><span class="info-value">${header?.timestamp ? new Date(header.timestamp).toLocaleString() : 'unknown'}</span></div>
|
<div class="info-item"><span class="info-label">Date:</span><span class="info-value">${header?.timestamp ? new Date(header.timestamp).toLocaleString() : 'unknown'}</span></div>
|
||||||
<div class="info-item"><span class="info-label">Models:</span><span class="info-value">${globalStats.models.join(', ') || 'unknown'}</span></div>
|
<div class="info-item"><span class="info-label">Models:</span><span class="info-value">${globalStats.models.join(', ') || 'unknown'}</span></div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue