Download index.html from eaglelandsonce/ADK_Multi-Agent_Blog_Simulation: direct link, hf CLI and curl.
- Browser
- Download file 15.7 kB
-
https://huggingface.co/spaces/eaglelandsonce/ADK_Multi-Agent_Blog_Simulation/resolve/main/index.html
- Command line
-
hf download hf://spaces/eaglelandsonce/ADK_Multi-Agent_Blog_Simulation/index.html
-
curl -L -o index.html https://huggingface.co/spaces/eaglelandsonce/ADK_Multi-Agent_Blog_Simulation/resolve/main/index.html
15.7 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>ADK Multi-Agent Blog Simulation</title> | |
| <style> | |
| :root{ | |
| --bg:#f6f7fb; | |
| --panel:#ffffff; | |
| --card:#ffffff; | |
| --text:#1f2937; | |
| --muted:#6b7280; | |
| --border:#d1d5db; | |
| --accent:#2563eb; | |
| --accent-bg:#dbeafe; | |
| --ok:#16a34a; | |
| --retry:#d97706; | |
| } | |
| *{box-sizing:border-box} | |
| body{ | |
| margin:0; | |
| font-family:Arial,Helvetica,sans-serif; | |
| background:var(--bg); | |
| color:var(--text); | |
| } | |
| .app{ | |
| max-width:1200px; | |
| margin:0 auto; | |
| padding:24px; | |
| } | |
| h1{margin:0 0 8px;font-size:28px} | |
| .subtitle{color:var(--muted);margin-bottom:18px} | |
| .controls{ | |
| display:flex; | |
| flex-wrap:wrap; | |
| gap:10px; | |
| align-items:center; | |
| margin-bottom:16px; | |
| } | |
| button,select{ | |
| font:inherit; | |
| padding:10px 14px; | |
| border-radius:8px; | |
| border:1px solid var(--border); | |
| background:#fff; | |
| color:var(--text); | |
| } | |
| button{cursor:pointer} | |
| button.primary{ | |
| background:var(--accent); | |
| color:#fff; | |
| border-color:var(--accent); | |
| } | |
| button:disabled{opacity:.5;cursor:not-allowed} | |
| .spacer{flex:1} | |
| .grid{ | |
| display:grid; | |
| grid-template-columns:1fr 50px 1fr 50px 1fr; | |
| gap:12px; | |
| align-items:stretch; | |
| } | |
| .arrow{ | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| font-size:28px; | |
| color:var(--muted); | |
| } | |
| .card,.panel{ | |
| background:var(--card); | |
| border:2px solid var(--border); | |
| border-radius:14px; | |
| padding:16px; | |
| transition:.2s ease; | |
| } | |
| .label{ | |
| font-size:12px; | |
| text-transform:uppercase; | |
| letter-spacing:.08em; | |
| color:var(--muted); | |
| font-weight:700; | |
| } | |
| .title{ | |
| margin-top:4px; | |
| font-size:18px; | |
| font-weight:700; | |
| } | |
| .node{ | |
| border:2px solid var(--border); | |
| border-radius:10px; | |
| padding:10px; | |
| margin-top:10px; | |
| background:#fff; | |
| transition:.2s ease; | |
| } | |
| .down{ | |
| text-align:center; | |
| color:var(--muted); | |
| padding:6px 0 0; | |
| } | |
| .loop-note{ | |
| border:2px dashed var(--border); | |
| border-radius:10px; | |
| padding:9px; | |
| margin-top:10px; | |
| text-align:center; | |
| font-size:12px; | |
| color:var(--muted); | |
| transition:.2s ease; | |
| } | |
| .status{ | |
| border:1px solid var(--border); | |
| border-radius:9px; | |
| padding:9px; | |
| margin-top:12px; | |
| font-size:14px; | |
| } | |
| .active{ | |
| border-color:var(--accent)!important; | |
| background:var(--accent-bg)!important; | |
| transform:translateY(-2px); | |
| } | |
| .ok{border-color:var(--ok)!important} | |
| .retry{border-color:var(--retry)!important} | |
| .pulse{animation:pulse .7s ease-in-out infinite alternate} | |
| @keyframes pulse{from{opacity:.68}to{opacity:1}} | |
| .panel{margin-top:14px;border-width:1px} | |
| .panel-head{ | |
| display:flex; | |
| gap:12px; | |
| justify-content:space-between; | |
| align-items:center; | |
| flex-wrap:wrap; | |
| } | |
| .badge{ | |
| border:1px solid var(--border); | |
| border-radius:999px; | |
| padding:6px 10px; | |
| font-size:13px; | |
| } | |
| .queue{ | |
| display:flex; | |
| flex-wrap:wrap; | |
| gap:8px; | |
| margin-top:12px; | |
| } | |
| .topic{ | |
| border:1px solid var(--border); | |
| border-radius:999px; | |
| padding:6px 10px; | |
| font-size:13px; | |
| background:#fff; | |
| } | |
| .two-col{ | |
| display:grid; | |
| grid-template-columns:1fr 1fr; | |
| gap:14px; | |
| margin-top:14px; | |
| } | |
| .state-row{ | |
| display:flex; | |
| justify-content:space-between; | |
| gap:16px; | |
| border-bottom:1px solid var(--border); | |
| padding:8px 0; | |
| font-size:14px; | |
| } | |
| .state-row:last-child{border-bottom:0} | |
| .mono{font-family:Consolas,monospace} | |
| .muted{color:var(--muted)} | |
| .log{ | |
| max-height:230px; | |
| overflow:auto; | |
| margin-top:10px; | |
| font-size:14px; | |
| line-height:1.5; | |
| } | |
| .final{margin-top:14px} | |
| @media(max-width:850px){ | |
| .grid{grid-template-columns:1fr} | |
| .arrow{transform:rotate(90deg)} | |
| .two-col{grid-template-columns:1fr} | |
| } | |
| @media(prefers-reduced-motion:reduce){ | |
| .card,.node,.loop-note{transition:none} | |
| .pulse{animation:none} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app" id="blogSim"> | |
| <h1>ADK Multi-Agent Blog Simulation</h1> | |
| <div class="subtitle">Watch topics flow through the Blogger root agent, planner validation loop, writer validation loop, and final response.</div> | |
| <div class="controls"> | |
| <button id="startBtn" class="primary" type="button">Start Simulation</button> | |
| <button id="pauseBtn" type="button" disabled>Pause</button> | |
| <button id="resetBtn" type="button">Reset</button> | |
| <div class="spacer"></div> | |
| <label>Speed | |
| <select id="speed"> | |
| <option value="1400">Slow</option> | |
| <option value="850" selected>Normal</option> | |
| <option value="420">Fast</option> | |
| </select> | |
| </label> | |
| </div> | |
| <div class="grid"> | |
| <section id="rootCard" class="card"> | |
| <div class="label">Root Agent</div> | |
| <div class="title">Blogger</div> | |
| <p class="muted">Instruction-enforced sequence:</p> | |
| <div class="mono">1. Planner → 2. Writer → 3. Final</div> | |
| <div id="rootStatus" class="status">Waiting for topic</div> | |
| </section> | |
| <div class="arrow">→</div> | |
| <section id="plannerCard" class="card"> | |
| <div class="label">LoopAgent · Max 3</div> | |
| <div class="title">RobustBlogPlanner</div> | |
| <div id="plannerAgent" class="node">BlogPlanner</div> | |
| <div class="down">↓ blog_outline</div> | |
| <div id="outlineValidator" class="node">OutlineValidationChecker</div> | |
| <div id="plannerLoop" class="loop-note">retry ↻ until OK or 3 iterations</div> | |
| </section> | |
| <div class="arrow">→</div> | |
| <section id="writerCard" class="card"> | |
| <div class="label">LoopAgent · Max 3</div> | |
| <div class="title">RobustBlogWriter</div> | |
| <div id="writerAgent" class="node">BlogWriter</div> | |
| <div class="down">↓ blog_post</div> | |
| <div id="postValidator" class="node">BlogPostValidationChecker</div> | |
| <div id="writerLoop" class="loop-note">retry ↻ until OK or 3 iterations</div> | |
| </section> | |
| </div> | |
| <section class="panel"> | |
| <div class="panel-head"> | |
| <div> | |
| <div class="label">Incoming Topic Queue</div> | |
| <div id="currentTopic" class="title">No topic active</div> | |
| </div> | |
| <div id="iterationBadge" class="badge">Idle</div> | |
| </div> | |
| <div id="topicQueue" class="queue"></div> | |
| </section> | |
| <div class="two-col"> | |
| <section class="panel" style="margin-top:0"> | |
| <div class="title">State / Output Keys</div> | |
| <div class="state-row"><span class="mono">blog_outline</span><span id="outlineState" class="muted">empty</span></div> | |
| <div class="state-row"><span class="mono">validation_result</span><span id="validationState" class="muted">empty</span></div> | |
| <div class="state-row"><span class="mono">blog_post</span><span id="postState" class="muted">empty</span></div> | |
| </section> | |
| <section class="panel" style="margin-top:0"> | |
| <div class="title">Execution Log</div> | |
| <div id="log" class="log"><div class="muted">Simulation ready.</div></div> | |
| </section> | |
| </div> | |
| <section id="finalCard" class="panel final"> | |
| <div class="label">Final Response</div> | |
| <div id="finalOutput" class="muted" style="margin-top:8px">Validated blog + 3 alternate titles + 2 tweet-length hooks will appear here.</div> | |
| </section> | |
| </div> | |
| <script> | |
| (() => { | |
| const root = document.getElementById('blogSim'); | |
| const $ = id => document.getElementById(id); | |
| const topics = [ | |
| 'ADK Agent Patterns', | |
| 'Context Engineering', | |
| 'Vertex AI Memory', | |
| 'Agentic Coding Workflows', | |
| 'Responsible AI Guardrails' | |
| ]; | |
| let running = false; | |
| let paused = false; | |
| let stopped = false; | |
| let topicIndex = 0; | |
| const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
| const delay = () => Number($('speed').value) || 850; | |
| async function waitStep(mult = 1) { | |
| let remaining = delay() * mult; | |
| while (remaining > 0 && !stopped) { | |
| if (!paused) remaining -= 80; | |
| await sleep(80); | |
| } | |
| } | |
| function log(msg) { | |
| const d = document.createElement('div'); | |
| d.textContent = msg; | |
| $('log').appendChild(d); | |
| $('log').scrollTop = $('log').scrollHeight; | |
| } | |
| function clearActive() { | |
| root.querySelectorAll('.active,.pulse,.retry,.ok').forEach(el => { | |
| el.classList.remove('active','pulse','retry','ok'); | |
| }); | |
| } | |
| function activate(id, status) { | |
| clearActive(); | |
| const el = $(id); | |
| if (el) el.classList.add('active','pulse'); | |
| if (status) $('iterationBadge').textContent = status; | |
| } | |
| function renderQueue() { | |
| $('topicQueue').innerHTML = ''; | |
| topics.forEach((t, i) => { | |
| const span = document.createElement('span'); | |
| span.className = 'topic'; | |
| span.textContent = (i < topicIndex ? '✓ ' : '') + t; | |
| if (i === topicIndex && running) span.classList.add('active'); | |
| $('topicQueue').appendChild(span); | |
| }); | |
| } | |
| async function planner(topic) { | |
| const iterations = topicIndex % 2 === 0 ? 2 : 1; | |
| for (let i = 1; i <= iterations; i++) { | |
| activate('plannerAgent', `Planner iteration ${i}/3`); | |
| $('rootStatus').textContent = 'Calling planner_tool'; | |
| log(`Planner: generating outline for “${topic}”`); | |
| await waitStep(); | |
| $('outlineState').textContent = `outline v${i}`; | |
| activate('outlineValidator', 'Validating outline'); | |
| log('OutlineValidationChecker: checking title, intro, 4–6 sections, conclusion'); | |
| await waitStep(); | |
| if (i < iterations) { | |
| $('validationState').textContent = 'retry'; | |
| $('outlineValidator').classList.add('retry'); | |
| $('plannerLoop').classList.add('active'); | |
| log('Validation result: retry → LoopAgent starts next iteration'); | |
| await waitStep(.75); | |
| } else { | |
| $('validationState').textContent = 'ok'; | |
| $('outlineValidator').classList.add('ok'); | |
| log('Validation result: ok → planner returns control to Blogger'); | |
| await waitStep(.75); | |
| } | |
| } | |
| } | |
| async function writer(topic) { | |
| const iterations = topicIndex % 3 === 0 ? 2 : 1; | |
| for (let i = 1; i <= iterations; i++) { | |
| activate('writerAgent', `Writer iteration ${i}/3`); | |
| $('rootStatus').textContent = 'Calling writer_tool'; | |
| log('Writer: producing article from blog_outline'); | |
| await waitStep(); | |
| $('postState').textContent = `draft v${i}`; | |
| activate('postValidator', 'Validating article'); | |
| log('BlogPostValidationChecker: checking structure and technical clarity'); | |
| await waitStep(); | |
| if (i < iterations) { | |
| $('validationState').textContent = 'retry'; | |
| $('postValidator').classList.add('retry'); | |
| $('writerLoop').classList.add('active'); | |
| log('Validation result: retry → writer loop repeats'); | |
| await waitStep(.75); | |
| } else { | |
| $('validationState').textContent = 'ok'; | |
| $('postValidator').classList.add('ok'); | |
| log('Validation result: ok → writer returns control to Blogger'); | |
| await waitStep(.75); | |
| } | |
| } | |
| } | |
| async function processTopic(topic) { | |
| $('currentTopic').textContent = topic; | |
| $('outlineState').textContent = 'empty'; | |
| $('validationState').textContent = 'empty'; | |
| $('postState').textContent = 'empty'; | |
| $('finalOutput').textContent = 'Processing…'; | |
| activate('rootCard', 'Step 1 · Planner'); | |
| $('rootStatus').textContent = 'Topic received → planner first'; | |
| log(`Blogger received topic: “${topic}”`); | |
| await waitStep(.8); | |
| await planner(topic); | |
| if (stopped) return; | |
| activate('rootCard', 'Step 2 · Writer'); | |
| $('rootStatus').textContent = 'Planner complete → writer second'; | |
| log('Blogger: planner returned; invoking writer_tool'); | |
| await waitStep(.8); | |
| await writer(topic); | |
| if (stopped) return; | |
| activate('finalCard', 'Step 3 · Final'); | |
| $('rootStatus').textContent = 'Writer complete → final response'; | |
| $('finalOutput').innerHTML = | |
| `<strong>${topic}</strong><br>✓ Validated blog post<br>✓ 3 alternate titles<br>✓ 2 tweet-length hooks`; | |
| log('Blogger: assembling final response with titles and hooks'); | |
| await waitStep(); | |
| clearActive(); | |
| } | |
| async function run() { | |
| if (running) return; | |
| running = true; | |
| paused = false; | |
| stopped = false; | |
| $('startBtn').disabled = true; | |
| $('pauseBtn').disabled = false; | |
| $('pauseBtn').textContent = 'Pause'; | |
| log('--- Simulation started ---'); | |
| renderQueue(); | |
| while (topicIndex < topics.length && !stopped) { | |
| renderQueue(); | |
| await processTopic(topics[topicIndex]); | |
| if (stopped) break; | |
| topicIndex++; | |
| renderQueue(); | |
| await waitStep(.5); | |
| } | |
| if (!stopped && topicIndex >= topics.length) { | |
| $('currentTopic').textContent = 'All queued topics complete'; | |
| $('iterationBadge').textContent = 'Complete'; | |
| $('rootStatus').textContent = 'Waiting for next topic'; | |
| log('--- Queue complete ---'); | |
| } | |
| running = false; | |
| $('startBtn').disabled = false; | |
| $('startBtn').textContent = | |
| topicIndex >= topics.length ? 'Run Again' : 'Start Simulation'; | |
| $('pauseBtn').disabled = true; | |
| } | |
| $('startBtn').addEventListener('click', () => { | |
| if (topicIndex >= topics.length) { | |
| topicIndex = 0; | |
| renderQueue(); | |
| } | |
| run(); | |
| }); | |
| $('pauseBtn').addEventListener('click', () => { | |
| paused = !paused; | |
| $('pauseBtn').textContent = paused ? 'Resume' : 'Pause'; | |
| $('iterationBadge').textContent = paused ? 'Paused' : 'Running'; | |
| }); | |
| $('resetBtn').addEventListener('click', () => { | |
| stopped = true; | |
| paused = false; | |
| running = false; | |
| topicIndex = 0; | |
| clearActive(); | |
| $('startBtn').disabled = false; | |
| $('startBtn').textContent = 'Start Simulation'; | |
| $('pauseBtn').disabled = true; | |
| $('pauseBtn').textContent = 'Pause'; | |
| $('currentTopic').textContent = 'No topic active'; | |
| $('iterationBadge').textContent = 'Idle'; | |
| $('rootStatus').textContent = 'Waiting for topic'; | |
| $('outlineState').textContent = 'empty'; | |
| $('validationState').textContent = 'empty'; | |
| $('postState').textContent = 'empty'; | |
| $('finalOutput').textContent = | |
| 'Validated blog + 3 alternate titles + 2 tweet-length hooks will appear here.'; | |
| $('log').innerHTML = | |
| '<div class="muted">Simulation reset.</div>'; | |
| renderQueue(); | |
| }); | |
| renderQueue(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |