code-bloat-lite / index.html
Molbap's picture
Molbap HF Staff
Update index.html
96388b4 verified
raw
history blame
17.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python Code Structure Visualizer</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
.fira-code {
font-family: 'Fira Code', monospace;
}
/* Custom styles for D3 graph */
.graph-container {
width: 100%;
height: 100%;
min-height: 500px;
cursor: grab;
}
.graph-container:active {
cursor: grabbing;
}
.node circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node text {
font-size: 10px;
font-family: 'Fira Code', monospace;
paint-order: stroke;
stroke: #111827; /* Match dark background */
stroke-width: 3px;
stroke-linecap: butt;
stroke-linejoin: miter;
pointer-events: none;
}
.link {
stroke-opacity: 0.6;
}
.link.inheritance {
stroke-dasharray: 5, 5;
stroke: #60a5fa; /* blue-400 */
}
.link.method {
stroke: #4b5563; /* gray-600 */
}
.node.selected > circle {
stroke: #facc15; /* yellow-400 */
stroke-width: 3px;
}
/* Tab styling */
.tab.active {
background-color: #3b82f6; /* blue-500 */
color: white;
}
</style>
</head>
<body class="bg-gray-900 text-gray-200 flex flex-col h-screen">
<header class="bg-gray-800/50 backdrop-blur-sm border-b border-gray-700 p-4 shadow-lg">
<h1 class="text-2xl font-bold text-center text-white">Bloatedness Visualizer</h1>
<p class="text-center text-gray-400 mt-1">Paste your model in the 'Main' tab and add dependencies in other tabs.</p>
</header>
<main class="flex-grow flex flex-col md:flex-row gap-4 p-4 overflow-hidden">
<!-- Left Panel: Code Input & Controls -->
<div class="md:w-1/3 flex flex-col h-full">
<div class="flex-grow flex flex-col bg-gray-800 rounded-lg shadow-2xl border border-gray-700">
<div class="p-4 border-b border-gray-700 flex justify-between items-center">
<h2 class="text-lg font-semibold">Code Input</h2>
<button id="visualize-btn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md transition-colors duration-300">
Visualize
</button>
</div>
<div class="border-b border-gray-700 bg-gray-900/50 px-2 pt-2 flex items-center gap-2">
<div id="tab-bar" class="flex gap-1">
<!-- Tabs will be dynamically inserted here -->
</div>
<button id="add-tab-btn" class="ml-auto bg-gray-600 hover:bg-gray-500 text-white font-bold h-8 w-8 rounded-full transition-colors duration-200">+</button>
</div>
<div id="code-inputs-container" class="flex-grow relative">
<!-- Textareas will be dynamically inserted here -->
</div>
</div>
</div>
<!-- Right Panel: Visualization & Details -->
<div class="md:w-2/3 flex flex-col gap-4 h-full">
<div class="flex-grow bg-gray-800 rounded-lg shadow-2xl border border-gray-700 relative overflow-hidden">
<div id="graph-container" class="w-full h-full"></div>
<div id="loading-spinner" class="absolute inset-0 bg-gray-800/50 flex items-center justify-center hidden z-10">
<svg class="animate-spin h-10 w-10 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</div>
<div id="details-panel" class="h-40 bg-gray-800 rounded-lg shadow-2xl border border-gray-700 p-4 flex flex-col">
<h3 class="text-lg font-semibold border-b border-gray-700 pb-2 mb-2">Details</h3>
<div id="details-content" class="text-gray-400 fira-code overflow-y-auto">
<p>Click on a node in the graph to see its details. Scroll to zoom, drag to pan.</p>
</div>
</div>
</div>
</main>
<script>
// --- DOM Element References ---
const visualizeBtn = document.getElementById('visualize-btn');
const graphContainer = document.getElementById('graph-container');
const detailsContent = document.getElementById('details-content');
const loadingSpinner = document.getElementById('loading-spinner');
const tabBar = document.getElementById('tab-bar');
const codeInputsContainer = document.getElementById('code-inputs-container');
const addTabBtn = document.getElementById('add-tab-btn');
// --- Example Code ---
fetch("main_code.py")
.then(res => res.text())
.then(code => {
const exampleCodeMain = code;
// do something with it
});
fetch("dependencies.py")
.then(res_deps => res_deps.text())
.then(code_deps => {
const exampleCodeDeps = code_deps;
// do something with it
});
// --- Tab Management ---
let tabCounter = 0;
function addTab(name, content = '', isActive = false) {
tabCounter++;
const tabId = `tab-${tabCounter}`;
const textareaId = `textarea-${tabCounter}`;
// Create Tab Button
const tabButton = document.createElement('button');
tabButton.id = tabId;
tabButton.className = 'tab px-4 py-2 text-sm font-medium rounded-t-md transition-colors duration-200';
tabButton.textContent = name;
tabButton.dataset.textareaId = textareaId;
tabBar.appendChild(tabButton);
// Create Textarea
const textarea = document.createElement('textarea');
textarea.id = textareaId;
textarea.className = 'fira-code w-full h-full p-4 bg-gray-900 text-gray-300 resize-none focus:outline-none absolute top-0 left-0';
textarea.placeholder = `Paste dependency code here...`;
textarea.value = content;
codeInputsContainer.appendChild(textarea);
tabButton.addEventListener('click', () => switchTab(tabId));
if (isActive) {
switchTab(tabId);
} else {
textarea.classList.add('hidden');
}
}
function switchTab(tabId) {
// Update tab buttons
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.toggle('active', tab.id === tabId);
});
// Update textareas
document.querySelectorAll('#code-inputs-container textarea').forEach(area => {
area.classList.toggle('hidden', area.id !== document.getElementById(tabId).dataset.textareaId);
});
}
addTabBtn.addEventListener('click', () => addTab(`Dep ${tabCounter}`));
// --- Core Logic: Parser ---
function parsePythonCode(code) {
const nodes = [];
const links = [];
const nodeRegistry = new Set();
let currentClassInfo = null;
const lines = code.split('\n');
lines.forEach(line => {
const indentation = line.match(/^\s*/)[0].length;
if (line.trim().length > 0 && indentation === 0) {
currentClassInfo = null;
}
const classMatch = /^\s*class\s+([\w\d_]+)\s*(?:\(([\w\d_,\s]+)\))?:/.exec(line);
if (classMatch) {
const className = classMatch[1];
const parents = classMatch[2] ? classMatch[2].split(',').map(p => p.trim()) : [];
if (!nodeRegistry.has(className)) {
nodes.push({ id: className, type: 'class', parents: parents });
nodeRegistry.add(className);
} else {
// If class was already created as an external placeholder, update it
const existingNode = nodes.find(n => n.id === className);
if (existingNode && existingNode.isExternal) {
existingNode.isExternal = false;
existingNode.parents = parents;
}
}
currentClassInfo = { name: className, indentation: indentation };
parents.forEach(parent => {
if (!nodeRegistry.has(parent)) {
nodes.push({ id: parent, type: 'class', isExternal: true, parents: [] });
nodeRegistry.add(parent);
}
links.push({ source: className, target: parent, type: 'inheritance' });
});
}
const methodMatch = /^\s+def\s+([\w\d_]+)\s*\(([^)]*)\)/.exec(line);
if (currentClassInfo && methodMatch && indentation > currentClassInfo.indentation) {
const methodName = methodMatch[1];
const signature = methodMatch[2];
const methodId = `${currentClassInfo.name}.${methodName}`;
if (!nodeRegistry.has(methodId)) {
nodes.push({ id: methodId, name: methodName, type: 'method', parentClass: currentClassInfo.name, signature: `(${signature})` });
nodeRegistry.add(methodId);
links.push({ source: currentClassInfo.name, target: methodId, type: 'method' });
}
}
});
return { nodes, links };
}
// --- Core Logic: D3 Visualization ---
let simulation;
function renderGraph(data) {
graphContainer.innerHTML = '';
const width = graphContainer.clientWidth;
const height = graphContainer.clientHeight;
const svg = d3.select(graphContainer).append("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);
const container = svg.append("g");
// Add zoom capabilities
const zoom = d3.zoom()
.scaleExtent([0.1, 4])
.on("zoom", (event) => {
container.attr("transform", event.transform);
});
svg.call(zoom);
if (simulation) {
simulation.stop();
}
simulation = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links).id(d => d.id).distance(d => d.type === 'inheritance' ? 150 : 60).strength(0.5))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(0, 0))
.force("x", d3.forceX())
.force("y", d3.forceY());
const link = container.append("g")
.selectAll("line")
.data(data.links)
.join("line")
.attr("class", d => `link ${d.type}`);
const node = container.append("g")
.selectAll("g")
.data(data.nodes)
.join("g")
.attr("class", "node")
.call(drag(simulation));
node.append("circle")
.attr("r", d => d.type === 'class' ? 15 : 8)
.attr("fill", d => {
if (d.type !== 'class') return '#9ca3af';
return d.isExternal ? '#be185d' : '#2563eb';
});
node.append("text")
.text(d => d.type === 'class' ? d.id : d.name)
.attr("x", d => d.type === 'class' ? 18 : 12)
.attr("y", 3)
.attr("fill", "#e5e7eb");
node.on("click", (event, d) => {
event.stopPropagation(); // Prevent zoom from firing on node click
updateDetailsPanel(d);
node.classed("selected", n => n.id === d.id);
});
simulation.on("tick", () => {
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
node.attr("transform", d => `translate(${d.x},${d.y})`);
});
}
// --- Interactivity ---
function drag(simulation) {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
// --- UI Updates ---
function updateDetailsPanel(d) {
let content = '';
if (d.type === 'class') {
content = `
<p><span class="text-gray-100 font-semibold">Name:</span> ${d.id}</p>
<p><span class="text-gray-100 font-semibold">Type:</span> ${d.isExternal ? 'External Class' : 'Class'}</p>
<p><span class="text-gray-100 font-semibold">Inherits from:</span> ${d.parents && d.parents.length > 0 ? d.parents.join(', ') : 'None'}</p>
${d.isExternal ? '<p class="text-pink-400 mt-1">Note: This class was not defined in the provided code.</p>' : ''}
`;
} else if (d.type === 'method') {
content = `
<p><span class="text-gray-100 font-semibold">Name:</span> ${d.name}</p>
<p><span class="text-gray-100 font-semibold">Type:</span> Method</p>
<p><span class="text-gray-100 font-semibold">Belongs to:</span> ${d.parentClass}</p>
<p><span class="text-gray-100 font-semibold">Signature:</span> ${d.name}${d.signature}</p>
`;
}
detailsContent.innerHTML = content;
}
function handleVisualize() {
loadingSpinner.classList.remove('hidden');
setTimeout(() => {
try {
let allCode = '';
document.querySelectorAll('#code-inputs-container textarea').forEach(area => {
allCode += area.value + '\n';
});
if (!allCode.trim()) {
graphContainer.innerHTML = '<p class="p-4 text-center text-gray-400">Please paste some code to visualize.</p>';
return;
}
const graphData = parsePythonCode(allCode);
renderGraph(graphData);
} catch (error) {
console.error("Failed to visualize code:", error);
graphContainer.innerHTML = `<p class="p-4 text-center text-red-400">An error occurred during parsing. Check the console for details.</p>`;
} finally {
loadingSpinner.classList.add('hidden');
}
}, 50);
}
// --- Event Listeners ---
visualizeBtn.addEventListener('click', handleVisualize);
// --- Initial Load ---
window.addEventListener('load', () => {
addTab('Main', exampleCodeMain, true);
addTab('Deps', exampleCodeDeps);
handleVisualize();
});
window.addEventListener('resize', () => {
let allCode = '';
document.querySelectorAll('#code-inputs-container textarea').forEach(area => {
allCode += area.value + '\n';
});
if (allCode.trim()) {
handleVisualize();
}
});
</script>
</body>
</html>