mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 08:03:39 +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;
|
||||
color: var(--warning);
|
||||
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 */
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@
|
|||
}
|
||||
|
||||
// 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();
|
||||
for (const entry of entries) {
|
||||
if (entry.type === 'label' && entry.parentId && entry.label) {
|
||||
labelMap.set(entry.parentId, entry.label);
|
||||
if (entry.type === 'label' && entry.targetId && entry.label) {
|
||||
labelMap.set(entry.targetId, entry.label);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +144,37 @@
|
|||
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.
|
||||
* Returns array of { node, indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots }.
|
||||
|
|
@ -674,7 +705,11 @@
|
|||
div.appendChild(prefixSpan);
|
||||
div.appendChild(marker);
|
||||
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);
|
||||
}
|
||||
|
|
@ -954,6 +989,33 @@
|
|||
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.
|
||||
* URL format: base?gistId&leafId=<leafId>&targetId=<entryId>
|
||||
|
|
@ -1212,7 +1274,10 @@
|
|||
let html = `
|
||||
<div class="header">
|
||||
<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="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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue