broadfield-dev commited on
Commit
351032d
Β·
verified Β·
1 Parent(s): aef5dad

Update templates/index.html

Browse files
Files changed (1) hide show
  1. 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 β€” Gradio Server</title>
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/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8</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 integrated FastAPI routes. Swagger API docs at <code>/docs</code>. 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,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>⌨️ Keyboard shortcuts (E, R, A, F, C)</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;">Loading...</p>
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 β€” Gradio Server Backend
479
- // - Dual prompts: Options for input, Outcomes for outcome
480
- // - Model: nvidia/nemotron-3-nano-30b-a3b via OpenRouter
 
 
 
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 treeId = null;
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 res = await fetch('/create_tree', {
945
- method:'POST', headers:{'Content-Type':'application/json'},
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
- showLoading('Exploring first outcomes...');
 
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
- showLoading('Generating ' + getNextTypeLabel(node._type || 'outcome') + '...');
 
976
  try {
977
- const params = new URLSearchParams({ node_id: nodeId, count, node_type: node._type || 'outcome' });
978
- if (comment) params.append('comment', comment);
979
- const res = await fetch(`/get_children?${params}`);
980
- const data = await res.json();
981
- if (data.error) { showToast(data.error, 'error'); return; }
 
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 res = await fetch('/add_options', {
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._comments = nodeObj.comments || [];
1034
- nodeObj._childrenCount = (nodeObj.children && nodeObj.children.length) || 0;
1035
- nodeObj._type = nodeObj.type || 'outcome';
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.push(nodeObj);
1040
- if (parentId !== null && parentId !== undefined) {
1041
- parentMap.set(nodeObj.id, parentId);
1042
- links.push({ source: parentId, target: nodeObj.id });
 
 
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[type] || 'πŸ“Š OUTCOME';
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 res = await fetch('/export_json', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ tree_id: treeId }) });
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 res = await fetch('/export_path_json', {
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', headers:{'Content-Type':'application/json'},
1617
- body:JSON.stringify({ node_id: selectedNodeId, tree_id: treeId })
 
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(e) {
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; treeId=null; treeDataRoot=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 β€” Production Ready');
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>