pageparse.ai / web /templates /file_explorer.html
Varun2007's picture
initial clean deployment commit with compilers
8c3e275
Raw
History Blame Contribute Delete
30.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PageParse - Universal File Analyzer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root { --bg: #0d1117; --card: #161b22; --border: #30363d; --text: #e6edf3; --text-muted: #8b949e; --accent: #58a6ff; --accent-hover: #79c0ff; --success: #3fb950; --warning: #d29922; --danger: #f85149; --radius: 8px; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
header { display: flex; align-items: center; justify-content: space-between; padding: 16px 0; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
header h1 { font-size: 24px; background: linear-gradient(135deg, var(--accent), #a371f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
header nav { display: flex; gap: 8px; }
.nav-btn { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 8px 16px; border-radius: var(--radius); cursor: pointer; font-size: 14px; }
.nav-btn:hover { border-color: var(--accent); }
.nav-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.panel { display: none; }
.panel.active { display: block; }
.upload-zone { border: 2px dashed var(--border); border-radius: 12px; padding: 60px 20px; text-align: center; cursor: pointer; transition: all .3s; background: var(--card); margin-bottom: 24px; }
.upload-zone:hover, .upload-zone.dragover { border-color: var(--accent); background: rgba(88,166,255,0.05); }
.upload-zone-icon { font-size: 48px; margin-bottom: 16px; }
.upload-zone h2 { font-size: 20px; margin-bottom: 8px; }
.upload-zone p { color: var(--text-muted); font-size: 14px; }
.upload-zone input { display: none; }
.file-info { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; margin-bottom: 16px; display: flex; align-items: center; gap: 12px; }
.file-info .icon { font-size: 32px; }
.file-info .name { flex: 1; font-weight: 600; }
.file-info .size { color: var(--text-muted); font-size: 13px; }
.file-info .remove-btn { background: none; border: none; color: var(--danger); cursor: pointer; font-size: 20px; padding: 4px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 16px; }
.card h3 { font-size: 16px; margin-bottom: 12px; color: var(--accent); }
.card .label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 4px; }
.card .value { font-size: 14px; line-height: 1.6; }
.code-block { background: #010409; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 13px; overflow-x: auto; line-height: 1.5; margin: 8px 0; max-height: 400px; overflow-y: auto; }
.step-item { padding: 10px 12px; border-left: 3px solid var(--accent); margin-bottom: 8px; background: rgba(88,166,255,0.05); border-radius: 0 var(--radius) var(--radius) 0; font-size: 14px; line-height: 1.5; }
.step-num { display: inline-block; background: var(--accent); color: #fff; border-radius: 50%; width: 22px; height: 22px; text-align: center; line-height: 22px; font-size: 12px; margin-right: 8px; flex-shrink: 0; }
.summary-box { background: linear-gradient(135deg, rgba(63,185,80,0.1), rgba(88,166,255,0.1)); border: 1px solid var(--success); border-radius: var(--radius); padding: 16px; font-size: 15px; line-height: 1.6; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
.badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.badge-blue { background: rgba(88,166,255,0.15); color: var(--accent); }
.badge-green { background: rgba(63,185,80,0.15); color: var(--success); }
.badge-yellow { background: rgba(210,153,34,0.15); color: var(--warning); }
.badge-red { background: rgba(248,81,73,0.15); color: var(--danger); }
.dsa-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; margin-bottom: 20px; }
.dsa-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; cursor: pointer; transition: all .2s; }
.dsa-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.dsa-card h4 { font-size: 15px; margin-bottom: 6px; }
.dsa-card p { font-size: 13px; color: var(--text-muted); line-height: 1.5; }
.dsa-card .complexity { display: flex; gap: 12px; margin-top: 8px; font-size: 12px; }
.array-bars { display: flex; align-items: flex-end; gap: 4px; height: 200px; padding: 20px 0; justify-content: center; }
.array-bar { background: var(--accent); border-radius: 4px 4px 0 0; transition: all .3s; min-width: 30px; display: flex; align-items: flex-end; justify-content: center; font-size: 11px; color: #fff; padding-bottom: 4px; }
.array-bar.highlight { background: var(--warning); }
.array-bar.sorted { background: var(--success); }
.viz-controls { display: flex; align-items: center; gap: 12px; margin: 16px 0; }
.viz-controls button { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 8px 16px; border-radius: var(--radius); cursor: pointer; font-size: 14px; }
.viz-controls button:hover { border-color: var(--accent); }
.viz-controls button:disabled { opacity: 0.4; cursor: not-allowed; }
.viz-controls .step-info { color: var(--text-muted); font-size: 13px; flex: 1; text-align: center; }
.viz-desc { font-size: 14px; color: var(--text-muted); margin: 8px 0; padding: 8px 12px; background: rgba(88,166,255,0.05); border-radius: var(--radius); border-left: 3px solid var(--accent); }
.tree-canvas, .ll-canvas { width: 100%; height: 300px; border: 1px solid var(--border); border-radius: var(--radius); margin: 12px 0; }
.template-select { width: 100%; padding: 10px 12px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; margin-bottom: 12px; }
.input-data { width: 100%; padding: 10px 12px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px; margin-bottom: 12px; }
.btn-primary { background: var(--accent); border: none; color: #fff; padding: 10px 24px; border-radius: var(--radius); cursor: pointer; font-size: 14px; font-weight: 600; }
.btn-primary:hover { background: var(--accent-hover); }
.loading { text-align: center; padding: 40px; color: var(--text-muted); }
.loading::after { content: ''; display: inline-block; width: 24px; height: 24px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; margin-left: 8px; vertical-align: middle; }
@keyframes spin { to { transform: rotate(360deg); } }
.tabs { display: flex; gap: 4px; margin-bottom: 16px; background: var(--card); border-radius: var(--radius); padding: 4px; border: 1px solid var(--border); }
.tab { padding: 8px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; background: none; color: var(--text-muted); }
.tab:hover { color: var(--text); }
.tab.active { background: var(--accent); color: #fff; }
.tab-content { display: none; }
.tab-content.active { display: block; }
textarea.code-input { width: 100%; min-height: 200px; background: #010409; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; color: var(--text); font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.5; resize: vertical; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; margin: 2px; }
.tag-py { background: #306998; color: #fff; }
.tag-js { background: #f0db4f; color: #323330; }
.tag-html { background: #e44d26; color: #fff; }
.tag-css { background: #264de4; color: #fff; }
.tag-java { background: #b07219; color: #fff; }
.tag-c { background: #555; color: #fff; }
.tag-cpp { background: #00599c; color: #fff; }
.image-preview { max-width: 100%; max-height: 400px; border-radius: var(--radius); border: 1px solid var(--border); margin: 12px 0; }
@media (max-width: 768px) { .grid-2, .grid-3 { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="container">
<header>
<h1>PageParse</h1>
<nav>
<button class="nav-btn active" data-panel="upload">Upload</button>
<button class="nav-btn" data-panel="dsa">DSA Visualizer</button>
<button class="nav-btn" data-panel="chat">Chat</button>
</nav>
</header>
<div id="panel-upload" class="panel active">
<div class="upload-zone" id="dropZone">
<div class="upload-zone-icon">&#x1F4C1;</div>
<h2>Upload any file</h2>
<p>Drop a file here or click to browse — Images, Audio, Video, Code (.py .js .c .cpp .java .html .css etc.)</p>
<input type="file" id="fileInput">
</div>
<div id="fileInfo" class="file-info" style="display:none">
<div class="icon" id="fileIcon">&#x1F4C4;</div>
<div><div class="name" id="fileName"></div><div class="size" id="fileSize"></div></div>
<button class="remove-btn" onclick="resetUpload()">&times;</button>
</div>
<div id="results" style="display:none">
<div class="tabs">
<button class="tab active" data-tab="overview">Overview</button>
<button class="tab" data-tab="steps">Step-by-Step</button>
<button class="tab" data-tab="preview">Preview</button>
</div>
<div id="tab-overview" class="tab-content active">
<div class="summary-box" id="overviewSummary"></div>
<div id="ovImageContainer" style="display:none;text-align:center;margin:16px 0">
<img id="ovImage" class="image-preview" alt="Uploaded image">
</div>
<div class="grid-3" style="margin-top:16px">
<div class="card"><div class="label">File Type</div><div class="value" id="ovFileType"></div></div>
<div class="card"><div class="label">Size</div><div class="value" id="ovSize"></div></div>
<div class="card"><div class="label">Lines of Code</div><div class="value" id="ovLines"></div></div>
</div>
<div class="card" id="ovExplanationCard">
<h3>Explanation</h3>
<div class="value" id="ovExplanation" style="white-space:pre-wrap"></div>
</div>
<div class="card" id="ovComplexityCard" style="display:none">
<h3>Complexity Analysis</h3>
<div class="grid-2">
<div><div class="label">Time Complexity</div><div class="value" id="ovTime"></div></div>
<div><div class="label">Space Complexity</div><div class="value" id="ovSpace"></div></div>
</div>
</div>
</div>
<div id="tab-steps" class="tab-content">
<div class="card"><h3>Step-by-Step Breakdown</h3><div id="stepsList"></div></div>
</div>
<div id="tab-preview" class="tab-content">
<div class="card"><h3>Content Preview</h3><div id="previewContent"></div></div>
</div>
</div>
</div>
<div id="panel-dsa" class="panel">
<div class="card">
<h3>DSA Visualizer</h3>
<p style="color:var(--text-muted);font-size:14px;margin-bottom:12px">Explore Data Structures & Algorithms with interactive step-by-step visualization</p>
<select class="template-select" id="dsaTemplate">
<option value="">-- Select a built-in template --</option>
</select>
<textarea class="code-input" id="dsaCode" placeholder="Or paste your code here..."></textarea>
<div class="grid-2" style="margin-top:12px">
<input class="input-data" id="dsaInput" placeholder="Input data (comma-separated, e.g. 64,34,25,12)">
<div style="display:flex;gap:8px">
<button class="btn-primary" onclick="runVisualization()">Visualize</button>
<button class="btn-primary" style="background:var(--card);color:var(--text);border:1px solid var(--border)" onclick="explainCode()">Explain Code</button>
</div>
</div>
</div>
<div id="dsaResult" style="display:none">
<div class="summary-box" id="dsaSummary"></div>
<div class="card">
<h3>Visualization</h3>
<div class="viz-controls">
<button id="vizPrev" onclick="prevStep()">&#9664; Prev</button>
<div class="step-info">Step <span id="vizStepNum">0</span> / <span id="vizTotalSteps">0</span></div>
<button id="vizNext" onclick="nextStep()">Next &#9654;</button>
<button onclick="playSteps()">&#9654; Play</button>
</div>
<div id="vizDescription" class="viz-desc"></div>
<div id="vizContainer"></div>
</div>
<div class="card" id="dsaExplanationCard" style="display:none">
<h3>Detailed Explanation</h3>
<div class="value" id="dsaExplanation"></div>
</div>
<div class="card" id="dsaCodeDisplay" style="display:none">
<h3>Code</h3>
<pre class="code-block" id="dsaCodeBlock"></pre>
</div>
</div>
</div>
</div>
<div id="panel-chat" class="panel">
<div class="card">
<h3>AI Assistant</h3>
<p style="color:var(--text-muted);font-size:14px;margin-bottom:12px">Ask questions about your uploaded files or get help with code, text, and more.</p>
<div id="chatMessages" style="height:400px;overflow-y:auto;border:1px solid var(--border);border-radius:var(--radius);padding:12px;margin-bottom:12px;background:#010409;">
<div style="text-align:center;color:var(--text-muted);padding:40px 0;font-size:14px;" id="chatPlaceholder">Upload a file or ask a question to get started.</div>
</div>
<div style="display:flex;gap:8px">
<input class="input-data" id="chatInput" placeholder="Ask something about your files..." style="flex:1" onkeydown="if(event.key==='Enter') sendChat()">
<button class="btn-primary" onclick="sendChat()">Send</button>
</div>
</div>
</div>
<script>
let currentFile = null;
let vizSteps = [];
let currentStep = 0;
let playInterval = null;
// Navigation
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.getElementById('panel-' + btn.dataset.panel).classList.add('active');
});
});
// Tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
const parent = tab.parentElement.parentElement;
parent.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
parent.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
parent.querySelector('#tab-' + tab.dataset.tab).classList.add('active');
});
});
// File upload
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('dragover'); });
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('dragover'); if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]); });
fileInput.addEventListener('change', () => { if (fileInput.files.length) handleFile(fileInput.files[0]); });
function handleFile(file) {
currentFile = file;
document.getElementById('fileInfo').style.display = 'flex';
document.getElementById('fileName').textContent = file.name;
document.getElementById('fileSize').textContent = (file.size / 1024).toFixed(1) + ' KB';
const ext = '.' + file.name.split('.').pop().toLowerCase();
const iconMap = {
'.py':'&#x1F40D;','.js':'&#x1F310;','.html':'&#x1F310;','.css':'&#x1F3A8;',
'.c':'&#x2699;','.cpp':'&#x2699;','.java':'&#x2615;',
'.jpg':'&#x1F5BC;','.jpeg':'&#x1F5BC;','.png':'&#x1F5BC;','.gif':'&#x1F5BC;',
'.mp3':'&#x1F3B5;','.wav':'&#x1F3B5;',
'.mp4':'&#x1F3AC;','.mkv':'&#x1F3AC;',
'.pdf':'&#x1F4C4;','.txt':'&#x1F4DD;','.csv':'&#x1F4CA;'
};
document.getElementById('fileIcon').innerHTML = iconMap[ext] || '&#x1F4C4;';
uploadFile(file);
}
function resetUpload() {
currentFile = null;
document.getElementById('fileInfo').style.display = 'none';
document.getElementById('results').style.display = 'none';
fileInput.value = '';
}
async function uploadFile(file) {
document.getElementById('results').style.display = 'block';
document.getElementById('overviewSummary').textContent = 'Loading...';
document.getElementById('tab-overview').style.display = 'none';
document.getElementById('tab-steps').style.display = 'none';
document.getElementById('tab-preview').style.display = 'none';
const formData = new FormData();
formData.append('file', file);
try {
const resp = await fetch('/analyze-file', { method: 'POST', body: formData });
const data = await resp.json();
displayResults(data);
} catch (err) {
document.getElementById('overviewSummary').textContent = 'Error: ' + err.message;
}
}
function displayResults(data) {
document.getElementById('results').style.display = 'block';
document.querySelectorAll('#results .tab')[0].click();
document.getElementById('overviewSummary').textContent = data.summary || 'No summary available.';
document.getElementById('ovFileType').innerHTML = (data.file_type || 'unknown') + (data.dsa_type ? ' <span class="badge badge-blue">' + data.dsa_type + '</span>' : '');
document.getElementById('ovSize').textContent = data.size_kb + ' KB';
document.getElementById('ovLines').textContent = data.total_lines || '-';
document.getElementById('ovExplanation').textContent = data.explanation || 'No explanation available.';
if (data.image_data_url) {
document.getElementById('ovImageContainer').style.display = 'block';
document.getElementById('ovImage').src = data.image_data_url;
document.getElementById('ovImage').alt = data.filename || 'Uploaded image';
document.getElementById('previewContent').innerHTML = '<img src="' + data.image_data_url + '" class="image-preview" alt="' + (data.filename || 'image') + '">';
} else {
document.getElementById('ovImageContainer').style.display = 'none';
document.getElementById('previewContent').innerHTML = '<pre class="code-block">' + escapeHtml(data.content_preview || 'No preview available') + '</pre>';
}
if (data.time_complexity || data.space_complexity) {
document.getElementById('ovComplexityCard').style.display = 'block';
document.getElementById('ovTime').textContent = data.time_complexity || '-';
document.getElementById('ovSpace').textContent = data.space_complexity || '-';
} else {
document.getElementById('ovComplexityCard').style.display = 'none';
}
const stepsList = document.getElementById('stepsList');
stepsList.innerHTML = '';
if (data.step_by_step && data.step_by_step.length) {
data.step_by_step.forEach((step, i) => {
const div = document.createElement('div');
div.className = 'step-item';
div.innerHTML = '<span class="step-num">' + (i+1) + '</span>' + escapeHtml(step);
stepsList.appendChild(div);
});
} else {
stepsList.innerHTML = '<p style="color:var(--text-muted)">No step-by-step breakdown available.</p>';
}
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// DSA Templates
async function loadTemplates() {
try {
const resp = await fetch('/dsa/templates');
const data = await resp.json();
const sel = document.getElementById('dsaTemplate');
sel.innerHTML = '<option value="">-- Select a built-in template --</option>';
for (const [key, tmpl] of Object.entries(data.templates)) {
const opt = document.createElement('option');
opt.value = key;
opt.textContent = tmpl.title + ' (' + tmpl.time + ')';
sel.appendChild(opt);
}
} catch (e) { console.error('Failed to load templates', e); }
}
loadTemplates();
document.getElementById('dsaTemplate').addEventListener('change', async function() {
if (!this.value) return;
try {
const resp = await fetch('/dsa/visualize-template', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({template: this.value})
});
const data = await resp.json();
document.getElementById('dsaCode').value = data.code || '// Template loaded';
if (data.steps && data.steps.length) {
vizSteps = data.steps;
currentStep = 0;
renderVisualization();
document.getElementById('dsaResult').style.display = 'block';
}
document.getElementById('dsaSummary').innerHTML = '<strong>' + data.title + '</strong> — ' + (data.summary || data.description || '');
if (data.explanation) {
document.getElementById('dsaExplanationCard').style.display = 'block';
document.getElementById('dsaExplanation').textContent = data.explanation;
} else {
document.getElementById('dsaExplanationCard').style.display = 'none';
}
if (data.code) {
document.getElementById('dsaCodeDisplay').style.display = 'block';
document.getElementById('dsaCodeBlock').textContent = data.code;
} else {
document.getElementById('dsaCodeDisplay').style.display = 'none';
}
} catch (e) { console.error(e); }
});
async function runVisualization() {
const code = document.getElementById('dsaCode').value.trim();
const inputRaw = document.getElementById('dsaInput').value.trim();
const template = document.getElementById('dsaTemplate').value;
if (!code && !template) { alert('Select a template or paste code'); return; }
let inputData = null;
if (inputRaw) {
inputData = inputRaw.split(',').map(s => {
const n = Number(s.trim());
return isNaN(n) ? s.trim() : n;
});
}
try {
let resp;
if (template && !code) {
resp = await fetch('/dsa/visualize-template', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({template, input_data: inputData})
});
} else {
resp = await fetch('/dsa/visualize', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({code, input_data: inputData})
});
}
const data = await resp.json();
document.getElementById('dsaResult').style.display = 'block';
document.getElementById('dsaSummary').innerHTML = '<strong>' + (data.title || 'Visualization') + '</strong> — ' + (data.summary || data.description || '');
if (data.explanation) {
document.getElementById('dsaExplanationCard').style.display = 'block';
document.getElementById('dsaExplanation').textContent = data.explanation;
} else {
document.getElementById('dsaExplanationCard').style.display = 'none';
}
if (data.code) {
document.getElementById('dsaCodeDisplay').style.display = 'block';
document.getElementById('dsaCodeBlock').textContent = data.code;
} else {
document.getElementById('dsaCodeDisplay').style.display = 'none';
}
if (data.steps && data.steps.length) {
vizSteps = data.steps;
currentStep = 0;
renderVisualization();
}
} catch (e) { alert('Error: ' + e.message); }
}
async function explainCode() {
const code = document.getElementById('dsaCode').value.trim();
if (!code) { alert('Paste some code first'); return; }
try {
const resp = await fetch('/dsa/explain-code', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({code})
});
const data = await resp.json();
document.getElementById('dsaResult').style.display = 'block';
document.getElementById('dsaSummary').innerHTML = '<strong>' + data.title + '</strong> — ' + (data.summary || '');
document.getElementById('dsaExplanationCard').style.display = 'block';
document.getElementById('dsaExplanation').textContent = data.explanation || data.summary || 'Analysis complete.';
document.getElementById('dsaCodeDisplay').style.display = 'block';
document.getElementById('dsaCodeBlock').textContent = code;
vizSteps = [];
document.getElementById('vizContainer').innerHTML = '';
document.getElementById('vizDescription').textContent = '';
document.querySelector('.viz-controls').style.display = 'none';
} catch (e) { alert('Error: ' + e.message); }
}
// Visualization rendering
function renderVisualization() {
if (!vizSteps.length) return;
currentStep = Math.max(0, Math.min(currentStep, vizSteps.length - 1));
const step = vizSteps[currentStep];
document.getElementById('vizStepNum').textContent = currentStep + 1;
document.getElementById('vizTotalSteps').textContent = vizSteps.length;
document.getElementById('vizPrev').disabled = currentStep === 0;
document.getElementById('vizNext').disabled = currentStep === vizSteps.length - 1;
document.getElementById('vizDescription').textContent = step.description || '';
document.querySelector('.viz-controls').style.display = 'flex';
const container = document.getElementById('vizContainer');
if (step.array_state) {
renderArrayBars(container, step.array_state, step.highlight_nodes || []);
} else if (step.tree_state) {
renderTree(container, step.tree_state, step.highlight_nodes || []);
} else if (step.linked_list_state) {
renderLinkedList(container, step.linked_list_state, step.highlight_nodes || []);
} else {
container.innerHTML = '<div class="loading">Step ' + (currentStep + 1) + '</div>';
}
}
function renderArrayBars(container, arr, highlights) {
const maxVal = Math.max(...arr.filter(v => typeof v === 'number'));
container.innerHTML = '<div class="array-bars">' +
arr.map((v, i) => {
const h = typeof v === 'number' ? (v / maxVal * 180) : 100;
const cls = highlights.includes(i) ? 'array-bar highlight' : 'array-bar';
return '<div class="' + cls + '" style="height:' + h + 'px;flex:' + (1/arr.length*100) + '%">' + v + '</div>';
}).join('') +
'</div>';
}
function renderTree(container, tree, highlights) {
const canvas = document.createElement('canvas');
canvas.className = 'tree-canvas';
canvas.width = container.offsetWidth || 800;
canvas.height = 300;
container.innerHTML = '';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
function drawNode(node, x, y, level, maxLevel) {
if (!node) return;
const radius = 22;
const spacing = Math.max(40, canvas.width / Math.pow(2, level + 1));
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
const isHighlight = highlights.includes(node.id);
ctx.fillStyle = isHighlight ? '#d29922' : '#58a6ff';
ctx.fill();
ctx.strokeStyle = '#30363d';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#fff';
ctx.font = 'bold 13px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(node.val || ''), x, y);
const childY = y + 70;
if (node.left) {
ctx.beginPath();
ctx.moveTo(x - 10, y + radius);
ctx.lineTo(x - spacing / 2, childY - radius);
ctx.strokeStyle = '#666';
ctx.lineWidth = 1.5;
ctx.stroke();
drawNode(node.left, x - spacing / 2, childY, level + 1, maxLevel);
}
if (node.right) {
ctx.beginPath();
ctx.moveTo(x + 10, y + radius);
ctx.lineTo(x + spacing / 2, childY - radius);
ctx.strokeStyle = '#666';
ctx.lineWidth = 1.5;
ctx.stroke();
drawNode(node.right, x + spacing / 2, childY, level + 1, maxLevel);
}
}
drawNode(tree, canvas.width / 2, 40, 0, 5);
}
function renderLinkedList(container, nodes, highlights) {
const canvas = document.createElement('canvas');
canvas.className = 'll-canvas';
canvas.width = container.offsetWidth || 800;
canvas.height = 200;
container.innerHTML = '';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const nodeW = 70, nodeH = 40;
const startX = 30, y = 80;
nodes.forEach((node, i) => {
const x = startX + i * (nodeW + 50);
const isHighlight = highlights.includes(node.id);
ctx.fillStyle = isHighlight ? '#d29922' : '#58a6ff';
ctx.fillRect(x, y - nodeH / 2, nodeW, nodeH);
ctx.strokeStyle = '#30363d';
ctx.lineWidth = 2;
ctx.strokeRect(x, y - nodeH / 2, nodeW, nodeH);
ctx.fillStyle = '#fff';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(node.value || ''), x + nodeW / 2, y);
if (node.next) {
const arrowX = x + nodeW;
ctx.beginPath();
ctx.moveTo(arrowX, y);
ctx.lineTo(arrowX + 40, y);
ctx.strokeStyle = '#666';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(arrowX + 40, y - 6);
ctx.lineTo(arrowX + 50, y);
ctx.lineTo(arrowX + 40, y + 6);
ctx.fillStyle = '#666';
ctx.fill();
}
});
}
function nextStep() { if (currentStep < vizSteps.length - 1) { currentStep++; renderVisualization(); } }
function prevStep() { if (currentStep > 0) { currentStep--; renderVisualization(); } }
function playSteps() {
if (playInterval) { clearInterval(playInterval); playInterval = null; return; }
playInterval = setInterval(() => {
if (currentStep < vizSteps.length - 1) { currentStep++; renderVisualization(); }
else { clearInterval(playInterval); playInterval = null; }
}, 1000);
}
// Chat
let chatHistory = [];
async function sendChat() {
const input = document.getElementById('chatInput');
const msg = input.value.trim();
if (!msg) return;
input.value = '';
document.getElementById('chatPlaceholder').style.display = 'none';
const container = document.getElementById('chatMessages');
const userDiv = document.createElement('div');
userDiv.style.cssText = 'background:var(--accent);color:#fff;padding:10px 14px;border-radius:12px 12px 2px 12px;margin:6px 0 6px auto;max-width:80%;width:fit-content;font-size:14px;line-height:1.5;';
userDiv.textContent = msg;
container.appendChild(userDiv);
container.scrollTop = container.scrollHeight;
chatHistory.push({role:'user',content:msg});
const loadingDiv = document.createElement('div');
loadingDiv.style.cssText = 'text-align:center;padding:12px;color:var(--text-muted);font-size:13px;';
loadingDiv.textContent = 'Thinking...';
container.appendChild(loadingDiv);
try {
const resp = await fetch('/chat', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({prompt:msg})
});
const data = await resp.json();
loadingDiv.remove();
const text = data.response || 'No response';
const aiDiv = document.createElement('div');
aiDiv.style.cssText = 'background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 14px;border-radius:12px 12px 12px 2px;margin:6px auto 6px 0;max-width:80%;width:fit-content;font-size:14px;line-height:1.5;white-space:pre-wrap;';
aiDiv.textContent = text;
container.appendChild(aiDiv);
chatHistory.push({role:'assistant',content:text});
} catch(e) {
loadingDiv.remove();
const errDiv = document.createElement('div');
errDiv.style.cssText = 'color:var(--danger);padding:8px 12px;font-size:13px;text-align:center;';
errDiv.textContent = 'AI assistant unavailable (Ollama not running). Try again later.';
container.appendChild(errDiv);
}
container.scrollTop = container.scrollHeight;
}
document.addEventListener('keydown', e => {
if (e.key === 'ArrowRight') nextStep();
if (e.key === 'ArrowLeft') prevStep();
});
</script>
</body>
</html>