Spaces:
Running
Running
| <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> | |