chatcad / static /how_it_works.html
Samarjithbiswas's picture
chat_cad live (Docker app + weights)
32c7c43 verified
Raw
History Blame Contribute Delete
15.4 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>How chat_cad works</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0e1116; color: #e6e8eb;
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
min-height: 100vh; padding: 24px;
display: flex; flex-direction: column; align-items: center; gap: 20px; }
h1 { font-size: 26px; font-weight: 700; letter-spacing: 0.2px;
background: linear-gradient(90deg, #6fb5ff, #c590e0, #ffd9a8);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text; }
.sub { font-size: 13px; opacity: 0.6; margin-top: -8px; }
#stage { width: 1100px; height: 620px; background: #14171c;
border-radius: 16px; border: 1px solid #2a2f37;
position: relative; overflow: hidden;
box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
.node { position: absolute; padding: 14px 18px; border-radius: 12px;
background: #1c2128; border: 2px solid #2a2f37;
min-width: 140px; text-align: center;
transition: all 0.4s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.25); }
.node .icn { font-size: 28px; line-height: 1; margin-bottom: 4px; }
.node .ttl { font-size: 13px; font-weight: 700; color: #e6e8eb; }
.node .sub2 { font-size: 10px; opacity: 0.55; margin-top: 2px;
font-family: ui-monospace, monospace; }
.node.active { transform: scale(1.08);
box-shadow: 0 0 0 3px var(--glow), 0 0 24px var(--glow),
0 8px 24px rgba(0,0,0,0.4);
background: #232831; border-color: var(--glow); }
.node.active .ttl { color: #fff; }
.node.user { left: 30px; top: 270px; --glow: #6fb5ff; }
.node.chat { left: 220px; top: 270px; --glow: #6fb5ff; }
.node.router { left: 410px; top: 270px; --glow: #ffb73d; }
.node.parser { left: 600px; top: 80px; --glow: #8fb98f; }
.node.llm { left: 600px; top: 200px; --glow: #c590e0; }
.node.agent { left: 600px; top: 320px; --glow: #f6c14b; }
.node.intent { left: 600px; top: 440px; --glow: #ff9b6f; }
.node.engine { left: 820px; top: 200px; --glow: #6fb5ff; }
.node.fea { left: 820px; top: 80px; --glow: #ff7777; }
.node.viewport { left: 820px; top: 320px; --glow: #6fb5ff; }
.node.feature { left: 820px; top: 440px; --glow: #95c08a; }
svg.wires { position: absolute; inset: 0; pointer-events: none; }
.wire { stroke: #2a3540; stroke-width: 2; fill: none;
stroke-dasharray: 6 6; transition: stroke 0.4s; }
.wire.active { stroke: var(--wirecolor, #6fb5ff); stroke-width: 3;
filter: drop-shadow(0 0 6px var(--wirecolor, #6fb5ff)); }
.packet { fill: #ffd9a8; r: 5;
filter: drop-shadow(0 0 8px #ffd9a8); }
#ribbon { position: absolute; bottom: 0; left: 0; right: 0; height: 56px;
background: #181c22; border-top: 1px solid #2a2f37;
display: flex; align-items: center; padding: 0 24px;
gap: 14px; font-family: ui-monospace, monospace;
font-size: 13px; }
#typing { color: #6fb5ff; }
#typing .cursor { display: inline-block; width: 8px; background: #6fb5ff;
height: 14px; vertical-align: middle; animation: blink 1s steps(2) infinite; }
@keyframes blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
#status { color: #9aa3b2; font-style: italic; flex: 1; text-align: right;
font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; }
#legend { display: flex; gap: 18px; flex-wrap: wrap;
font-size: 11px; opacity: 0.75; max-width: 1100px;
font-family: ui-monospace, monospace; }
#legend .pill { padding: 4px 10px; border-radius: 999px;
background: #1c2128; border: 1px solid #2a2f37; }
#ctl { display: flex; gap: 10px; }
button { background: #2461c2; color: #fff; border: none;
padding: 8px 18px; border-radius: 6px; cursor: pointer;
font-size: 13px; font-weight: 600; }
button:hover { background: #1c4e9e; }
button.ghost { background: transparent; color: #e6e8eb;
border: 1px solid #3a4050; }
</style>
</head>
<body>
<h1>How chat_cad works</h1>
<div class="sub">Plain English → real parametric CAD, FEA, design report — in seconds.</div>
<div id="ctl">
<button id="play">▶ Play</button>
<button id="pause" class="ghost">⏸ Pause</button>
<button id="restart" class="ghost">↻ Restart</button>
<select id="scenario">
<option value="bolt">Scenario: build a bolt</option>
<option value="bracket">Scenario: AI-recognise an L-bracket</option>
<option value="stress">Scenario: stress analysis with color plot</option>
<option value="acoustic">Scenario: inverse acoustic design</option>
</select>
</div>
<div id="stage">
<svg class="wires" viewBox="0 0 1100 620" preserveAspectRatio="none">
<!-- main routing wires (drawn behind nodes) -->
<path class="wire" id="w_user_chat" d="M 175 297 L 220 297"/>
<path class="wire" id="w_chat_router" d="M 365 297 L 410 297"/>
<path class="wire" id="w_router_parser" d="M 555 297 Q 580 297 580 130 L 600 110"/>
<path class="wire" id="w_router_llm" d="M 555 297 Q 580 297 580 230 L 600 230"/>
<path class="wire" id="w_router_agent" d="M 555 297 Q 580 297 580 350 L 600 350"/>
<path class="wire" id="w_router_intent" d="M 555 297 Q 580 297 580 470 L 600 470"/>
<path class="wire" id="w_parser_engine" d="M 745 110 Q 790 110 790 230 L 820 230"/>
<path class="wire" id="w_llm_engine" d="M 745 230 L 820 230"/>
<path class="wire" id="w_agent_engine" d="M 745 350 Q 790 350 790 230 L 820 230"/>
<path class="wire" id="w_intent_engine" d="M 745 470 Q 790 470 790 230 L 820 230"/>
<path class="wire" id="w_engine_view" d="M 890 268 L 890 320"/>
<path class="wire" id="w_engine_fea" d="M 890 195 L 890 140"/>
<path class="wire" id="w_engine_feat" d="M 890 268 Q 890 360 890 440"/>
<circle id="packet" class="packet" r="5" cx="0" cy="0" opacity="0"/>
</svg>
<div class="node user"><div class="icn">🧑‍💼</div>
<div class="ttl">You</div>
<div class="sub2">prompt</div></div>
<div class="node chat"><div class="icn">💬</div>
<div class="ttl">Chat box</div>
<div class="sub2">browser</div></div>
<div class="node router"><div class="icn">🔀</div>
<div class="ttl">Smart router</div>
<div class="sub2">classifier</div></div>
<div class="node parser"><div class="icn">⚙️</div>
<div class="ttl">Parser</div>
<div class="sub2">regex grammar</div></div>
<div class="node llm"><div class="icn">🧠</div>
<div class="ttl">LLM</div>
<div class="sub2">Claude / Ollama / Gemini</div></div>
<div class="node agent"><div class="icn">🤖</div>
<div class="ttl">Design agent</div>
<div class="sub2">multi-agent loop</div></div>
<div class="node intent"><div class="icn">💡</div>
<div class="ttl">Intent fallback</div>
<div class="sub2">47 keywords</div></div>
<div class="node fea"><div class="icn">🔧</div>
<div class="ttl">FEA</div>
<div class="sub2">gmsh + skfem</div></div>
<div class="node engine"><div class="icn">🏗️</div>
<div class="ttl">CadQuery engine</div>
<div class="sub2">B-rep solid model</div></div>
<div class="node viewport"><div class="icn">🖼️</div>
<div class="ttl">3D viewport</div>
<div class="sub2">Three.js</div></div>
<div class="node feature"><div class="icn">🌲</div>
<div class="ttl">Feature tree</div>
<div class="sub2">edit + rebuild</div></div>
<div id="ribbon">
<span id="typing"></span>
<span id="status">ready</span>
</div>
</div>
<div id="legend">
<span class="pill" style="color:#8fb98f">parser</span>
<span class="pill" style="color:#c590e0">LLM</span>
<span class="pill" style="color:#f6c14b">agent</span>
<span class="pill" style="color:#ff9b6f">intent fallback</span>
<span class="pill" style="color:#6fb5ff">3D engine</span>
<span class="pill" style="color:#ff7777">stress / FEA</span>
<span class="pill" style="color:#95c08a">parametric edit</span>
</div>
<script>
// ---- timeline definitions ----
const SCENARIOS = {
bolt: [
{ ms: 0, type: 'type', text: 'bolt b1 M8 30', status: "you type a parser command" },
{ ms: 1100, type: 'wire', id: 'w_user_chat', color: '#6fb5ff', node: 'chat' },
{ ms: 1500, type: 'wire', id: 'w_chat_router', color: '#6fb5ff', node: 'router' },
{ ms: 1900, type: 'status', text: 'router sees "bolt" — known parser keyword → bypass LLM' },
{ ms: 2200, type: 'wire', id: 'w_router_parser', color: '#8fb98f', node: 'parser' },
{ ms: 2900, type: 'wire', id: 'w_parser_engine', color: '#8fb98f', node: 'engine' },
{ ms: 3200, type: 'status', text: 'CadQuery builds M8×30 bolt with real helical thread' },
{ ms: 3800, type: 'wire', id: 'w_engine_view', color: '#6fb5ff', node: 'viewport' },
{ ms: 4200, type: 'wire', id: 'w_engine_feat', color: '#95c08a', node: 'feature' },
{ ms: 4500, type: 'status', text: '✓ bbox 13×11×35 mm rendered. Edit any param in Feature Tree.' },
],
bracket: [
{ ms: 0, type: 'type', text: 'Draw me a L bracket', status: "you type plain English" },
{ ms: 1500, type: 'wire', id: 'w_user_chat', color: '#6fb5ff', node: 'chat' },
{ ms: 1900, type: 'wire', id: 'w_chat_router', color: '#6fb5ff', node: 'router' },
{ ms: 2300, type: 'status', text: 'not a parser command — but contains keyword "bracket"' },
{ ms: 2600, type: 'wire', id: 'w_router_intent', color: '#ff9b6f', node: 'intent' },
{ ms: 3000, type: 'status', text: 'intent fallback emits: l_bracket b1 50 40 4 30 5 2' },
{ ms: 3400, type: 'wire', id: 'w_intent_engine', color: '#ff9b6f', node: 'engine' },
{ ms: 4000, type: 'wire', id: 'w_engine_view', color: '#6fb5ff', node: 'viewport' },
{ ms: 4300, type: 'wire', id: 'w_engine_feat', color: '#95c08a', node: 'feature' },
{ ms: 4600, type: 'status', text: '✓ L-bracket created without ever calling the LLM.' },
],
stress: [
{ ms: 0, type: 'type', text: 'stress bar1 load=2000 axis=X', status: "request 3D stress analysis" },
{ ms: 1500, type: 'wire', id: 'w_user_chat', color: '#6fb5ff', node: 'chat' },
{ ms: 1900, type: 'wire', id: 'w_chat_router', color: '#6fb5ff', node: 'router' },
{ ms: 2200, type: 'wire', id: 'w_router_parser', color: '#8fb98f', node: 'parser' },
{ ms: 2700, type: 'wire', id: 'w_parser_engine', color: '#8fb98f', node: 'engine' },
{ ms: 3000, type: 'status', text: 'engine exports STL → FEA worker mesh + solve' },
{ ms: 3400, type: 'wire', id: 'w_engine_fea', color: '#ff7777', node: 'fea' },
{ ms: 4200, type: 'status', text: 'gmsh meshes • skfem solves K·u = f • von Mises per node' },
{ ms: 5000, type: 'wire', id: 'w_engine_view', color: '#6fb5ff', node: 'viewport' },
{ ms: 5400, type: 'status', text: '✓ COMSOL-style colored mesh in viewport. SF = 1.81. OK.' },
],
acoustic: [
{ ms: 0, type: 'type', text: 'acoustic block 12000 name silencer1', status: "inverse acoustic design" },
{ ms: 1700, type: 'wire', id: 'w_user_chat', color: '#6fb5ff', node: 'chat' },
{ ms: 2000, type: 'wire', id: 'w_chat_router', color: '#6fb5ff', node: 'router' },
{ ms: 2300, type: 'status', text: 'parser sees "acoustic" → inverse-design loop' },
{ ms: 2500, type: 'wire', id: 'w_router_parser', color: '#8fb98f', node: 'parser' },
{ ms: 2900, type: 'status', text: 'coordinate-descent across 5 unit-cell families…' },
{ ms: 3800, type: 'wire', id: 'w_parser_engine', color: '#8fb98f', node: 'engine' },
{ ms: 4200, type: 'status', text: 'best: square_hole lattice • 95% match • 1 ms' },
{ ms: 4700, type: 'wire', id: 'w_engine_view', color: '#6fb5ff', node: 'viewport' },
{ ms: 5100, type: 'status', text: '✓ 4×4 lattice rendered. Wire PhononIQ surrogate for production accuracy.' },
],
};
let RAF = null; let timer = null;
function resetAll() {
document.querySelectorAll('.node').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.wire').forEach(w => {
w.classList.remove('active');
w.style.removeProperty('--wirecolor');
});
document.getElementById('packet').setAttribute('opacity', '0');
document.getElementById('typing').textContent = '';
document.getElementById('status').textContent = 'ready';
}
function animatePacket(wirePath) {
const path = document.getElementById(wirePath);
if (!path) return;
const len = path.getTotalLength();
const packet = document.getElementById('packet');
packet.setAttribute('opacity', '1');
const t0 = performance.now();
function step(t) {
const u = Math.min(1, (t - t0) / 700);
const pt = path.getPointAtLength(len * u);
packet.setAttribute('cx', pt.x);
packet.setAttribute('cy', pt.y);
if (u < 1) RAF = requestAnimationFrame(step);
else { setTimeout(() => packet.setAttribute('opacity', '0'), 200); }
}
RAF = requestAnimationFrame(step);
}
function typeText(target, full, ms_total = 1000) {
const start = performance.now();
function step(t) {
const u = Math.min(1, (t - start) / ms_total);
const n = Math.floor(full.length * u);
target.innerHTML = full.slice(0, n) + '<span class="cursor"></span>';
if (u < 1) requestAnimationFrame(step);
else target.innerHTML = full + '<span class="cursor"></span>';
}
requestAnimationFrame(step);
}
function runScenario(key) {
resetAll();
const steps = SCENARIOS[key];
steps.forEach(step => {
setTimeout(() => {
if (step.type === 'type') {
typeText(document.getElementById('typing'), step.text, 1000);
if (step.status) document.getElementById('status').textContent = step.status;
} else if (step.type === 'wire') {
const w = document.getElementById(step.id);
if (w) { w.classList.add('active'); w.style.setProperty('--wirecolor', step.color); }
if (step.node) document.querySelector('.node.' + step.node)?.classList.add('active');
animatePacket(step.id);
} else if (step.type === 'status') {
document.getElementById('status').textContent = step.text;
}
}, step.ms);
});
}
let _loop = null;
function loopScenario() {
const sel = document.getElementById('scenario');
runScenario(sel.value);
_loop = setTimeout(loopScenario, 7000);
}
document.getElementById('play').addEventListener('click', () => {
clearTimeout(_loop);
loopScenario();
});
document.getElementById('pause').addEventListener('click', () => {
clearTimeout(_loop); cancelAnimationFrame(RAF);
});
document.getElementById('restart').addEventListener('click', () => {
clearTimeout(_loop); resetAll();
setTimeout(loopScenario, 200);
});
document.getElementById('scenario').addEventListener('change', () => {
clearTimeout(_loop); resetAll();
setTimeout(loopScenario, 200);
});
// auto-start
window.addEventListener('load', () => setTimeout(loopScenario, 600));
</script>
</body>
</html>