Spaces:
Paused
Paused
Update templates/index.html
Browse files- templates/index.html +138 -118
templates/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
| 6 |
-
<title>Overthinker β
|
| 7 |
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
| 8 |
<style>
|
| 9 |
* { margin:0; padding:0; box-sizing:border-box; }
|
|
@@ -344,14 +344,14 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 344 |
<div id="info-dropdown" class="dropdown-overlay">
|
| 345 |
<div class="dropdown-box">
|
| 346 |
<button class="dropdown-close" id="info-close">β</button>
|
| 347 |
-
<h3>βΉοΈ About Overthinker</h3>
|
| 348 |
<div class="dropdown-section">
|
| 349 |
<h4>π€ Model</h4>
|
| 350 |
-
<p>Powered by <strong>nvidia/
|
| 351 |
</div>
|
| 352 |
<div class="dropdown-section">
|
| 353 |
<h4>π Framework</h4>
|
| 354 |
-
<p>Built on <strong>Gradio.Server</strong> with
|
| 355 |
</div>
|
| 356 |
<div class="dropdown-section">
|
| 357 |
<h4>β¨ Key Features</h4>
|
|
@@ -362,9 +362,10 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 362 |
<li>π Path sidebar with depth tracking & tips</li>
|
| 363 |
<li>πΌ Export: SVG, JSON, Markdown, PNG</li>
|
| 364 |
<li>π Dark/Light theme toggle</li>
|
| 365 |
-
<li>β¨
|
| 366 |
-
<li>β© Undo/Redo support</li>
|
| 367 |
<li>π Regenerate with user context</li>
|
|
|
|
|
|
|
| 368 |
</ul>
|
| 369 |
</div>
|
| 370 |
<div class="dropdown-section">
|
|
@@ -407,6 +408,10 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 407 |
<h4>5. Export</h4>
|
| 408 |
<p>Use the toolbar buttons to export the entire tree or just the selected path in your preferred format: SVG, JSON, Markdown, or PNG.</p>
|
| 409 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
</div>
|
| 411 |
</div>
|
| 412 |
|
|
@@ -434,9 +439,6 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 434 |
|
| 435 |
<div class="toolbar" id="toolbar" style="display:none;">
|
| 436 |
<div class="toolbar-group">
|
| 437 |
-
<button class="btn btn-secondary btn-icon" id="undo-btn" title="Undo (Ctrl+Z)">β©</button>
|
| 438 |
-
<button class="btn btn-secondary btn-icon" id="redo-btn" title="Redo (Ctrl+Shift+Z)">βͺ</button>
|
| 439 |
-
<div class="toolbar-divider"></div>
|
| 440 |
<button class="btn btn-secondary btn-icon" id="fit-btn" title="Fit View (F)">π―</button>
|
| 441 |
<button class="btn btn-secondary btn-icon" id="clear-btn" title="Clear (C)">π</button>
|
| 442 |
<div class="toolbar-divider"></div>
|
|
@@ -445,6 +447,9 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 445 |
<button class="btn btn-secondary" id="export-md-btn">π Markdown</button>
|
| 446 |
<button class="btn btn-secondary" id="export-png-btn">π· PNG</button>
|
| 447 |
</div>
|
|
|
|
|
|
|
|
|
|
| 448 |
<div class="stats">
|
| 449 |
<span>Nodes: <span class="highlight" id="stat-nodes">0</span></span>
|
| 450 |
<span>Depth: <span class="highlight" id="stat-depth">0</span></span>
|
|
@@ -464,7 +469,7 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 464 |
|
| 465 |
<div id="loading-overlay" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:999;align-items:center;justify-content:center;flex-direction:column;">
|
| 466 |
<div class="spinner" style="width:40px;height:40px;border:4px solid rgba(255,255,255,0.1);border-top-color:#ffd700;border-radius:50%;animation:spin 0.8s linear infinite;margin-bottom:16px;"></div>
|
| 467 |
-
<p id="loading-text" style="color:#ffd700;font-size:0.9em;">
|
| 468 |
</div>
|
| 469 |
|
| 470 |
<div class="toast" id="toast"></div>
|
|
@@ -475,9 +480,12 @@ body.light .dropdown-section kbd { background: rgba(0,0,0,0.04); border-color: r
|
|
| 475 |
|
| 476 |
<script>
|
| 477 |
// =============================================
|
| 478 |
-
// OVER THINKER β
|
| 479 |
-
// -
|
| 480 |
-
// -
|
|
|
|
|
|
|
|
|
|
| 481 |
// =============================================
|
| 482 |
|
| 483 |
let nodes = [];
|
|
@@ -485,12 +493,10 @@ let links = [];
|
|
| 485 |
let selectedNodeId = null;
|
| 486 |
let parentMap = new Map();
|
| 487 |
let expandedSet = new Set();
|
| 488 |
-
let
|
| 489 |
let svg, gLinks, gNodes, gMain, zoomBehavior;
|
| 490 |
let foreignPopup = null;
|
| 491 |
let isDark = true;
|
| 492 |
-
let historyStack = { undo: [], redo: [] };
|
| 493 |
-
let treeDataRoot = null;
|
| 494 |
|
| 495 |
const CARD_W = 230, CARD_H = 130;
|
| 496 |
const HEAVY_W = 300, HEAVY_H = 480;
|
|
@@ -549,6 +555,17 @@ function splitLabel(label) {
|
|
| 549 |
return [label.substring(0, splitIdx).trim(), label.substring(splitIdx).trim()];
|
| 550 |
}
|
| 551 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
// ---------- LAYOUT ----------
|
| 553 |
function computeTreeLayout() {
|
| 554 |
if (nodes.length === 0) return;
|
|
@@ -933,7 +950,20 @@ function updatePathSidebar() {
|
|
| 933 |
});
|
| 934 |
}
|
| 935 |
|
| 936 |
-
// ---------- API CALLS ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 937 |
async function generateTree() {
|
| 938 |
const decision = inputEl.value.trim();
|
| 939 |
if (!decision) return;
|
|
@@ -941,23 +971,17 @@ async function generateTree() {
|
|
| 941 |
generateBtn.disabled = true;
|
| 942 |
showLoading('Creating decision root...');
|
| 943 |
try {
|
| 944 |
-
const
|
| 945 |
-
|
| 946 |
-
body:JSON.stringify({ decision })
|
| 947 |
-
});
|
| 948 |
-
const data = await res.json();
|
| 949 |
-
if (data.error) { showToast(data.error, 'error'); return; }
|
| 950 |
-
treeId = data.tree_id;
|
| 951 |
-
const rootNode = data.root_node;
|
| 952 |
nodes = []; links = []; parentMap = new Map(); expandedSet = new Set(); selectedNodeId = null;
|
| 953 |
-
historyStack = { undo: [], redo: [] };
|
| 954 |
addNodeToGraph(rootNode, null);
|
| 955 |
renderCanvas();
|
| 956 |
-
|
|
|
|
| 957 |
await exploreNode(rootNode.id, parseInt(branchInput.value) || 3, null);
|
| 958 |
} catch(e) {
|
| 959 |
console.error(e);
|
| 960 |
-
showToast('Generation failed: '+e.message, 'error');
|
| 961 |
} finally {
|
| 962 |
hideLoading();
|
| 963 |
generateBtn.textContent = 'π³ Generate';
|
|
@@ -972,20 +996,20 @@ inputEl.addEventListener('keydown', (e) => { if (e.key==='Enter' && !e.shiftKey)
|
|
| 972 |
async function exploreNode(nodeId, count, comment) {
|
| 973 |
const node = nodes.find(n => n.id === nodeId);
|
| 974 |
if (!node) return;
|
| 975 |
-
|
|
|
|
| 976 |
try {
|
| 977 |
-
const
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
|
|
|
| 982 |
if (expandedSet.has(nodeId)) { removeChildrenOf(nodeId); }
|
| 983 |
const children = data.children || [];
|
| 984 |
children.forEach(child => addNodeToGraph(child, nodeId));
|
| 985 |
expandedSet.add(nodeId);
|
| 986 |
node._childrenCount = children.length;
|
| 987 |
-
historyStack.undo.push({ type: 'expand', nodeId, count: children.length });
|
| 988 |
-
historyStack.redo = [];
|
| 989 |
renderCanvas();
|
| 990 |
removeForeignPopup();
|
| 991 |
selectedNodeId = null;
|
|
@@ -996,25 +1020,18 @@ async function exploreNode(nodeId, count, comment) {
|
|
| 996 |
updatePathSidebar();
|
| 997 |
} catch(e) {
|
| 998 |
console.error(e);
|
| 999 |
-
showToast('Failed: '+e.message, 'error');
|
| 1000 |
} finally { hideLoading(); }
|
| 1001 |
}
|
| 1002 |
|
| 1003 |
async function addOptionsToNode(nodeId, count) {
|
| 1004 |
-
showLoading('Adding new ...');
|
| 1005 |
try {
|
| 1006 |
-
const
|
| 1007 |
-
method:'POST', headers:{'Content-Type':'application/json'},
|
| 1008 |
-
body:JSON.stringify({ node_id: nodeId, count })
|
| 1009 |
-
});
|
| 1010 |
-
const data = await res.json();
|
| 1011 |
-
if (data.error) { showToast(data.error, 'error'); return; }
|
| 1012 |
const newChildren = data.children || [];
|
| 1013 |
newChildren.forEach(child => addNodeToGraph(child, nodeId));
|
| 1014 |
const parentNode = nodes.find(n => n.id === nodeId);
|
| 1015 |
if (parentNode) parentNode._childrenCount = (parentNode._childrenCount||0) + newChildren.length;
|
| 1016 |
-
historyStack.undo.push({ type: 'add', nodeId, count: newChildren.length });
|
| 1017 |
-
historyStack.redo = [];
|
| 1018 |
renderCanvas();
|
| 1019 |
removeForeignPopup();
|
| 1020 |
selectedNodeId = null;
|
|
@@ -1023,23 +1040,24 @@ async function addOptionsToNode(nodeId, count) {
|
|
| 1023 |
if (parentNode) showPopupForNode(parentNode, true);
|
| 1024 |
updatePathSidebar();
|
| 1025 |
} catch(e) {
|
| 1026 |
-
showToast('Failed: '+e.message, 'error');
|
| 1027 |
} finally { hideLoading(); }
|
| 1028 |
}
|
| 1029 |
|
| 1030 |
function addNodeToGraph(nodeObj, parentId) {
|
| 1031 |
-
nodeObj._label = nodeObj.label || 'Untitled';
|
| 1032 |
-
nodeObj._description = nodeObj.description || '';
|
| 1033 |
-
nodeObj.
|
| 1034 |
-
nodeObj.
|
| 1035 |
-
nodeObj.
|
| 1036 |
-
nodeObj._tips = nodeObj.tips || [];
|
| 1037 |
nodeObj.depth = parentId ? (nodes.find(n=>n.id===parentId)?.depth ?? 0) + 1 : 0;
|
| 1038 |
nodeObj.x = 0; nodeObj.y = 0;
|
| 1039 |
-
nodes.
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
|
|
|
|
|
|
|
| 1043 |
}
|
| 1044 |
}
|
| 1045 |
|
|
@@ -1391,9 +1409,7 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1391 |
rect.setAttribute('stroke-width', '2');
|
| 1392 |
g.appendChild(rect);
|
| 1393 |
|
| 1394 |
-
const type = n._type || 'outcome';
|
| 1395 |
const [line1, line2] = splitLabel(n._label || '');
|
| 1396 |
-
|
| 1397 |
const txt1 = document.createElementNS(ns, 'text');
|
| 1398 |
txt1.setAttribute('x', '0');
|
| 1399 |
txt1.setAttribute('y', -(tileH/2) + 20);
|
|
@@ -1401,7 +1417,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1401 |
txt1.setAttribute('fill', labelColor);
|
| 1402 |
txt1.setAttribute('font-size', '12');
|
| 1403 |
txt1.setAttribute('font-weight', '700');
|
| 1404 |
-
txt1.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1405 |
txt1.textContent = line1 || '';
|
| 1406 |
g.appendChild(txt1);
|
| 1407 |
|
|
@@ -1413,7 +1428,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1413 |
txt2.setAttribute('fill', labelColor);
|
| 1414 |
txt2.setAttribute('font-size', '12');
|
| 1415 |
txt2.setAttribute('font-weight', '700');
|
| 1416 |
-
txt2.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1417 |
txt2.textContent = line2;
|
| 1418 |
g.appendChild(txt2);
|
| 1419 |
}
|
|
@@ -1424,9 +1438,8 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1424 |
typeTxt.setAttribute('text-anchor', 'middle');
|
| 1425 |
typeTxt.setAttribute('fill', typeFillColor);
|
| 1426 |
typeTxt.setAttribute('font-size', '9');
|
| 1427 |
-
typeTxt.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1428 |
const typeLabels = { root: 'π³ ROOT', input: 'π§ INPUT', outcome: 'π OUTCOME' };
|
| 1429 |
-
typeTxt.textContent = typeLabels[
|
| 1430 |
g.appendChild(typeTxt);
|
| 1431 |
|
| 1432 |
const maxDescLen = 35;
|
|
@@ -1451,7 +1464,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1451 |
dtxt.setAttribute('text-anchor', 'middle');
|
| 1452 |
dtxt.setAttribute('fill', isDark ? '#aaa' : '#666');
|
| 1453 |
dtxt.setAttribute('font-size', '8');
|
| 1454 |
-
dtxt.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1455 |
dtxt.textContent = line;
|
| 1456 |
g.appendChild(dtxt);
|
| 1457 |
});
|
|
@@ -1481,7 +1493,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1481 |
ttxt.setAttribute('text-anchor', 'middle');
|
| 1482 |
ttxt.setAttribute('fill', '#7ec8a0');
|
| 1483 |
ttxt.setAttribute('font-size', '8');
|
| 1484 |
-
ttxt.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1485 |
ttxt.textContent = line;
|
| 1486 |
g.appendChild(ttxt);
|
| 1487 |
});
|
|
@@ -1492,7 +1503,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1492 |
depthTxt.setAttribute('y', tileH/2 - 10);
|
| 1493 |
depthTxt.setAttribute('fill', isDark ? '#c9aaff' : '#7c5cbf');
|
| 1494 |
depthTxt.setAttribute('font-size', '10');
|
| 1495 |
-
depthTxt.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1496 |
depthTxt.textContent = 'D' + (n.depth || 0);
|
| 1497 |
g.appendChild(depthTxt);
|
| 1498 |
|
|
@@ -1502,7 +1512,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1502 |
chTxt.setAttribute('text-anchor', 'end');
|
| 1503 |
chTxt.setAttribute('fill', isDark ? '#7ecfff' : '#2e86ab');
|
| 1504 |
chTxt.setAttribute('font-size', '10');
|
| 1505 |
-
chTxt.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1506 |
chTxt.textContent = 'β³' + (n._childrenCount || 0);
|
| 1507 |
g.appendChild(chTxt);
|
| 1508 |
|
|
@@ -1516,7 +1525,6 @@ function buildExportSvg(isPathOnly = false) {
|
|
| 1516 |
title.setAttribute('fill', labelColor);
|
| 1517 |
title.setAttribute('font-size', '18');
|
| 1518 |
title.setAttribute('font-weight', '700');
|
| 1519 |
-
title.setAttribute('font-family', 'Segoe UI, system-ui, sans-serif');
|
| 1520 |
const rootLabel = exportNodes.find(n => n.depth === 0)?._label || 'Decision Tree';
|
| 1521 |
title.textContent = isPathOnly ? `π Path: ${rootLabel}` : `π§ ${rootLabel}`;
|
| 1522 |
svgDoc.appendChild(title);
|
|
@@ -1548,11 +1556,8 @@ function exportPathSvg() {
|
|
| 1548 |
}
|
| 1549 |
|
| 1550 |
async function exportJson() {
|
| 1551 |
-
if (!treeId) { showToast('No tree to export', 'error'); return; }
|
| 1552 |
try {
|
| 1553 |
-
const
|
| 1554 |
-
const data = await res.json();
|
| 1555 |
-
if (data.error) { showToast(data.error, 'error'); return; }
|
| 1556 |
const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
|
| 1557 |
const url = URL.createObjectURL(blob);
|
| 1558 |
const a = document.createElement('a'); a.href=url; a.download='overthinker_tree.json'; a.click();
|
|
@@ -1563,14 +1568,8 @@ async function exportJson() {
|
|
| 1563 |
|
| 1564 |
async function exportPathJson() {
|
| 1565 |
if (!selectedNodeId) { showToast('Select a node first', 'error'); return; }
|
| 1566 |
-
if (!treeId) { showToast('No tree', 'error'); return; }
|
| 1567 |
try {
|
| 1568 |
-
const
|
| 1569 |
-
method:'POST', headers:{'Content-Type':'application/json'},
|
| 1570 |
-
body:JSON.stringify({ node_id: selectedNodeId, tree_id: treeId })
|
| 1571 |
-
});
|
| 1572 |
-
const data = await res.json();
|
| 1573 |
-
if (data.error) { showToast(data.error, 'error'); return; }
|
| 1574 |
const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
|
| 1575 |
const url = URL.createObjectURL(blob);
|
| 1576 |
const a = document.createElement('a'); a.href=url; a.download='overthinker_path.json'; a.click();
|
|
@@ -1579,43 +1578,18 @@ async function exportPathJson() {
|
|
| 1579 |
} catch(e) { showToast('Export failed: '+e.message, 'error'); }
|
| 1580 |
}
|
| 1581 |
|
| 1582 |
-
function exportMarkdown() {
|
| 1583 |
-
if (!nodes.length) { showToast('Generate a tree first', 'error'); return; }
|
| 1584 |
-
let md = '# π§ Overthinker Decision Tree\n\n';
|
| 1585 |
-
const root = nodes.find(n => n.depth === 0);
|
| 1586 |
-
if (root) {
|
| 1587 |
-
md += `## π³ Root Decision\n**${root._label}**\n\n`;
|
| 1588 |
-
if (root._description) md += `> ${root._description}\n\n`;
|
| 1589 |
-
}
|
| 1590 |
-
function collectMd(nodeId, depth) {
|
| 1591 |
-
const indent = ' '.repeat(depth);
|
| 1592 |
-
const node = nodes.find(n => n.id === nodeId);
|
| 1593 |
-
if (!node) return;
|
| 1594 |
-
const emoji = node._type === 'root' ? 'π³' : node._type === 'outcome' ? 'π' : 'π§ ';
|
| 1595 |
-
md += `${indent}- ${emoji} **[${node._type.toUpperCase()}] ${node._label}**\n`;
|
| 1596 |
-
if (node._description) md += `${indent} - _${node._description.substring(0, 100)}_\n`;
|
| 1597 |
-
if (node._tips && node._tips.length) md += `${indent} - π‘ ${node._tips[0]}\n`;
|
| 1598 |
-
md += '\n';
|
| 1599 |
-
const childIds = [];
|
| 1600 |
-
parentMap.forEach((pid, cid) => { if (pid === nodeId) childIds.push(cid); });
|
| 1601 |
-
childIds.forEach(cid => collectMd(cid, depth+1));
|
| 1602 |
-
}
|
| 1603 |
-
if (root) collectMd(root.id, 0);
|
| 1604 |
-
const blob = new Blob([md], {type:'text/markdown'});
|
| 1605 |
-
const url = URL.createObjectURL(blob);
|
| 1606 |
-
const a = document.createElement('a'); a.href=url; a.download='overthinker_tree.md'; a.click();
|
| 1607 |
-
URL.revokeObjectURL(url);
|
| 1608 |
-
showToast('Markdown exported!','success');
|
| 1609 |
-
}
|
| 1610 |
-
|
| 1611 |
async function exportPathMd() {
|
| 1612 |
if (!selectedNodeId) { showToast('Select a node first', 'error'); return; }
|
| 1613 |
-
if (!treeId) { showToast('No tree', 'error'); return; }
|
| 1614 |
try {
|
| 1615 |
const res = await fetch('/export_path_md', {
|
| 1616 |
-
method:'POST',
|
| 1617 |
-
|
|
|
|
| 1618 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1619 |
const md = await res.text();
|
| 1620 |
const blob = new Blob([md], {type:'text/markdown'});
|
| 1621 |
const url = URL.createObjectURL(blob);
|
|
@@ -1632,7 +1606,6 @@ function svgToPng(svgDoc, filename) {
|
|
| 1632 |
const img = new Image();
|
| 1633 |
const container = document.getElementById('ensurePngContainer');
|
| 1634 |
container.appendChild(img);
|
| 1635 |
-
|
| 1636 |
img.onload = function() {
|
| 1637 |
try {
|
| 1638 |
const cv = document.createElement('canvas');
|
|
@@ -1655,7 +1628,7 @@ function svgToPng(svgDoc, filename) {
|
|
| 1655 |
container.removeChild(img);
|
| 1656 |
}
|
| 1657 |
};
|
| 1658 |
-
img.onerror = function(
|
| 1659 |
showToast('PNG export failed, try SVG', 'error');
|
| 1660 |
URL.revokeObjectURL(url);
|
| 1661 |
container.removeChild(img);
|
|
@@ -1678,6 +1651,35 @@ function exportPathPng() {
|
|
| 1678 |
svgToPng(svgDoc, 'overthinker_path.png');
|
| 1679 |
}
|
| 1680 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1681 |
document.getElementById('export-svg-btn').addEventListener('click', exportSvg);
|
| 1682 |
document.getElementById('export-json-btn').addEventListener('click', exportJson);
|
| 1683 |
document.getElementById('export-md-btn').addEventListener('click', exportMarkdown);
|
|
@@ -1696,7 +1698,8 @@ function fitView() {
|
|
| 1696 |
}
|
| 1697 |
|
| 1698 |
function clearTree() {
|
| 1699 |
-
nodes=[]; links=[]; parentMap=new Map(); expandedSet.clear(); selectedNodeId=null;
|
|
|
|
| 1700 |
if (svg) svg.selectAll('*').remove();
|
| 1701 |
document.getElementById('toolbar').style.display = 'none';
|
| 1702 |
removeForeignPopup();
|
|
@@ -1710,6 +1713,25 @@ function clearTree() {
|
|
| 1710 |
document.getElementById('fit-btn').addEventListener('click', fitView);
|
| 1711 |
document.getElementById('clear-btn').addEventListener('click', clearTree);
|
| 1712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1713 |
// ---------- KEYBOARD SHORTCUTS ----------
|
| 1714 |
document.addEventListener('keydown', (ev) => {
|
| 1715 |
if (ev.target.tagName === 'TEXTAREA' || ev.target.tagName === 'INPUT') return;
|
|
@@ -1727,8 +1749,6 @@ document.addEventListener('keydown', (ev) => {
|
|
| 1727 |
ev.preventDefault();
|
| 1728 |
}
|
| 1729 |
if (key === 'a' && selectedNodeId) { addOptionsToNode(selectedNodeId, parseInt(branchInput.value)||3); ev.preventDefault(); }
|
| 1730 |
-
if (key === 'z' && ev.ctrlKey && !ev.shiftKey) { showToast('Undo supported via API', 'info'); ev.preventDefault(); }
|
| 1731 |
-
if (key === 'z' && ev.ctrlKey && ev.shiftKey) { showToast('Redo supported via API', 'info'); ev.preventDefault(); }
|
| 1732 |
if (key === 'escape') {
|
| 1733 |
document.getElementById('context-menu').style.display = 'none';
|
| 1734 |
selectedNodeId = null; removeForeignPopup(); clearHighlights();
|
|
@@ -1759,7 +1779,6 @@ document.getElementById('info-close').addEventListener('click', () => {
|
|
| 1759 |
document.getElementById('help-close').addEventListener('click', () => {
|
| 1760 |
document.getElementById('help-dropdown').classList.remove('show');
|
| 1761 |
});
|
| 1762 |
-
// Close on overlay click
|
| 1763 |
document.querySelectorAll('.dropdown-overlay').forEach(el => {
|
| 1764 |
el.addEventListener('click', (e) => {
|
| 1765 |
if (e.target === el) el.classList.remove('show');
|
|
@@ -1782,10 +1801,11 @@ document.getElementById('sample-btn').addEventListener('click', () => {
|
|
| 1782 |
});
|
| 1783 |
|
| 1784 |
// ---------- INIT ----------
|
|
|
|
| 1785 |
initSvg();
|
| 1786 |
if (localStorage.getItem('overthinker_theme') === 'light') toggleTheme();
|
| 1787 |
|
| 1788 |
-
console.log('π§ Overthinker β
|
| 1789 |
</script>
|
| 1790 |
</body>
|
| 1791 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
| 6 |
+
<title>Overthinker β v28 Trace Upload</title>
|
| 7 |
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
| 8 |
<style>
|
| 9 |
* { margin:0; padding:0; box-sizing:border-box; }
|
|
|
|
| 344 |
<div id="info-dropdown" class="dropdown-overlay">
|
| 345 |
<div class="dropdown-box">
|
| 346 |
<button class="dropdown-close" id="info-close">β</button>
|
| 347 |
+
<h3>βΉοΈ About Overthinker v28</h3>
|
| 348 |
<div class="dropdown-section">
|
| 349 |
<h4>π€ Model</h4>
|
| 350 |
+
<p>Powered by <strong>nvidia/nemotron-3-nano-30b-a3b</strong> via <strong>OpenRouter</strong>. Optimized for structured decision tree generation with dual-prompt architecture (options vs outcomes).</p>
|
| 351 |
</div>
|
| 352 |
<div class="dropdown-section">
|
| 353 |
<h4>π Framework</h4>
|
| 354 |
+
<p>Built on <strong>Gradio.Server</strong> with SQLite per-session persistence. Fully client-side rendering with D3.js for interactive tree visualization.</p>
|
| 355 |
</div>
|
| 356 |
<div class="dropdown-section">
|
| 357 |
<h4>β¨ Key Features</h4>
|
|
|
|
| 362 |
<li>π Path sidebar with depth tracking & tips</li>
|
| 363 |
<li>πΌ Export: SVG, JSON, Markdown, PNG</li>
|
| 364 |
<li>π Dark/Light theme toggle</li>
|
| 365 |
+
<li>β¨ Keyboard shortcuts (E, R, A, F, C)</li>
|
|
|
|
| 366 |
<li>π Regenerate with user context</li>
|
| 367 |
+
<li>πΎ Multi-user session isolation via SQLite</li>
|
| 368 |
+
<li>π€ Upload trace to HF dataset</li>
|
| 369 |
</ul>
|
| 370 |
</div>
|
| 371 |
<div class="dropdown-section">
|
|
|
|
| 408 |
<h4>5. Export</h4>
|
| 409 |
<p>Use the toolbar buttons to export the entire tree or just the selected path in your preferred format: SVG, JSON, Markdown, or PNG.</p>
|
| 410 |
</div>
|
| 411 |
+
<div class="dropdown-section">
|
| 412 |
+
<h4>6. Upload Trace</h4>
|
| 413 |
+
<p>Click the <strong>Upload Trace</strong> button in the toolbar to upload your decision tree to a shared HuggingFace dataset for public analysis.</p>
|
| 414 |
+
</div>
|
| 415 |
</div>
|
| 416 |
</div>
|
| 417 |
|
|
|
|
| 439 |
|
| 440 |
<div class="toolbar" id="toolbar" style="display:none;">
|
| 441 |
<div class="toolbar-group">
|
|
|
|
|
|
|
|
|
|
| 442 |
<button class="btn btn-secondary btn-icon" id="fit-btn" title="Fit View (F)">π―</button>
|
| 443 |
<button class="btn btn-secondary btn-icon" id="clear-btn" title="Clear (C)">π</button>
|
| 444 |
<div class="toolbar-divider"></div>
|
|
|
|
| 447 |
<button class="btn btn-secondary" id="export-md-btn">π Markdown</button>
|
| 448 |
<button class="btn btn-secondary" id="export-png-btn">π· PNG</button>
|
| 449 |
</div>
|
| 450 |
+
<div class="toolbar-group">
|
| 451 |
+
<button class="btn btn-secondary" id="upload-trace-btn">π€ Upload Trace</button>
|
| 452 |
+
</div>
|
| 453 |
<div class="stats">
|
| 454 |
<span>Nodes: <span class="highlight" id="stat-nodes">0</span></span>
|
| 455 |
<span>Depth: <span class="highlight" id="stat-depth">0</span></span>
|
|
|
|
| 469 |
|
| 470 |
<div id="loading-overlay" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:999;align-items:center;justify-content:center;flex-direction:column;">
|
| 471 |
<div class="spinner" style="width:40px;height:40px;border:4px solid rgba(255,255,255,0.1);border-top-color:#ffd700;border-radius:50%;animation:spin 0.8s linear infinite;margin-bottom:16px;"></div>
|
| 472 |
+
<p id="loading-text" style="color:#ffd700;font-size:0.9em;">Thinking deeply...</p>
|
| 473 |
</div>
|
| 474 |
|
| 475 |
<div class="toast" id="toast"></div>
|
|
|
|
| 480 |
|
| 481 |
<script>
|
| 482 |
// =============================================
|
| 483 |
+
// OVER THINKER v28 β Session Mode (POST-only) with Trace Upload
|
| 484 |
+
// - All API calls use POST with session_id in body
|
| 485 |
+
// - Session ID stored in localStorage
|
| 486 |
+
// - No undo/redo, no fallback generators
|
| 487 |
+
// - Full D3 tree with path sidebar, export, keyboard shortcuts
|
| 488 |
+
// - Upload Trace button to push tree to HF dataset
|
| 489 |
// =============================================
|
| 490 |
|
| 491 |
let nodes = [];
|
|
|
|
| 493 |
let selectedNodeId = null;
|
| 494 |
let parentMap = new Map();
|
| 495 |
let expandedSet = new Set();
|
| 496 |
+
let sessionId = '';
|
| 497 |
let svg, gLinks, gNodes, gMain, zoomBehavior;
|
| 498 |
let foreignPopup = null;
|
| 499 |
let isDark = true;
|
|
|
|
|
|
|
| 500 |
|
| 501 |
const CARD_W = 230, CARD_H = 130;
|
| 502 |
const HEAVY_W = 300, HEAVY_H = 480;
|
|
|
|
| 555 |
return [label.substring(0, splitIdx).trim(), label.substring(splitIdx).trim()];
|
| 556 |
}
|
| 557 |
|
| 558 |
+
function getSessionId() {
|
| 559 |
+
if (!sessionId) {
|
| 560 |
+
sessionId = localStorage.getItem('overthinker_session_id');
|
| 561 |
+
if (!sessionId) {
|
| 562 |
+
sessionId = crypto.randomUUID();
|
| 563 |
+
localStorage.setItem('overthinker_session_id', sessionId);
|
| 564 |
+
}
|
| 565 |
+
}
|
| 566 |
+
return sessionId;
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
// ---------- LAYOUT ----------
|
| 570 |
function computeTreeLayout() {
|
| 571 |
if (nodes.length === 0) return;
|
|
|
|
| 950 |
});
|
| 951 |
}
|
| 952 |
|
| 953 |
+
// ---------- API CALLS (POST with session_id) ----------
|
| 954 |
+
async function apiPost(endpoint, payload) {
|
| 955 |
+
const res = await fetch(endpoint, {
|
| 956 |
+
method: 'POST',
|
| 957 |
+
headers: { 'Content-Type': 'application/json' },
|
| 958 |
+
body: JSON.stringify({ session_id: getSessionId(), ...payload })
|
| 959 |
+
});
|
| 960 |
+
if (!res.ok) {
|
| 961 |
+
const errData = await res.json().catch(() => ({}));
|
| 962 |
+
throw new Error(errData.detail || `HTTP ${res.status}`);
|
| 963 |
+
}
|
| 964 |
+
return await res.json();
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
async function generateTree() {
|
| 968 |
const decision = inputEl.value.trim();
|
| 969 |
if (!decision) return;
|
|
|
|
| 971 |
generateBtn.disabled = true;
|
| 972 |
showLoading('Creating decision root...');
|
| 973 |
try {
|
| 974 |
+
const data = await apiPost('/create_tree', { decision });
|
| 975 |
+
const rootNode = data.node;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 976 |
nodes = []; links = []; parentMap = new Map(); expandedSet = new Set(); selectedNodeId = null;
|
|
|
|
| 977 |
addNodeToGraph(rootNode, null);
|
| 978 |
renderCanvas();
|
| 979 |
+
showToast('Root created! Exploring first children...', 'success');
|
| 980 |
+
showLoading('Generating first outcomes...');
|
| 981 |
await exploreNode(rootNode.id, parseInt(branchInput.value) || 3, null);
|
| 982 |
} catch(e) {
|
| 983 |
console.error(e);
|
| 984 |
+
showToast('Generation failed: ' + e.message, 'error');
|
| 985 |
} finally {
|
| 986 |
hideLoading();
|
| 987 |
generateBtn.textContent = 'π³ Generate';
|
|
|
|
| 996 |
async function exploreNode(nodeId, count, comment) {
|
| 997 |
const node = nodes.find(n => n.id === nodeId);
|
| 998 |
if (!node) return;
|
| 999 |
+
const nodeType = node._type || 'outcome';
|
| 1000 |
+
showLoading('Generating ' + getNextTypeLabel(nodeType) + '...');
|
| 1001 |
try {
|
| 1002 |
+
const data = await apiPost('/get_children', {
|
| 1003 |
+
node_id: nodeId,
|
| 1004 |
+
count,
|
| 1005 |
+
node_type: nodeType,
|
| 1006 |
+
comment: comment || ''
|
| 1007 |
+
});
|
| 1008 |
if (expandedSet.has(nodeId)) { removeChildrenOf(nodeId); }
|
| 1009 |
const children = data.children || [];
|
| 1010 |
children.forEach(child => addNodeToGraph(child, nodeId));
|
| 1011 |
expandedSet.add(nodeId);
|
| 1012 |
node._childrenCount = children.length;
|
|
|
|
|
|
|
| 1013 |
renderCanvas();
|
| 1014 |
removeForeignPopup();
|
| 1015 |
selectedNodeId = null;
|
|
|
|
| 1020 |
updatePathSidebar();
|
| 1021 |
} catch(e) {
|
| 1022 |
console.error(e);
|
| 1023 |
+
showToast('Failed: ' + e.message, 'error');
|
| 1024 |
} finally { hideLoading(); }
|
| 1025 |
}
|
| 1026 |
|
| 1027 |
async function addOptionsToNode(nodeId, count) {
|
| 1028 |
+
showLoading('Adding new options...');
|
| 1029 |
try {
|
| 1030 |
+
const data = await apiPost('/add_options', { node_id: nodeId, count });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1031 |
const newChildren = data.children || [];
|
| 1032 |
newChildren.forEach(child => addNodeToGraph(child, nodeId));
|
| 1033 |
const parentNode = nodes.find(n => n.id === nodeId);
|
| 1034 |
if (parentNode) parentNode._childrenCount = (parentNode._childrenCount||0) + newChildren.length;
|
|
|
|
|
|
|
| 1035 |
renderCanvas();
|
| 1036 |
removeForeignPopup();
|
| 1037 |
selectedNodeId = null;
|
|
|
|
| 1040 |
if (parentNode) showPopupForNode(parentNode, true);
|
| 1041 |
updatePathSidebar();
|
| 1042 |
} catch(e) {
|
| 1043 |
+
showToast('Failed: ' + e.message, 'error');
|
| 1044 |
} finally { hideLoading(); }
|
| 1045 |
}
|
| 1046 |
|
| 1047 |
function addNodeToGraph(nodeObj, parentId) {
|
| 1048 |
+
nodeObj._label = nodeObj.label || nodeObj._label || 'Untitled';
|
| 1049 |
+
nodeObj._description = nodeObj.description || nodeObj._description || '';
|
| 1050 |
+
nodeObj._childrenCount = nodeObj._childrenCount || 0;
|
| 1051 |
+
nodeObj._type = nodeObj.type || nodeObj._type || 'outcome';
|
| 1052 |
+
nodeObj._tips = nodeObj.tips || nodeObj._tips || [];
|
|
|
|
| 1053 |
nodeObj.depth = parentId ? (nodes.find(n=>n.id===parentId)?.depth ?? 0) + 1 : 0;
|
| 1054 |
nodeObj.x = 0; nodeObj.y = 0;
|
| 1055 |
+
if (!nodes.find(n => n.id === nodeObj.id)) {
|
| 1056 |
+
nodes.push(nodeObj);
|
| 1057 |
+
if (parentId !== null && parentId !== undefined) {
|
| 1058 |
+
parentMap.set(nodeObj.id, parentId);
|
| 1059 |
+
links.push({ source: parentId, target: nodeObj.id });
|
| 1060 |
+
}
|
| 1061 |
}
|
| 1062 |
}
|
| 1063 |
|
|
|
|
| 1409 |
rect.setAttribute('stroke-width', '2');
|
| 1410 |
g.appendChild(rect);
|
| 1411 |
|
|
|
|
| 1412 |
const [line1, line2] = splitLabel(n._label || '');
|
|
|
|
| 1413 |
const txt1 = document.createElementNS(ns, 'text');
|
| 1414 |
txt1.setAttribute('x', '0');
|
| 1415 |
txt1.setAttribute('y', -(tileH/2) + 20);
|
|
|
|
| 1417 |
txt1.setAttribute('fill', labelColor);
|
| 1418 |
txt1.setAttribute('font-size', '12');
|
| 1419 |
txt1.setAttribute('font-weight', '700');
|
|
|
|
| 1420 |
txt1.textContent = line1 || '';
|
| 1421 |
g.appendChild(txt1);
|
| 1422 |
|
|
|
|
| 1428 |
txt2.setAttribute('fill', labelColor);
|
| 1429 |
txt2.setAttribute('font-size', '12');
|
| 1430 |
txt2.setAttribute('font-weight', '700');
|
|
|
|
| 1431 |
txt2.textContent = line2;
|
| 1432 |
g.appendChild(txt2);
|
| 1433 |
}
|
|
|
|
| 1438 |
typeTxt.setAttribute('text-anchor', 'middle');
|
| 1439 |
typeTxt.setAttribute('fill', typeFillColor);
|
| 1440 |
typeTxt.setAttribute('font-size', '9');
|
|
|
|
| 1441 |
const typeLabels = { root: 'π³ ROOT', input: 'π§ INPUT', outcome: 'π OUTCOME' };
|
| 1442 |
+
typeTxt.textContent = typeLabels[n._type] || 'π OUTCOME';
|
| 1443 |
g.appendChild(typeTxt);
|
| 1444 |
|
| 1445 |
const maxDescLen = 35;
|
|
|
|
| 1464 |
dtxt.setAttribute('text-anchor', 'middle');
|
| 1465 |
dtxt.setAttribute('fill', isDark ? '#aaa' : '#666');
|
| 1466 |
dtxt.setAttribute('font-size', '8');
|
|
|
|
| 1467 |
dtxt.textContent = line;
|
| 1468 |
g.appendChild(dtxt);
|
| 1469 |
});
|
|
|
|
| 1493 |
ttxt.setAttribute('text-anchor', 'middle');
|
| 1494 |
ttxt.setAttribute('fill', '#7ec8a0');
|
| 1495 |
ttxt.setAttribute('font-size', '8');
|
|
|
|
| 1496 |
ttxt.textContent = line;
|
| 1497 |
g.appendChild(ttxt);
|
| 1498 |
});
|
|
|
|
| 1503 |
depthTxt.setAttribute('y', tileH/2 - 10);
|
| 1504 |
depthTxt.setAttribute('fill', isDark ? '#c9aaff' : '#7c5cbf');
|
| 1505 |
depthTxt.setAttribute('font-size', '10');
|
|
|
|
| 1506 |
depthTxt.textContent = 'D' + (n.depth || 0);
|
| 1507 |
g.appendChild(depthTxt);
|
| 1508 |
|
|
|
|
| 1512 |
chTxt.setAttribute('text-anchor', 'end');
|
| 1513 |
chTxt.setAttribute('fill', isDark ? '#7ecfff' : '#2e86ab');
|
| 1514 |
chTxt.setAttribute('font-size', '10');
|
|
|
|
| 1515 |
chTxt.textContent = 'β³' + (n._childrenCount || 0);
|
| 1516 |
g.appendChild(chTxt);
|
| 1517 |
|
|
|
|
| 1525 |
title.setAttribute('fill', labelColor);
|
| 1526 |
title.setAttribute('font-size', '18');
|
| 1527 |
title.setAttribute('font-weight', '700');
|
|
|
|
| 1528 |
const rootLabel = exportNodes.find(n => n.depth === 0)?._label || 'Decision Tree';
|
| 1529 |
title.textContent = isPathOnly ? `π Path: ${rootLabel}` : `π§ ${rootLabel}`;
|
| 1530 |
svgDoc.appendChild(title);
|
|
|
|
| 1556 |
}
|
| 1557 |
|
| 1558 |
async function exportJson() {
|
|
|
|
| 1559 |
try {
|
| 1560 |
+
const data = await apiPost('/export_json', {});
|
|
|
|
|
|
|
| 1561 |
const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
|
| 1562 |
const url = URL.createObjectURL(blob);
|
| 1563 |
const a = document.createElement('a'); a.href=url; a.download='overthinker_tree.json'; a.click();
|
|
|
|
| 1568 |
|
| 1569 |
async function exportPathJson() {
|
| 1570 |
if (!selectedNodeId) { showToast('Select a node first', 'error'); return; }
|
|
|
|
| 1571 |
try {
|
| 1572 |
+
const data = await apiPost('/export_path_json', { node_id: selectedNodeId });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1573 |
const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
|
| 1574 |
const url = URL.createObjectURL(blob);
|
| 1575 |
const a = document.createElement('a'); a.href=url; a.download='overthinker_path.json'; a.click();
|
|
|
|
| 1578 |
} catch(e) { showToast('Export failed: '+e.message, 'error'); }
|
| 1579 |
}
|
| 1580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1581 |
async function exportPathMd() {
|
| 1582 |
if (!selectedNodeId) { showToast('Select a node first', 'error'); return; }
|
|
|
|
| 1583 |
try {
|
| 1584 |
const res = await fetch('/export_path_md', {
|
| 1585 |
+
method: 'POST',
|
| 1586 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1587 |
+
body: JSON.stringify({ session_id: getSessionId(), node_id: selectedNodeId })
|
| 1588 |
});
|
| 1589 |
+
if (!res.ok) {
|
| 1590 |
+
const errData = await res.json().catch(() => ({}));
|
| 1591 |
+
throw new Error(errData.detail || `HTTP ${res.status}`);
|
| 1592 |
+
}
|
| 1593 |
const md = await res.text();
|
| 1594 |
const blob = new Blob([md], {type:'text/markdown'});
|
| 1595 |
const url = URL.createObjectURL(blob);
|
|
|
|
| 1606 |
const img = new Image();
|
| 1607 |
const container = document.getElementById('ensurePngContainer');
|
| 1608 |
container.appendChild(img);
|
|
|
|
| 1609 |
img.onload = function() {
|
| 1610 |
try {
|
| 1611 |
const cv = document.createElement('canvas');
|
|
|
|
| 1628 |
container.removeChild(img);
|
| 1629 |
}
|
| 1630 |
};
|
| 1631 |
+
img.onerror = function() {
|
| 1632 |
showToast('PNG export failed, try SVG', 'error');
|
| 1633 |
URL.revokeObjectURL(url);
|
| 1634 |
container.removeChild(img);
|
|
|
|
| 1651 |
svgToPng(svgDoc, 'overthinker_path.png');
|
| 1652 |
}
|
| 1653 |
|
| 1654 |
+
function exportMarkdown() {
|
| 1655 |
+
if (!nodes.length) { showToast('Generate a tree first', 'error'); return; }
|
| 1656 |
+
let md = '# π§ Overthinker Decision Tree\n\n';
|
| 1657 |
+
const root = nodes.find(n => n.depth === 0);
|
| 1658 |
+
if (root) {
|
| 1659 |
+
md += `## π³ Root Decision\n**${root._label}**\n\n`;
|
| 1660 |
+
if (root._description) md += `> ${root._description}\n\n`;
|
| 1661 |
+
}
|
| 1662 |
+
function collectMd(nodeId, depth) {
|
| 1663 |
+
const indent = ' '.repeat(depth);
|
| 1664 |
+
const node = nodes.find(n => n.id === nodeId);
|
| 1665 |
+
if (!node) return;
|
| 1666 |
+
const emoji = node._type === 'root' ? 'π³' : node._type === 'outcome' ? 'π' : 'π§ ';
|
| 1667 |
+
md += `${indent}- ${emoji} **[${node._type.toUpperCase()}] ${node._label}**\n`;
|
| 1668 |
+
if (node._description) md += `${indent} - _${node._description.substring(0, 100)}_\n`;
|
| 1669 |
+
if (node._tips && node._tips.length) md += `${indent} - π‘ ${node._tips[0]}\n`;
|
| 1670 |
+
md += '\n';
|
| 1671 |
+
const childIds = [];
|
| 1672 |
+
parentMap.forEach((pid, cid) => { if (pid === nodeId) childIds.push(cid); });
|
| 1673 |
+
childIds.forEach(cid => collectMd(cid, depth+1));
|
| 1674 |
+
}
|
| 1675 |
+
if (root) collectMd(root.id, 0);
|
| 1676 |
+
const blob = new Blob([md], {type:'text/markdown'});
|
| 1677 |
+
const url = URL.createObjectURL(blob);
|
| 1678 |
+
const a = document.createElement('a'); a.href=url; a.download='overthinker_tree.md'; a.click();
|
| 1679 |
+
URL.revokeObjectURL(url);
|
| 1680 |
+
showToast('Markdown exported!','success');
|
| 1681 |
+
}
|
| 1682 |
+
|
| 1683 |
document.getElementById('export-svg-btn').addEventListener('click', exportSvg);
|
| 1684 |
document.getElementById('export-json-btn').addEventListener('click', exportJson);
|
| 1685 |
document.getElementById('export-md-btn').addEventListener('click', exportMarkdown);
|
|
|
|
| 1698 |
}
|
| 1699 |
|
| 1700 |
function clearTree() {
|
| 1701 |
+
nodes=[]; links=[]; parentMap=new Map(); expandedSet.clear(); selectedNodeId=null; sessionId='';
|
| 1702 |
+
localStorage.removeItem('overthinker_session_id');
|
| 1703 |
if (svg) svg.selectAll('*').remove();
|
| 1704 |
document.getElementById('toolbar').style.display = 'none';
|
| 1705 |
removeForeignPopup();
|
|
|
|
| 1713 |
document.getElementById('fit-btn').addEventListener('click', fitView);
|
| 1714 |
document.getElementById('clear-btn').addEventListener('click', clearTree);
|
| 1715 |
|
| 1716 |
+
// ---------- UPLOAD TRACE BUTTON ----------
|
| 1717 |
+
document.getElementById('upload-trace-btn').addEventListener('click', async () => {
|
| 1718 |
+
if (!nodes || nodes.length === 0) {
|
| 1719 |
+
showToast('Generate a tree first', 'error');
|
| 1720 |
+
return;
|
| 1721 |
+
}
|
| 1722 |
+
showToast('Uploading trace to HF dataset...', 'info');
|
| 1723 |
+
try {
|
| 1724 |
+
const result = await apiPost('/upload_trace', {});
|
| 1725 |
+
if (result.status === 'success') {
|
| 1726 |
+
showToast(result.message, 'success');
|
| 1727 |
+
} else {
|
| 1728 |
+
showToast(result.message || 'Upload failed', 'error');
|
| 1729 |
+
}
|
| 1730 |
+
} catch(e) {
|
| 1731 |
+
showToast('Upload failed: ' + e.message, 'error');
|
| 1732 |
+
}
|
| 1733 |
+
});
|
| 1734 |
+
|
| 1735 |
// ---------- KEYBOARD SHORTCUTS ----------
|
| 1736 |
document.addEventListener('keydown', (ev) => {
|
| 1737 |
if (ev.target.tagName === 'TEXTAREA' || ev.target.tagName === 'INPUT') return;
|
|
|
|
| 1749 |
ev.preventDefault();
|
| 1750 |
}
|
| 1751 |
if (key === 'a' && selectedNodeId) { addOptionsToNode(selectedNodeId, parseInt(branchInput.value)||3); ev.preventDefault(); }
|
|
|
|
|
|
|
| 1752 |
if (key === 'escape') {
|
| 1753 |
document.getElementById('context-menu').style.display = 'none';
|
| 1754 |
selectedNodeId = null; removeForeignPopup(); clearHighlights();
|
|
|
|
| 1779 |
document.getElementById('help-close').addEventListener('click', () => {
|
| 1780 |
document.getElementById('help-dropdown').classList.remove('show');
|
| 1781 |
});
|
|
|
|
| 1782 |
document.querySelectorAll('.dropdown-overlay').forEach(el => {
|
| 1783 |
el.addEventListener('click', (e) => {
|
| 1784 |
if (e.target === el) el.classList.remove('show');
|
|
|
|
| 1801 |
});
|
| 1802 |
|
| 1803 |
// ---------- INIT ----------
|
| 1804 |
+
getSessionId();
|
| 1805 |
initSvg();
|
| 1806 |
if (localStorage.getItem('overthinker_theme') === 'light') toggleTheme();
|
| 1807 |
|
| 1808 |
+
console.log('π§ Overthinker v28 β Session Mode Active, ID:', sessionId);
|
| 1809 |
</script>
|
| 1810 |
</body>
|
| 1811 |
</html>
|