@akshayram1/omnibrowser-agent 0.2.8 → 0.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.html CHANGED
@@ -5,12 +5,19 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>OmniBrowser Agent — Local-first Browser AI</title>
7
7
  <meta name="description" content="OmniBrowser Agent — local-first browser AI operator. No API keys. No cloud. Runs entirely in the browser via WebLLM + WebGPU." />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Quantico:wght@400;700&display=swap" rel="stylesheet">
8
10
  <link rel="stylesheet" href="./styles.css" />
9
11
  </head>
10
12
  <body>
13
+
14
+ <div class="orb orb-1"></div>
15
+ <div class="orb orb-2"></div>
16
+ <div class="orb orb-3"></div>
17
+
11
18
  <header class="header">
12
19
  <div class="wrap header-row">
13
- <a class="brand" href="#home">OmniBrowser Agent</a>
20
+ <a class="brand" href="#home">omnibrowser-agent</a>
14
21
  <nav class="nav">
15
22
  <a href="#home">Home</a>
16
23
  <a href="#whats-new">What's New</a>
@@ -24,16 +31,17 @@
24
31
  </header>
25
32
 
26
33
  <main>
27
- <!-- HOME -->
34
+
35
+ <!-- ── HOME ── -->
28
36
  <section id="home" class="section hero">
29
37
  <div class="wrap">
30
- <p class="eyebrow">Open-source browser automation SDK · v0.2.6</p>
31
- <h1>Local-first browser AI automation library</h1>
32
- <p>
38
+ <div class="badge-pill fade-up">omnibrowser-agent v0.2.8</div>
39
+ <h1 class="fade-up delay-1">Local-first browser AI automation library</h1>
40
+ <p class="fade-up delay-2">
33
41
  OmniBrowser Agent plans and executes DOM actions entirely in the browser — no API keys, no cloud costs, no data leaving your machine.
34
42
  Wire in a WebLLM model and it reasons, remembers, and acts on any webpage.
35
43
  </p>
36
- <div class="chips">
44
+ <div class="chips fade-up delay-2">
37
45
  <span>Privacy-first</span>
38
46
  <span>WebLLM + WebGPU</span>
39
47
  <span>Reflection loop</span>
@@ -41,18 +49,18 @@
41
49
  <span>Custom system prompt</span>
42
50
  <span>Embeddable API</span>
43
51
  </div>
44
- <div class="actions">
45
- <a class="btn primary" href="./examples/chatbot/">Live Demo</a>
52
+ <div class="actions fade-up delay-3">
53
+ <a class="btn primary" href="./examples/">🚀 Live Demo</a>
46
54
  <a class="btn" href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">NPM Package</a>
47
55
  <a class="btn" href="https://github.com/akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">GitHub</a>
48
56
  </div>
49
- <div class="stats" aria-label="project stats">
57
+ <div class="stats fade-up delay-3" aria-label="project stats">
50
58
  <div class="stat"><strong>2</strong><span>Agent Modes</span></div>
51
59
  <div class="stat"><strong>2</strong><span>Planner Modes</span></div>
52
60
  <div class="stat"><strong>8</strong><span>Action Types</span></div>
53
61
  <div class="stat"><strong>MIT</strong><span>License</span></div>
54
62
  </div>
55
- <div class="home-grid">
63
+ <div class="home-grid fade-up delay-4">
56
64
  <article class="card">
57
65
  <h3>Use Cases</h3>
58
66
  <ul>
@@ -76,18 +84,19 @@
76
84
  <ul>
77
85
  <li><a href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">NPM package</a></li>
78
86
  <li><a href="https://github.com/akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">GitHub repository</a></li>
79
- <li><a href="./examples/chatbot/" target="_blank">Live Demo</a></li>
87
+ <li><a href="./examples/">Live Examples</a></li>
80
88
  </ul>
81
89
  </article>
82
90
  </div>
83
91
  </div>
84
92
  </section>
85
93
 
86
- <!-- WHAT'S NEW -->
94
+ <!-- ── WHAT'S NEW ── -->
87
95
  <section id="whats-new" class="section">
88
96
  <div class="wrap">
89
- <div class="surface">
90
- <h2>What's New in v0.2.6</h2>
97
+ <div class="section-label fade-up">what's new</div>
98
+ <div class="surface fade-up delay-1">
99
+ <h2>What's New in v0.2.8</h2>
91
100
  <p>This release implements the <strong>reflection-before-action pattern</strong> — the same loop used by leading browser agents — plus a new <code>systemPrompt</code> option so you can shape agent behaviour without rewriting the bridge.</p>
92
101
 
93
102
  <h3>Reflection Loop <span class="badge new">New</span></h3>
@@ -142,10 +151,10 @@
142
151
  <pre><code>import { parsePlannerResult } from "@akshayram1/omnibrowser-agent";
143
152
 
144
153
  const result = parsePlannerResult(llmRawOutput);
145
- // result.action → AgentAction
146
- // result.evaluation → string | undefined
147
- // result.memory → string | undefined
148
- // result.nextGoal → string | undefined</code></pre>
154
+ // result.action → AgentAction
155
+ // result.evaluation → string | undefined
156
+ // result.memory → string | undefined
157
+ // result.nextGoal → string | undefined</code></pre>
149
158
 
150
159
  <h3>Backward Compatible</h3>
151
160
  <p>Existing bridges that return a bare <code>AgentAction</code> object still work without any changes. The library normalises both formats automatically.</p>
@@ -153,10 +162,11 @@ const result = parsePlannerResult(llmRawOutput);
153
162
  </div>
154
163
  </section>
155
164
 
156
- <!-- DOCS / QUICK START -->
165
+ <!-- ── DOCS ── -->
157
166
  <section id="docs" class="section">
158
167
  <div class="wrap">
159
- <div class="surface">
168
+ <div class="section-label fade-up">docs</div>
169
+ <div class="surface fade-up delay-1">
160
170
  <h2>Docs</h2>
161
171
  <p>Everything you need to install, initialise, and run your first browser agent.</p>
162
172
 
@@ -262,42 +272,177 @@ controller.abort(); // cancel from outside</code></pre>
262
272
  </div>
263
273
  </section>
264
274
 
265
- <!-- ARCHITECTURE -->
275
+ <!-- ── ARCHITECTURE ── -->
266
276
  <section id="architecture" class="section">
267
277
  <div class="wrap">
268
- <div class="surface">
269
- <h2>Architecture</h2>
270
- <p>OmniBrowser Agent is split into two delivery modes that share the same underlying engine. See the full breakdown in <a href="https://github.com/akshayram1/omnibrowser-agent/blob/main/docs/arch.md" target="_blank" rel="noreferrer">docs/arch.md</a>.</p>
278
+ <div class="section-label fade-up">architecture</div>
271
279
 
272
- <h3>Delivery Layer</h3>
273
- <div class="docs-grid">
274
- <article class="doc-card">
275
- <h4>🧩 Chrome Extension</h4>
276
- <p>Popup UI + background service worker. Manages sessions per tab and drives the tick loop via <code>chrome.tabs.sendMessage</code>.</p>
277
- </article>
278
- <article class="doc-card">
279
- <h4>📦 npm Library</h4>
280
- <p><code>createBrowserAgent()</code> runs the same tick loop in-process inside your web app. No extension required.</p>
281
- </article>
280
+ <div class="section-label fade-up delay-1">01 — delivery</div>
281
+ <div class="delivery-grid fade-up delay-1">
282
+ <div class="card card-ext">
283
+ <span class="card-icon">🧩</span>
284
+ <div class="card-title">Chrome Extension</div>
285
+ <div class="card-sub">popup + background worker</div>
286
+ <div class="card-desc">Load the <code>dist/</code> folder as an unpacked extension. Enter a goal in the popup, pick a mode, and hit Start. The background service worker drives the tick loop across tabs.</div>
287
+ <span class="tag tag-purple">MV3</span>
288
+ <span class="tag tag-purple">background worker</span>
282
289
  </div>
290
+ <div class="card card-lib">
291
+ <span class="card-icon">📦</span>
292
+ <div class="card-title">npm Library</div>
293
+ <div class="card-sub">createBrowserAgent()</div>
294
+ <div class="card-desc">Embed the agent directly into any web app. Import, configure, and wire up event callbacks. The same core engine powers both — zero duplication.</div>
295
+ <span class="tag tag-green">@akshayram1/omnibrowser-agent</span>
296
+ </div>
297
+ </div>
283
298
 
284
- <h3>Core Modules <code>src/core/</code></h3>
285
- <div class="docs-grid">
286
- <article class="doc-card">
287
- <h4>observer.ts</h4>
288
- <p>Queries all interactive elements, filters invisible ones, resolves accessible labels (<code>aria-label</code>, <code>for/id</code>, wrapping <code>&lt;label&gt;</code>), caps at 60 candidates. Returns <code>PageSnapshot</code>.</p>
289
- </article>
290
- <article class="doc-card">
291
- <h4>planner.ts</h4>
292
- <p>Calls heuristic regex or the <code>window.__browserAgentWebLLM</code> bridge. Returns <code>PlannerResult</code> — action plus optional <code>evaluation</code>, <code>memory</code>, <code>nextGoal</code>.</p>
293
- </article>
294
- <article class="doc-card">
295
- <h4>executor.ts</h4>
296
- <p>Performs DOM actions. Uses <code>InputEvent</code> with <code>bubbles: true</code> for React/Vue compat. Verifies element exists, is not disabled, and value updated. Throws on failure so the retry loop feeds <code>lastError</code> back.</p>
297
- </article>
299
+ <div class="connector fade-up delay-2">
300
+ <div class="connector-line"></div>
301
+ <span class="connector-label">both share the same core engine</span>
302
+ </div>
303
+
304
+ <div class="section-label fade-up delay-2">02 — agent tick loop</div>
305
+ <div class="flow-section fade-up delay-2">
306
+ <div class="flow-header">
307
+ <div class="flow-header-dot"></div>
308
+ <div class="flow-header-title">one tick = observe → plan → assess risk → execute</div>
309
+ </div>
310
+ <div class="flow-steps">
311
+ <div class="step">
312
+ <div class="step-num step-num-1">01</div>
313
+ <div class="step-title">Observe</div>
314
+ <div class="step-file">observer.ts</div>
315
+ <div class="step-desc">Scans the live DOM. Filters invisible elements. Prioritises in-viewport candidates. Resolves ARIA labels. Returns a PageSnapshot.</div>
316
+ </div>
317
+ <div class="step">
318
+ <div class="step-num step-num-2">02</div>
319
+ <div class="step-title">Plan</div>
320
+ <div class="step-file">planner.ts</div>
321
+ <div class="step-desc">Takes the goal, snapshot, and history. Returns the next AgentAction — plus optional reflection (evaluation, memory, nextGoal).</div>
322
+ </div>
323
+ <div class="step">
324
+ <div class="step-num step-num-3">03</div>
325
+ <div class="step-title">Assess risk</div>
326
+ <div class="step-file">safety.ts</div>
327
+ <div class="step-desc">Every action gets a risk level: safe, review, or blocked. Risky actions pause for human approval in human-approved mode.</div>
328
+ </div>
329
+ <div class="step">
330
+ <div class="step-num step-num-4">04</div>
331
+ <div class="step-title">Execute</div>
332
+ <div class="step-file">executor.ts</div>
333
+ <div class="step-desc">Performs the DOM action. Dispatches proper InputEvents for framework compat. Verifies success. Feeds errors back to the planner.</div>
334
+ </div>
298
335
  </div>
336
+ </div>
337
+
338
+ <div class="connector fade-up delay-3">
339
+ <div class="connector-line"></div>
340
+ </div>
341
+
342
+ <div class="section-label fade-up delay-3">03 — core modules (src/core/)</div>
343
+ <div class="core-grid fade-up delay-3">
344
+ <div class="module-card mc-blue">
345
+ <div class="module-name">planner.ts</div>
346
+ <div class="module-fn">planNextAction(config, input)</div>
347
+ <ul class="module-items">
348
+ <li>URL, fill, search, click regex patterns</li>
349
+ <li>Fallback: first input → first button → done</li>
350
+ <li>WebLLM bridge via window.__browserAgentWebLLM</li>
351
+ <li>Returns PlannerResult with reflection fields</li>
352
+ <li>lastError fed back on retry</li>
353
+ </ul>
354
+ </div>
355
+ <div class="module-card mc-green">
356
+ <div class="module-name">observer.ts</div>
357
+ <div class="module-fn">collectSnapshot()</div>
358
+ <ul class="module-items">
359
+ <li>Queries a, button, input, textarea, select…</li>
360
+ <li>Filters hidden &amp; zero-dimension elements</li>
361
+ <li>In-viewport elements listed first</li>
362
+ <li>Resolves label via aria-labelledby, for/id, aria-label</li>
363
+ <li>Max 60 candidates, 1500-char text preview</li>
364
+ </ul>
365
+ </div>
366
+ <div class="module-card mc-amber">
367
+ <div class="module-name">executor.ts</div>
368
+ <div class="module-fn">executeAction(action)</div>
369
+ <ul class="module-items">
370
+ <li>click — disabled-check before firing</li>
371
+ <li>type — InputEvent + change dispatch (React/Vue safe)</li>
372
+ <li>navigate — sets window.location.href</li>
373
+ <li>extract — verifies non-empty text</li>
374
+ <li>scroll, focus, wait actions</li>
375
+ </ul>
376
+ </div>
377
+ </div>
299
378
 
300
- <h3>Data Flow One Tick</h3>
379
+ <div class="section-label fade-up delay-4">04safety &amp; planner modes</div>
380
+ <div class="shared-grid fade-up delay-4">
381
+ <div class="risk-panel">
382
+ <div class="risk-title">// safety.ts — assessRisk()</div>
383
+ <div class="risk-row">
384
+ <div class="risk-dot" style="background:var(--accent)"></div>
385
+ <div class="risk-label" style="color:var(--accent)">safe</div>
386
+ <div class="risk-bar-wrap"><div class="risk-bar" style="width:90%;background:var(--accent)"></div></div>
387
+ </div>
388
+ <div class="risk-note" style="margin-bottom:12px;padding-left:18px">navigate (http/s), click, scroll, wait, focus, done</div>
389
+ <div class="risk-row">
390
+ <div class="risk-dot" style="background:var(--amber)"></div>
391
+ <div class="risk-label" style="color:var(--amber)">review</div>
392
+ <div class="risk-bar-wrap"><div class="risk-bar" style="width:55%;background:var(--amber)"></div></div>
393
+ </div>
394
+ <div class="risk-note" style="margin-bottom:12px;padding-left:18px">extract, click/type on delete · pay · submit · transfer</div>
395
+ <div class="risk-row">
396
+ <div class="risk-dot" style="background:var(--accent3)"></div>
397
+ <div class="risk-label" style="color:var(--accent3)">blocked</div>
398
+ <div class="risk-bar-wrap"><div class="risk-bar" style="width:20%;background:var(--accent3)"></div></div>
399
+ </div>
400
+ <div class="risk-note" style="padding-left:18px">javascript: · file: · malformed URLs</div>
401
+ </div>
402
+
403
+ <div class="planner-panel">
404
+ <div class="risk-title" style="margin-bottom:14px">// planner modes</div>
405
+ <div class="planner-tabs">
406
+ <button class="ptab active-h" onclick="switchPlanner('h')">heuristic</button>
407
+ <button class="ptab" onclick="switchPlanner('w')">webllm</button>
408
+ </div>
409
+ <div class="pmode active" id="pm-h">
410
+ <div class="pmode-title">Heuristic — zero deps, works offline</div>
411
+ <div class="pmode-desc">Pure regex matching against your goal string. Handles navigate, fill, search, and click patterns. Falls back to the first visible input or button.</div>
412
+ <code class="code-chip"><span class="kw">const</span> agent = createBrowserAgent({<br>&nbsp;&nbsp;goal: <span class="str">"search for John Smith"</span>,<br>&nbsp;&nbsp;planner: { kind: <span class="str">"heuristic"</span> }<br>});</code>
413
+ </div>
414
+ <div class="pmode" id="pm-w">
415
+ <div class="pmode-title">WebLLM — on-device via WebGPU</div>
416
+ <div class="pmode-desc">Delegates to <code>window.__browserAgentWebLLM</code>. Fully private — no API calls, runs local inference via WebGPU. Wire in your own bridge implementation.</div>
417
+ <code class="code-chip"><span class="kw">const</span> agent = createBrowserAgent({<br>&nbsp;&nbsp;goal: <span class="str">"open CRM and find customer"</span>,<br>&nbsp;&nbsp;planner: { kind: <span class="str">"webllm"</span>, modelId: <span class="str">"Llama-3"</span> }<br>}); <span class="cm">// needs bridge on window</span></code>
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <div class="section-label fade-up delay-5">05 — action outcomes</div>
423
+ <div class="outcomes-grid fade-up delay-5">
424
+ <div class="outcome-card oc-safe">
425
+ <span class="outcome-icon">✅</span>
426
+ <div class="outcome-title" style="color:var(--accent)">Executed</div>
427
+ <div class="outcome-desc">Action is safe. Runs immediately. Result message appended to session history. Next tick begins.</div>
428
+ <span class="tag tag-green">status: "executed"</span>
429
+ </div>
430
+ <div class="outcome-card oc-review">
431
+ <span class="outcome-icon">⏸</span>
432
+ <div class="outcome-title" style="color:var(--amber)">Needs approval</div>
433
+ <div class="outcome-desc">Risk level is "review" in human-approved mode. Agent pauses, fires onApprovalRequired. Resume with agent.resume().</div>
434
+ <span class="tag tag-amber">status: "needs_approval"</span>
435
+ </div>
436
+ <div class="outcome-card oc-block">
437
+ <span class="outcome-icon">🚫</span>
438
+ <div class="outcome-title" style="color:var(--accent3)">Blocked</div>
439
+ <div class="outcome-desc">Dangerous protocol or malformed URL. Action is rejected entirely. Session stops without executing anything.</div>
440
+ <span class="tag tag-red">status: "blocked"</span>
441
+ </div>
442
+ </div>
443
+
444
+ <div class="surface fade-up delay-5">
445
+ <h3 style="margin-top:0">Data Flow — One Tick</h3>
301
446
  <pre><code>goal + history + memory
302
447
 
303
448
 
@@ -348,19 +493,154 @@ blocked review (human-approved mode)
348
493
  <li>No selector healing or fallback strategy yet</li>
349
494
  </ul>
350
495
  </div>
496
+
351
497
  </div>
352
498
  </section>
353
499
 
354
- <!-- EMBEDDING -->
500
+ <!-- ── EMBEDDING ── -->
355
501
  <section id="embedding" class="section">
356
502
  <div class="wrap">
357
- <div class="surface">
503
+ <div class="section-label fade-up">embedding guide</div>
504
+
505
+ <div class="section-label fade-up delay-1">06 — get started</div>
506
+
507
+ <div class="install-strip fade-up delay-1">
508
+ <div class="install-left">
509
+ <div class="install-pkg">
510
+ <span class="install-scope">@akshayram1/</span><span class="install-name">omnibrowser-agent</span>
511
+ </div>
512
+ <div class="install-meta">
513
+ <span class="tag tag-green" style="margin:0">v0.2.8</span>
514
+ <span class="tag tag-purple" style="margin:0">MIT</span>
515
+ <span class="tag tag-amber" style="margin:0">TypeScript</span>
516
+ </div>
517
+ </div>
518
+ <div class="install-cmd-wrap">
519
+ <span class="install-prompt">$</span>
520
+ <span class="install-cmd" id="install-cmd-text">npm install @akshayram1/omnibrowser-agent</span>
521
+ <button class="copy-btn" onclick="copyInstall()" title="Copy">
522
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
523
+ <span id="copy-label">copy</span>
524
+ </button>
525
+ </div>
526
+ </div>
527
+
528
+ <div class="snippet-panel fade-up delay-2">
529
+ <div class="snippet-tabs">
530
+ <button class="stab stab-active" onclick="switchSnippet('basic')">basic usage</button>
531
+ <button class="stab" onclick="switchSnippet('approved')">human-approved</button>
532
+ <button class="stab" onclick="switchSnippet('events')">event callbacks</button>
533
+ <button class="stab" onclick="switchSnippet('abort')">abort signal</button>
534
+ <button class="stab" onclick="switchSnippet('webllm')">webllm bridge</button>
535
+ </div>
536
+ <div class="snippet-body">
537
+ <div class="snippet active" id="sn-basic">
538
+ <div class="snippet-comment">// Minimal setup — heuristic planner, autonomous mode</div>
539
+ <div class="snippet-line"><span class="kw">import</span> { createBrowserAgent } <span class="kw">from</span> <span class="str">'@akshayram1/omnibrowser-agent'</span>;</div>
540
+ <div class="snippet-line">&nbsp;</div>
541
+ <div class="snippet-line"><span class="kw">const</span> agent = createBrowserAgent({</div>
542
+ <div class="snippet-line">&nbsp;&nbsp;goal: <span class="str">"search for contact John Smith in CRM"</span>,</div>
543
+ <div class="snippet-line">&nbsp;&nbsp;mode: <span class="str">"autonomous"</span>,</div>
544
+ <div class="snippet-line">&nbsp;&nbsp;planner: { kind: <span class="str">"heuristic"</span> }</div>
545
+ <div class="snippet-line">});</div>
546
+ <div class="snippet-line">&nbsp;</div>
547
+ <div class="snippet-line"><span class="kw">await</span> agent.start();</div>
548
+ </div>
549
+ <div class="snippet" id="sn-approved">
550
+ <div class="snippet-comment">// Human-approved mode — agent pauses on risky actions</div>
551
+ <div class="snippet-line"><span class="kw">import</span> { createBrowserAgent } <span class="kw">from</span> <span class="str">'@akshayram1/omnibrowser-agent'</span>;</div>
552
+ <div class="snippet-line">&nbsp;</div>
553
+ <div class="snippet-line"><span class="kw">const</span> agent = createBrowserAgent({</div>
554
+ <div class="snippet-line">&nbsp;&nbsp;goal: <span class="str">"fill out the payment form"</span>,</div>
555
+ <div class="snippet-line">&nbsp;&nbsp;mode: <span class="str">"human-approved"</span>, <span class="cm">// pauses on delete / submit / pay</span></div>
556
+ <div class="snippet-line">&nbsp;&nbsp;planner: { kind: <span class="str">"heuristic"</span> },</div>
557
+ <div class="snippet-line">&nbsp;&nbsp;maxSteps: <span class="num">20</span>,</div>
558
+ <div class="snippet-line">&nbsp;&nbsp;stepDelayMs: <span class="num">500</span></div>
559
+ <div class="snippet-line">});</div>
560
+ <div class="snippet-line">&nbsp;</div>
561
+ <div class="snippet-line"><span class="kw">await</span> agent.start();</div>
562
+ <div class="snippet-line">&nbsp;</div>
563
+ <div class="snippet-comment">// Later — after user reviews the pending action:</div>
564
+ <div class="snippet-line"><span class="kw">await</span> agent.resume();</div>
565
+ </div>
566
+ <div class="snippet" id="sn-events">
567
+ <div class="snippet-comment">// Full event callback API</div>
568
+ <div class="snippet-line"><span class="kw">import</span> { createBrowserAgent } <span class="kw">from</span> <span class="str">'@akshayram1/omnibrowser-agent'</span>;</div>
569
+ <div class="snippet-line">&nbsp;</div>
570
+ <div class="snippet-line"><span class="kw">const</span> agent = createBrowserAgent(</div>
571
+ <div class="snippet-line">&nbsp;&nbsp;{ goal: <span class="str">"open CRM and find customer"</span>, mode: <span class="str">"human-approved"</span> },</div>
572
+ <div class="snippet-line">&nbsp;&nbsp;{</div>
573
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;onStart: (session) =&gt; console.log(<span class="str">'started'</span>, session.id),</div>
574
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;onStep: (result, session) =&gt; console.log(result.message),</div>
575
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;onApprovalRequired: (action, session) =&gt; {</div>
576
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(<span class="str">'Review:'</span>, action);</div>
577
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="cm">// call agent.resume() after user confirms</span></div>
578
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;},</div>
579
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;onDone: (result) =&gt; console.log(<span class="str">'Done:'</span>, result.message),</div>
580
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;onError: (err) =&gt; console.error(err),</div>
581
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;onMaxStepsReached: (session) =&gt; console.warn(<span class="str">'max steps'</span>, session)</div>
582
+ <div class="snippet-line">&nbsp;&nbsp;}</div>
583
+ <div class="snippet-line">);</div>
584
+ <div class="snippet-line">&nbsp;</div>
585
+ <div class="snippet-line"><span class="kw">await</span> agent.start();</div>
586
+ </div>
587
+ <div class="snippet" id="sn-abort">
588
+ <div class="snippet-comment">// AbortSignal — cancel from outside at any time</div>
589
+ <div class="snippet-line"><span class="kw">import</span> { createBrowserAgent } <span class="kw">from</span> <span class="str">'@akshayram1/omnibrowser-agent'</span>;</div>
590
+ <div class="snippet-line">&nbsp;</div>
591
+ <div class="snippet-line"><span class="kw">const</span> controller = <span class="kw">new</span> AbortController();</div>
592
+ <div class="snippet-line">&nbsp;</div>
593
+ <div class="snippet-line"><span class="kw">const</span> agent = createBrowserAgent({</div>
594
+ <div class="snippet-line">&nbsp;&nbsp;goal: <span class="str">"extract all product prices"</span>,</div>
595
+ <div class="snippet-line">&nbsp;&nbsp;signal: controller.signal <span class="cm">// wire in the abort signal</span></div>
596
+ <div class="snippet-line">});</div>
597
+ <div class="snippet-line">&nbsp;</div>
598
+ <div class="snippet-line">agent.start();</div>
599
+ <div class="snippet-line">&nbsp;</div>
600
+ <div class="snippet-comment">// Cancel externally (e.g. button click, timeout, unmount)</div>
601
+ <div class="snippet-line">setTimeout(() =&gt; controller.abort(), <span class="num">5000</span>);</div>
602
+ <div class="snippet-line">&nbsp;</div>
603
+ <div class="snippet-comment">// Or call stop() directly:</div>
604
+ <div class="snippet-line">agent.stop();</div>
605
+ </div>
606
+ <div class="snippet" id="sn-webllm">
607
+ <div class="snippet-comment">// WebLLM bridge with reflection loop</div>
608
+ <div class="snippet-line"><span class="kw">import</span> * <span class="kw">as</span> webllm <span class="kw">from</span> <span class="str">"@mlc-ai/web-llm"</span>;</div>
609
+ <div class="snippet-line"><span class="kw">import</span> { createBrowserAgent, parsePlannerResult } <span class="kw">from</span> <span class="str">"@akshayram1/omnibrowser-agent"</span>;</div>
610
+ <div class="snippet-line">&nbsp;</div>
611
+ <div class="snippet-line"><span class="kw">const</span> engine = <span class="kw">await</span> webllm.CreateMLCEngine(<span class="str">"Llama-3.2-3B-Instruct-q4f16_1-MLC"</span>);</div>
612
+ <div class="snippet-line">&nbsp;</div>
613
+ <div class="snippet-line">window.__browserAgentWebLLM = {</div>
614
+ <div class="snippet-line">&nbsp;&nbsp;<span class="kw">async</span> plan(input, modelId) {</div>
615
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;<span class="kw">const</span> resp = <span class="kw">await</span> engine.chat.completions.create({</div>
616
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messages: [{ role: <span class="str">"user"</span>, content: `Goal: ${input.goal}` }],</div>
617
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;temperature: <span class="num">0</span>, max_tokens: <span class="num">200</span></div>
618
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;});</div>
619
+ <div class="snippet-line">&nbsp;&nbsp;&nbsp;&nbsp;<span class="kw">return</span> parsePlannerResult(resp.choices[<span class="num">0</span>].message.content);</div>
620
+ <div class="snippet-line">&nbsp;&nbsp;}</div>
621
+ <div class="snippet-line">};</div>
622
+ </div>
623
+ </div>
624
+ <div class="exports-row">
625
+ <div class="exports-label">exports</div>
626
+ <div class="exports-chips">
627
+ <span class="exp-chip exp-fn">createBrowserAgent()</span>
628
+ <span class="exp-chip exp-fn">parseAction()</span>
629
+ <span class="exp-chip exp-fn">parsePlannerResult()</span>
630
+ <span class="exp-chip exp-type">AgentAction</span>
631
+ <span class="exp-chip exp-type">AgentMode</span>
632
+ <span class="exp-chip exp-type">AgentSession</span>
633
+ <span class="exp-chip exp-type">PlannerConfig</span>
634
+ <span class="exp-chip exp-type">ContentResult</span>
635
+ <span class="exp-chip exp-type">RiskLevel</span>
636
+ </div>
637
+ </div>
638
+ </div>
639
+
640
+ <div class="surface fade-up delay-3">
358
641
  <h2>Embedding Guide</h2>
359
642
  <p>Embed OmniBrowser Agent as a library in any web application. Full reference in <a href="https://github.com/akshayram1/omnibrowser-agent/blob/main/docs/EMBEDDING.md" target="_blank" rel="noreferrer">docs/EMBEDDING.md</a>.</p>
360
643
 
361
- <h3>Install</h3>
362
- <pre><code>npm install @akshayram1/omnibrowser-agent</code></pre>
363
-
364
644
  <h3>Heuristic Planner (zero setup)</h3>
365
645
  <pre><code>import { createBrowserAgent } from "@akshayram1/omnibrowser-agent";
366
646
 
@@ -451,10 +731,11 @@ Never navigate away from the booking portal.`
451
731
  </div>
452
732
  </section>
453
733
 
454
- <!-- ROADMAP -->
734
+ <!-- ── ROADMAP ── -->
455
735
  <section id="roadmap" class="section">
456
736
  <div class="wrap">
457
- <div class="surface">
737
+ <div class="section-label fade-up">roadmap</div>
738
+ <div class="surface fade-up delay-1">
458
739
  <h2>Roadmap</h2>
459
740
  <p>Full roadmap in <a href="https://github.com/akshayram1/omnibrowser-agent/blob/main/docs/ROADMAP.md" target="_blank" rel="noreferrer">docs/ROADMAP.md</a>.</p>
460
741
 
@@ -466,7 +747,7 @@ Never navigate away from the booking portal.`
466
747
  <li>Human-approved mode</li>
467
748
  </ul>
468
749
 
469
- <h3>v0.2 <span class="badge">stable</span></h3>
750
+ <h3>v0.2 <span class="badge stable">stable</span></h3>
470
751
  <ul>
471
752
  <li>New actions: <code>scroll</code>, <code>focus</code></li>
472
753
  <li>Improved heuristic planner with regex goal patterns</li>
@@ -475,7 +756,7 @@ Never navigate away from the booking portal.`
475
756
  <li>CI pipeline with auto version bump on push to main</li>
476
757
  </ul>
477
758
 
478
- <h3>v0.2.6 <span class="badge new">current</span></h3>
759
+ <h3>v0.2.8 <span class="badge new">current</span></h3>
479
760
  <ul>
480
761
  <li>Reflection-before-action pattern (<code>evaluation → memory → next_goal → act</code>)</li>
481
762
  <li>Working memory carried across ticks via <code>AgentSession.memory</code></li>
@@ -483,32 +764,34 @@ Never navigate away from the booking portal.`
483
764
  <li><code>systemPrompt</code> option in <code>PlannerConfig</code></li>
484
765
  <li>Thought bubble (💭) messages in live demo</li>
485
766
  <li>Chatbot UI redesign: tabs, typing indicator, right-aligned messages</li>
767
+ <li>Doc Viewer example with hidden tabs + side chat</li>
768
+ <li>Live Examples hub at <code>/examples</code></li>
486
769
  </ul>
487
770
 
488
771
  <h3>v0.3</h3>
489
772
  <ul>
490
- <li>Site profile and policy engine (allowlist, blocked domains)</li>
491
- <li>Selector healing and fallback strategy</li>
492
- <li>Session replay log</li>
773
+ <li>Expanded WebLLM model catalog (new 7B/8B options + compatibility matrix)</li>
774
+ <li>Improved model loading UX (recommended presets by speed/quality and device memory)</li>
775
+ <li>Enhanced default system prompts for safer, clearer multi-step planning</li>
776
+ <li>Prompt presets for common workflows (docs navigation, CRM form fill, task automation)</li>
493
777
  </ul>
494
778
 
495
779
  <h3>v1.0</h3>
496
780
  <ul>
497
- <li>Long-term encrypted memory in IndexedDB</li>
498
- <li>Goal decomposition planner (multi-step task graphs)</li>
499
- <li>Multi-tab workflows</li>
500
- <li>Stable plugin API for site skills</li>
501
- <li>Validation/eval harness with benchmark tasks</li>
502
- <li>Cross-browser packaging (Chromium + Firefox)</li>
781
+ <li>Advanced prompt orchestration (goal-aware system prompt routing and contextual guardrails)</li>
782
+ <li>Functionality expansion: richer action toolkit and stronger extraction/navigation reliability</li>
783
+ <li>Adaptive planner behaviour (model-aware retries, fallback strategies, and recovery flows)</li>
784
+ <li>Evaluation suite for prompt and model quality across benchmark browser tasks</li>
503
785
  </ul>
504
786
  </div>
505
787
  </div>
506
788
  </section>
507
789
 
508
- <!-- CONTACT -->
790
+ <!-- ── CONTACT ── -->
509
791
  <section id="contact" class="section">
510
792
  <div class="wrap">
511
- <div class="surface">
793
+ <div class="section-label fade-up">contact</div>
794
+ <div class="surface fade-up delay-1">
512
795
  <h2>Contact</h2>
513
796
  <p>Maintainer: Akshay Chame</p>
514
797
  <ul>
@@ -518,14 +801,648 @@ Never navigate away from the booking portal.`
518
801
  </ul>
519
802
  <p class="contact-note">For feature requests or bugs, please open an issue on GitHub with reproduction steps.</p>
520
803
  </div>
804
+
805
+ <div class="cta-row fade-up delay-2">
806
+ <a href="https://github.com/akshayram1/omnibrowser-agent" class="btn primary">↗ View on GitHub</a>
807
+ <a href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent" class="btn">npm package</a>
808
+ <a href="./examples/" class="btn">live examples</a>
809
+ </div>
521
810
  </div>
522
811
  </section>
812
+
523
813
  </main>
524
814
 
525
815
  <footer class="footer">
526
816
  <div class="wrap">
527
- <p>© 2026 OmniBrowser Agent · MIT License · <a href="https://github.com/akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">GitHub</a></p>
817
+ <p>© 2026 OmniBrowser Agent · MIT License · <a href="https://github.com/akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">GitHub</a> · <a href="https://www.npmjs.com/package/@akshayram1/omnibrowser-agent" target="_blank" rel="noreferrer">npm</a></p>
528
818
  </div>
529
819
  </footer>
820
+
821
+ <!-- ── DOCS NAVIGATION CHATBOT WIDGET ── -->
822
+ <style>
823
+ #chatbot-toggle {
824
+ position: fixed; bottom: 28px; right: 28px; z-index: 9000;
825
+ width: 52px; height: 52px; border-radius: 50%;
826
+ background: var(--accent); border: none; cursor: pointer;
827
+ display: flex; align-items: center; justify-content: center;
828
+ box-shadow: 0 4px 24px rgba(0,229,160,0.35);
829
+ transition: transform .2s, box-shadow .2s; font-size: 22px;
830
+ }
831
+ #chatbot-toggle:hover { transform: scale(1.1); box-shadow: 0 6px 30px rgba(0,229,160,0.45); }
832
+ #chatbot-toggle.open { background: var(--surface2); box-shadow: 0 4px 20px rgba(0,0,0,0.4); }
833
+
834
+ #chatbot-panel {
835
+ position: fixed; bottom: 92px; right: 28px; z-index: 9000;
836
+ width: 350px; max-height: 560px;
837
+ background: var(--surface); border: 1px solid var(--border-bright);
838
+ border-radius: 20px; display: flex; flex-direction: column;
839
+ box-shadow: 0 20px 60px rgba(0,0,0,0.55), 0 0 0 1px rgba(0,229,160,0.06);
840
+ transform: translateY(12px) scale(0.97);
841
+ opacity: 0; pointer-events: none;
842
+ transition: transform .22s cubic-bezier(.34,1.56,.64,1), opacity .18s ease;
843
+ overflow: hidden;
844
+ }
845
+ #chatbot-panel.visible { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
846
+
847
+ /* Header */
848
+ .cb-header {
849
+ padding: 13px 16px 11px; border-bottom: 1px solid var(--border);
850
+ display: flex; align-items: center; gap: 10px; flex-shrink: 0;
851
+ }
852
+ .cb-icon {
853
+ width: 30px; height: 30px; border-radius: 9px;
854
+ background: rgba(0,229,160,0.12); border: 1px solid rgba(0,229,160,0.2);
855
+ display: flex; align-items: center; justify-content: center; font-size: 14px;
856
+ }
857
+ .cb-title { font-family: var(--sans); font-size: 13px; font-weight: 600; color: var(--text); }
858
+ .cb-subtitle { font-family: var(--mono); font-size: 10px; color: var(--muted); letter-spacing: 0.04em; }
859
+ .cb-status-pill {
860
+ margin-left: auto; font-family: var(--mono); font-size: 10px;
861
+ border-radius: 999px; padding: 2px 8px; letter-spacing: 0.04em;
862
+ white-space: nowrap; display: flex; align-items: center; gap: 5px;
863
+ border: 1px solid var(--border); color: var(--muted); background: transparent;
864
+ transition: all .2s;
865
+ }
866
+ .cb-status-pill.ready { color: var(--accent); border-color: rgba(0,229,160,0.28); background: rgba(0,229,160,0.07); }
867
+ .cb-status-pill.loading{ color: var(--amber); border-color: rgba(245,158,11,0.3); background: rgba(245,158,11,0.07); }
868
+ .cb-sdot {
869
+ width: 5px; height: 5px; border-radius: 50%; background: var(--muted); flex-shrink: 0;
870
+ }
871
+ .cb-sdot.ready { background: var(--accent); }
872
+ .cb-sdot.loading { background: var(--amber); animation: cbPulse 1s ease-in-out infinite; }
873
+ @keyframes cbPulse { 0%,100%{opacity:1} 50%{opacity:.3} }
874
+
875
+ /* Model loader bar */
876
+ .cb-model-bar {
877
+ padding: 9px 14px; border-bottom: 1px solid var(--border);
878
+ flex-shrink: 0; display: flex; flex-direction: column; gap: 7px;
879
+ }
880
+ .cb-model-row { display: flex; gap: 7px; align-items: center; }
881
+ .cb-model-select {
882
+ flex: 1; background: var(--surface2); border: 1px solid var(--border);
883
+ border-radius: 8px; padding: 5px 8px; color: var(--text);
884
+ font-family: var(--mono); font-size: 10.5px; outline: none; cursor: pointer;
885
+ transition: border-color .15s;
886
+ }
887
+ .cb-model-select:focus { border-color: rgba(0,229,160,0.35); }
888
+ .cb-model-select option { background: #0d1117; }
889
+ .cb-load-btn {
890
+ padding: 5px 13px; border-radius: 8px; border: none; cursor: pointer;
891
+ background: var(--accent); color: #000; font-family: var(--mono);
892
+ font-size: 10.5px; font-weight: 700; white-space: nowrap;
893
+ transition: opacity .15s, transform .1s;
894
+ }
895
+ .cb-load-btn:hover:not(:disabled) { opacity: .88; transform: translateY(-1px); }
896
+ .cb-load-btn:disabled { opacity: .4; cursor: not-allowed; transform: none; }
897
+ .cb-load-btn.loaded {
898
+ background: transparent; border: 1px solid rgba(0,229,160,0.28);
899
+ color: var(--accent);
900
+ }
901
+
902
+ .cb-progress-wrap { display: none; }
903
+ .cb-progress-wrap.visible { display: flex; flex-direction: column; gap: 3px; }
904
+ .cb-progress-track {
905
+ height: 2px; background: var(--border); border-radius: 999px; overflow: hidden;
906
+ }
907
+ .cb-progress-fill {
908
+ height: 100%; background: linear-gradient(90deg, var(--accent), #00ffc2);
909
+ border-radius: 999px; width: 0%; transition: width .4s ease;
910
+ }
911
+ .cb-progress-text { font-family: var(--mono); font-size: 10px; color: var(--muted); }
912
+ .cb-webgpu-warn {
913
+ font-family: var(--mono); font-size: 10px; color: var(--amber);
914
+ background: rgba(245,158,11,0.07); border: 1px solid rgba(245,158,11,0.2);
915
+ border-radius: 6px; padding: 5px 8px; display: none; line-height: 1.5;
916
+ }
917
+ .cb-webgpu-warn.visible { display: block; }
918
+
919
+ /* Messages */
920
+ .cb-messages {
921
+ flex: 1; overflow-y: auto; padding: 12px 14px;
922
+ display: flex; flex-direction: column; gap: 8px; scroll-behavior: smooth;
923
+ scrollbar-width: thin; scrollbar-color: var(--border) transparent;
924
+ min-height: 0;
925
+ }
926
+ .cb-messages::-webkit-scrollbar { width: 4px; }
927
+ .cb-messages::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 4px; }
928
+
929
+ .cb-msg { display: flex; gap: 8px; align-items: flex-end; animation: cbMsgIn .18s ease; }
930
+ @keyframes cbMsgIn { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:translateY(0)} }
931
+ .cb-msg.user { flex-direction: row-reverse; }
932
+ .cb-bubble { max-width: 82%; padding: 8px 12px; font-size: 12.5px; line-height: 1.55; border-radius: 14px; }
933
+ .cb-msg.bot .cb-bubble { background: var(--surface2); border: 1px solid var(--border); color: var(--text); border-radius: 4px 14px 14px 14px; }
934
+ .cb-msg.bot.thought .cb-bubble {
935
+ max-width: 88%; padding: 7px 10px; font-size: 11px;
936
+ color: var(--muted); background: rgba(0,229,160,0.06);
937
+ border: 1px solid rgba(0,229,160,0.16);
938
+ }
939
+ .cb-msg.user .cb-bubble { background: rgba(0,229,160,0.12); border: 1px solid rgba(0,229,160,0.22); color: var(--text); border-radius: 14px 14px 4px 14px; }
940
+ .cb-msg.bot .cb-bubble a { color: var(--accent); text-decoration: none; }
941
+ .cb-msg.bot .cb-bubble a:hover { text-decoration: underline; }
942
+
943
+ /* Typing indicator */
944
+ .cb-typing { display: flex; gap: 8px; align-items: flex-end; animation: cbMsgIn .18s ease; }
945
+ .cb-typing-dots {
946
+ background: var(--surface2); border: 1px solid var(--border);
947
+ border-radius: 4px 14px 14px 14px; padding: 10px 14px;
948
+ display: flex; gap: 4px; align-items: center;
949
+ }
950
+ .cb-tdot { width: 5px; height: 5px; border-radius: 50%; background: var(--muted); animation: cbTdot 1.2s ease-in-out infinite; }
951
+ .cb-tdot:nth-child(2) { animation-delay: .15s; }
952
+ .cb-tdot:nth-child(3) { animation-delay: .3s; }
953
+ @keyframes cbTdot { 0%,60%,100%{transform:translateY(0)} 30%{transform:translateY(-4px)} }
954
+
955
+ /* Suggestions */
956
+ .cb-suggestions { padding: 8px 14px 0; display: flex; flex-wrap: wrap; gap: 6px; flex-shrink: 0; }
957
+ .cb-chip {
958
+ font-family: var(--mono); font-size: 10px; color: var(--muted);
959
+ background: var(--surface2); border: 1px solid var(--border);
960
+ border-radius: 999px; padding: 3px 10px; cursor: pointer;
961
+ transition: all .15s; white-space: nowrap; letter-spacing: 0.02em;
962
+ }
963
+ .cb-chip:hover { color: var(--accent); border-color: rgba(0,229,160,0.3); }
964
+
965
+ /* Input */
966
+ .cb-input-area {
967
+ padding: 11px 14px; border-top: 1px solid var(--border);
968
+ display: flex; gap: 8px; flex-shrink: 0;
969
+ }
970
+ .cb-input {
971
+ flex: 1; background: var(--surface2); border: 1px solid var(--border);
972
+ border-radius: 10px; padding: 8px 12px; color: var(--text);
973
+ font-size: 12.5px; font-family: var(--sans); outline: none; transition: border-color .15s;
974
+ }
975
+ .cb-input::placeholder { color: var(--muted); }
976
+ .cb-input:focus { border-color: rgba(0,229,160,0.35); }
977
+ .cb-send {
978
+ width: 34px; height: 34px; border-radius: 9px;
979
+ background: var(--accent); border: none; cursor: pointer;
980
+ display: flex; align-items: center; justify-content: center;
981
+ font-size: 14px; flex-shrink: 0; transition: opacity .15s, transform .1s;
982
+ }
983
+ .cb-send:hover { opacity: .85; transform: scale(1.05); }
984
+ .cb-send:disabled { opacity: .4; cursor: not-allowed; transform: none; }
985
+ </style>
986
+
987
+ <button id="chatbot-toggle" title="Ask about the docs" aria-label="Open docs assistant">💬</button>
988
+
989
+ <div id="chatbot-panel" role="dialog" aria-label="Docs navigation assistant">
990
+
991
+ <div class="cb-header">
992
+ <div class="cb-icon">🤖</div>
993
+ <div>
994
+ <div class="cb-title">Docs Assistant</div>
995
+ <div class="cb-subtitle">powered by omnibrowser-agent</div>
996
+ </div>
997
+ <div class="cb-status-pill" id="cb-status-pill">
998
+ <span class="cb-sdot" id="cb-sdot"></span>
999
+ <span id="cb-status-text">heuristic</span>
1000
+ </div>
1001
+ </div>
1002
+
1003
+ <!-- Model loader -->
1004
+ <div class="cb-model-bar">
1005
+ <div class="cb-model-row">
1006
+ <select class="cb-model-select" id="cb-model-select">
1007
+ <option value="Phi-3.5-mini-instruct-q4f16_1-MLC" selected>Phi-3.5 Mini — quality (~2 GB)</option>
1008
+ <option value="Llama-3.2-3B-Instruct-q4f16_1-MLC">Llama 3.2 3B — balanced (~1.5 GB)</option>
1009
+ <option value="Llama-3.2-1B-Instruct-q4f16_1-MLC">Llama 3.2 1B — fast (~600 MB)</option>
1010
+ <option value="Mistral-7B-Instruct-v0.3-q4f16_1-MLC">Mistral 7B v0.3 — balanced (~4.1 GB)</option>
1011
+ <option value="Qwen2.5-7B-Instruct-q4f16_1-MLC">Qwen2.5 7B — strongest (~4.3 GB)</option>
1012
+ <option value="Llama-3.1-8B-Instruct-q4f16_1-MLC">Llama 3.1 8B — strong (~4.8 GB)</option>
1013
+ </select>
1014
+ <button class="cb-load-btn" id="cb-load-btn">Load AI</button>
1015
+ </div>
1016
+ <div class="cb-progress-wrap" id="cb-progress-wrap">
1017
+ <div class="cb-progress-track"><div class="cb-progress-fill" id="cb-progress-fill"></div></div>
1018
+ <div class="cb-progress-text" id="cb-progress-text">Initialising…</div>
1019
+ </div>
1020
+ <div class="cb-webgpu-warn" id="cb-webgpu-warn">
1021
+ ⚠️ WebGPU unavailable — try Chrome 113+. Running in heuristic mode.
1022
+ </div>
1023
+ </div>
1024
+
1025
+ <div class="cb-messages" id="cb-messages"></div>
1026
+
1027
+ <div class="cb-suggestions" id="cb-suggestions">
1028
+ <button class="cb-chip" data-q="How do I install?">install</button>
1029
+ <button class="cb-chip" data-q="Show me the examples">examples</button>
1030
+ <button class="cb-chip" data-q="How does the architecture work?">architecture</button>
1031
+ <button class="cb-chip" data-q="What's new in v0.2?">what's new</button>
1032
+ <button class="cb-chip" data-q="Embedding guide">embedding</button>
1033
+ </div>
1034
+
1035
+ <div class="cb-input-area">
1036
+ <input class="cb-input" id="cb-input" type="text" placeholder="Ask me anything about the docs…" autocomplete="off" />
1037
+ <button class="cb-send" id="cb-send" aria-label="Send">↑</button>
1038
+ </div>
1039
+ </div>
1040
+
1041
+ <script type="module">
1042
+
1043
+ /* ── Navigation map ── */
1044
+ const NAV = [
1045
+ { match: /all.?docs|show.?docs|docs.?section|documentation|all.?sections/i, id: '#docs', reply: 'Taking you to the <strong>Docs</strong> section overview.' },
1046
+ { match: /install|npm|package|get.?start|quick.?start|setup/i, id: '#docs', reply: 'Scrolling to <strong>Installation & Quick Start</strong>.' },
1047
+ { match: /what.?s.?new|release|v0\.|changelog|update|new.?feature/i, id: '#whats-new', reply: 'Here are the <strong>latest features</strong> — reflection loop, working memory, custom system prompts.' },
1048
+ { match: /architect|system|design|how.?it.?work|internals|engine|tick/i, id: '#architecture', reply: 'Navigating to <strong>Architecture</strong> — agent tick loop, safety model, core modules.' },
1049
+ { match: /embed|integrat|api|librar|wire|drop.?in|snippet/i, id: '#embedding', reply: 'Taking you to the <strong>Embedding Guide</strong> with heuristic & WebLLM code snippets.' },
1050
+ { match: /roadmap|future|plan|coming|v1|next/i, id: '#roadmap', reply: 'Here\'s the <strong>Roadmap</strong> — v0.3 through v1.0.' },
1051
+ { match: /contact|author|maintainer|who|person/i, id: '#contact', reply: 'Navigating to <strong>Contact</strong> info.' },
1052
+ { match: /home|top|start|begin|back/i, id: '#home', reply: 'Back to the <strong>top</strong>.' },
1053
+ { match: /example|demo|live|chatbot|simple|doc.?viewer/i, href: './examples/', reply: 'Opening the <strong>Live Examples</strong> page.' },
1054
+ { match: /github|source|repo|code/i, href: 'https://github.com/akshayram1/omnibrowser-agent', reply: 'Opening the <strong>GitHub repository</strong>.' },
1055
+ ];
1056
+
1057
+ const FALLBACK_REPLIES = [
1058
+ 'Try asking about <strong>installation</strong>, <strong>architecture</strong>, <strong>embedding</strong>, or <strong>examples</strong>.',
1059
+ 'I can navigate to any section — e.g. "show me docs" or "how does the architecture work?"',
1060
+ 'I know about: Install, What\'s New, Architecture, Embedding, Roadmap, Contact, and Examples.',
1061
+ ];
1062
+ let fallbackIdx = 0;
1063
+
1064
+ /* ── DOM refs ── */
1065
+ const toggleBtn = document.getElementById('chatbot-toggle');
1066
+ const panel = document.getElementById('chatbot-panel');
1067
+ const messagesEl = document.getElementById('cb-messages');
1068
+ const inputEl = document.getElementById('cb-input');
1069
+ const sendBtn = document.getElementById('cb-send');
1070
+ const suggsEl = document.getElementById('cb-suggestions');
1071
+ const modelSelect = document.getElementById('cb-model-select');
1072
+ const loadBtn = document.getElementById('cb-load-btn');
1073
+ const progressWrap = document.getElementById('cb-progress-wrap');
1074
+ const progressFill = document.getElementById('cb-progress-fill');
1075
+ const progressText = document.getElementById('cb-progress-text');
1076
+ const webgpuWarn = document.getElementById('cb-webgpu-warn');
1077
+ const statusPill = document.getElementById('cb-status-pill');
1078
+ const statusDot = document.getElementById('cb-sdot');
1079
+ const statusText = document.getElementById('cb-status-text');
1080
+
1081
+ let isOpen = false;
1082
+ let loadedEngine = null; // WebLLM engine when loaded
1083
+
1084
+ /* ── Model loader ── */
1085
+ loadBtn.addEventListener('click', loadModel);
1086
+
1087
+ function getPlannerTimeoutMs() {
1088
+ const modelId = modelSelect.value;
1089
+ if (/1B/i.test(modelId)) return 18000;
1090
+ if (/3B|Phi-3\.5-mini/i.test(modelId)) return 30000;
1091
+ if (/7B|8B/i.test(modelId)) return 45000;
1092
+ return 30000;
1093
+ }
1094
+
1095
+ function contentToText(content) {
1096
+ if (typeof content === 'string') {
1097
+ return content;
1098
+ }
1099
+ if (Array.isArray(content)) {
1100
+ return content
1101
+ .map(part => {
1102
+ if (typeof part === 'string') return part;
1103
+ if (part && typeof part === 'object' && typeof part.text === 'string') return part.text;
1104
+ return '';
1105
+ })
1106
+ .join('\n');
1107
+ }
1108
+ return '';
1109
+ }
1110
+
1111
+ function extractFirstJsonObject(text) {
1112
+ const start = text.indexOf('{');
1113
+ if (start === -1) return null;
1114
+ let depth = 0;
1115
+ for (let i = start; i < text.length; i += 1) {
1116
+ const ch = text[i];
1117
+ if (ch === '{') depth += 1;
1118
+ if (ch === '}') {
1119
+ depth -= 1;
1120
+ if (depth === 0) return text.slice(start, i + 1);
1121
+ }
1122
+ }
1123
+ return null;
1124
+ }
1125
+
1126
+ async function loadModel() {
1127
+ if (!navigator.gpu) {
1128
+ webgpuWarn.classList.add('visible');
1129
+ setStatus('heuristic', 'idle');
1130
+ return;
1131
+ }
1132
+
1133
+ const modelId = modelSelect.value;
1134
+ loadBtn.disabled = true;
1135
+ modelSelect.disabled = true;
1136
+ progressWrap.classList.add('visible');
1137
+ setStatus('loading', 'loading');
1138
+ addBotMsg(`Loading <strong>${modelSelect.options[modelSelect.selectedIndex].text.split('—')[0].trim()}</strong>… this may take a moment on first run.`);
1139
+
1140
+ try {
1141
+ const webllm = await import('https://esm.run/@mlc-ai/web-llm');
1142
+ loadedEngine = await webllm.CreateMLCEngine(modelId, {
1143
+ initProgressCallback({ progress, text }) {
1144
+ progressFill.style.width = `${Math.round(progress * 100)}%`;
1145
+ progressText.textContent = text || `${Math.round(progress * 100)}%`;
1146
+ }
1147
+ });
1148
+
1149
+ progressText.textContent = 'Warming up first response…';
1150
+ await loadedEngine.chat.completions.create({
1151
+ messages: [{ role: 'user', content: 'Reply with exactly: ready' }],
1152
+ temperature: 0,
1153
+ max_tokens: 4
1154
+ });
1155
+
1156
+ /* Install WebLLM bridge for the library */
1157
+ window.__browserAgentWebLLM = {
1158
+ async plan(input) {
1159
+ const sectionList = NAV.filter(n => n.id).map(n => {
1160
+ const keywords = n.match.source.replace(/\\/g, '').replace(/\|/g, ', ');
1161
+ return `${n.id} (matches: ${keywords})`;
1162
+ }).join('\n');
1163
+ const prompt = [
1164
+ 'You are a docs navigation assistant. Your ONLY job is to pick which section to scroll to.',
1165
+ 'Available sections:',
1166
+ sectionList,
1167
+ '',
1168
+ 'IMPORTANT RULES:',
1169
+ '- You MUST always set "section" to one of the IDs above. NEVER set it to null.',
1170
+ '- If the query mentions install, setup, getting started → use "#docs"',
1171
+ '- If the query mentions home, top, start → use "#home"',
1172
+ '- If unsure, pick the closest match. Do NOT tell the user to "visit" a section — you must navigate them there.',
1173
+ '- Reply with ONLY a JSON object: {"section":"#id","reply":"short confirmation","reasoning":"brief reason"}',
1174
+ '',
1175
+ `User query: ${input.goal}`
1176
+ ].join('\n');
1177
+
1178
+ const resp = await loadedEngine.chat.completions.create({
1179
+ messages: [
1180
+ { role: 'system', content: 'Return only one JSON object. No markdown or extra prose.' },
1181
+ { role: 'user', content: prompt }
1182
+ ],
1183
+ temperature: 0,
1184
+ max_tokens: 160
1185
+ });
1186
+
1187
+ try {
1188
+ const raw = contentToText(resp.choices?.[0]?.message?.content ?? '').trim();
1189
+ const jsonText = extractFirstJsonObject(raw);
1190
+ if (!jsonText) {
1191
+ return { type: 'done', reason: 'Could not parse model response.' };
1192
+ }
1193
+ const parsed = JSON.parse(jsonText);
1194
+ if (parsed.section) {
1195
+ return {
1196
+ type: 'scroll',
1197
+ selector: parsed.section,
1198
+ _reply: parsed.reply,
1199
+ _reasoning: typeof parsed.reasoning === 'string' ? parsed.reasoning : ''
1200
+ };
1201
+ }
1202
+ /* Model didn't return a section — try keyword fallback before giving up */
1203
+ const kwFallback = NAV.find(n => n.match.test(input.goal));
1204
+ if (kwFallback) {
1205
+ return {
1206
+ type: 'scroll',
1207
+ selector: kwFallback.id || null,
1208
+ _reply: kwFallback.reply,
1209
+ _href: kwFallback.href || null,
1210
+ _reasoning: typeof parsed.reasoning === 'string' ? parsed.reasoning : 'Matched via keyword fallback.'
1211
+ };
1212
+ }
1213
+ return {
1214
+ type: 'done',
1215
+ reason: parsed.reply || 'No matching section.',
1216
+ _reasoning: typeof parsed.reasoning === 'string' ? parsed.reasoning : ''
1217
+ };
1218
+ } catch {
1219
+ return { type: 'done', reason: 'Could not parse model response.' };
1220
+ }
1221
+ }
1222
+ };
1223
+
1224
+ progressWrap.classList.remove('visible');
1225
+ loadBtn.textContent = 'Reload';
1226
+ loadBtn.classList.add('loaded');
1227
+ loadBtn.disabled = false;
1228
+ modelSelect.disabled = false;
1229
+ setStatus('ready', 'AI ready');
1230
+ addBotMsg('✅ Model loaded! I can now answer questions about the docs and show a brief <strong>reasoning summary</strong>.');
1231
+ } catch (err) {
1232
+ loadedEngine = null;
1233
+ progressWrap.classList.remove('visible');
1234
+ loadBtn.disabled = false;
1235
+ modelSelect.disabled = false;
1236
+ setStatus('idle', 'heuristic');
1237
+ addBotMsg('⚠️ Model failed to load — falling back to heuristic mode. Check WebGPU support.');
1238
+ }
1239
+ }
1240
+
1241
+ function setStatus(dotClass, label) {
1242
+ statusDot.className = `cb-sdot${dotClass === 'idle' ? '' : ' ' + dotClass}`;
1243
+ statusText.textContent = label;
1244
+ statusPill.className = `cb-status-pill${dotClass === 'ready' ? ' ready' : dotClass === 'loading' ? ' loading' : ''}`;
1245
+ }
1246
+
1247
+ /* ── Toggle ── */
1248
+ toggleBtn.addEventListener('click', () => {
1249
+ isOpen = !isOpen;
1250
+ panel.classList.toggle('visible', isOpen);
1251
+ toggleBtn.classList.toggle('open', isOpen);
1252
+ toggleBtn.textContent = isOpen ? '✕' : '💬';
1253
+ if (isOpen) {
1254
+ if (messagesEl.children.length === 0) {
1255
+ addBotMsg('Hi! I\'m your <strong>docs assistant</strong>. Load an AI model above for smarter answers, or ask me about any section right now.');
1256
+ }
1257
+ inputEl.focus();
1258
+ }
1259
+ });
1260
+
1261
+ /* ── Suggestions ── */
1262
+ suggsEl.querySelectorAll('.cb-chip').forEach(chip => {
1263
+ chip.addEventListener('click', () => handleQuery(chip.dataset.q));
1264
+ });
1265
+
1266
+ /* ── Send ── */
1267
+ sendBtn.addEventListener('click', send);
1268
+ inputEl.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } });
1269
+
1270
+ function send() {
1271
+ const q = inputEl.value.trim();
1272
+ if (!q) return;
1273
+ addUserMsg(q);
1274
+ inputEl.value = '';
1275
+ handleQuery(q);
1276
+ }
1277
+
1278
+ /* ── Greetings — answer instantly, no model needed ── */
1279
+ const GREETINGS = /^(hi|hey|hello|sup|yo|hiya|howdy|what's up|whats up|greetings)[!?.]*$/i;
1280
+ const GREETING_REPLIES = [
1281
+ 'Hey! Ask me about any section — <strong>install</strong>, <strong>architecture</strong>, <strong>embedding</strong>, <strong>examples</strong>…',
1282
+ 'Hi there! What part of the docs can I take you to?',
1283
+ 'Hello! I can navigate to any section. What are you looking for?',
1284
+ ];
1285
+ let greetIdx = 0;
1286
+
1287
+ function withTimeout(promise, timeoutMs = 30000) {
1288
+ let timeoutId;
1289
+ const timeoutPromise = new Promise((_, reject) => {
1290
+ timeoutId = setTimeout(() => reject(new Error('planner_timeout')), timeoutMs);
1291
+ });
1292
+ return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
1293
+ }
1294
+
1295
+ /* ── Route query ── */
1296
+ async function handleQuery(q) {
1297
+ sendBtn.disabled = true;
1298
+ inputEl.disabled = true;
1299
+
1300
+ /* 1. Instant greeting reply — never hits the model */
1301
+ if (GREETINGS.test(q.trim())) {
1302
+ addBotMsg(GREETING_REPLIES[greetIdx % GREETING_REPLIES.length]);
1303
+ greetIdx++;
1304
+ sendBtn.disabled = false;
1305
+ inputEl.disabled = false;
1306
+ inputEl.focus();
1307
+ return;
1308
+ }
1309
+
1310
+ /* 2. AI-first routing — when model is loaded, always route via LLM */
1311
+ if (loadedEngine && window.__browserAgentWebLLM) {
1312
+ setStatus('loading', 'AI thinking');
1313
+ const typingEl = addTyping();
1314
+ try {
1315
+ const result = await withTimeout(window.__browserAgentWebLLM.plan({ goal: q }), getPlannerTimeoutMs());
1316
+ typingEl.remove();
1317
+ if (result._reasoning) addBotThought(result._reasoning);
1318
+ const replyText = result._reply || (result.reason ?? 'Done.');
1319
+ addBotMsg(replyText);
1320
+ if (result._href) {
1321
+ setTimeout(() => window.open(result._href, '_blank', 'noopener'), 400);
1322
+ } else {
1323
+ const targetId = result.selector || null;
1324
+ if (targetId) {
1325
+ const target = document.querySelector(targetId);
1326
+ if (target) setTimeout(() => target.scrollIntoView({ behavior: 'smooth', block: 'start' }), 200);
1327
+ }
1328
+ }
1329
+ } catch (err) {
1330
+ typingEl.remove();
1331
+ const kwFallback = NAV.find(n => n.match.test(q));
1332
+ if (kwFallback) {
1333
+ addBotThought('Using the keyword fallback because the model was slow.');
1334
+ addBotMsg(kwFallback.reply);
1335
+ if (kwFallback.href) {
1336
+ setTimeout(() => window.open(kwFallback.href, '_blank', 'noopener'), 400);
1337
+ } else {
1338
+ const target = document.querySelector(kwFallback.id);
1339
+ if (target) setTimeout(() => target.scrollIntoView({ behavior: 'smooth', block: 'start' }), 200);
1340
+ }
1341
+ } else {
1342
+ const isTimeout = err instanceof Error && err.message === 'planner_timeout';
1343
+ addBotMsg(
1344
+ isTimeout
1345
+ ? 'The AI model is still thinking and hit the current wait limit. Try a more direct query, switch to <strong>Llama 3.2 1B</strong> for speed, or retry after the first prompt warm-up.'
1346
+ : 'The AI model failed to answer this request. Try again, or use a direct query like <strong>install</strong>, <strong>architecture</strong>, <strong>embedding</strong>, or <strong>docs</strong>.'
1347
+ );
1348
+ }
1349
+ } finally {
1350
+ setStatus('ready', 'AI ready');
1351
+ }
1352
+ } else {
1353
+ const kwMatch = NAV.find(n => n.match.test(q));
1354
+ if (kwMatch) {
1355
+ addBotMsg(kwMatch.reply);
1356
+ if (kwMatch.href) {
1357
+ setTimeout(() => window.open(kwMatch.href, '_blank', 'noopener'), 400);
1358
+ } else {
1359
+ const target = document.querySelector(kwMatch.id);
1360
+ if (target) setTimeout(() => target.scrollIntoView({ behavior: 'smooth', block: 'start' }), 200);
1361
+ }
1362
+ } else {
1363
+ addBotMsg(FALLBACK_REPLIES[fallbackIdx % FALLBACK_REPLIES.length]);
1364
+ fallbackIdx++;
1365
+ }
1366
+ }
1367
+
1368
+ sendBtn.disabled = false;
1369
+ inputEl.disabled = false;
1370
+ inputEl.focus();
1371
+ }
1372
+
1373
+ /* ── Helpers ── */
1374
+ function addBotMsg(html) {
1375
+ const msg = document.createElement('div');
1376
+ msg.className = 'cb-msg bot';
1377
+ msg.innerHTML = `<div class="cb-bubble">${html}</div>`;
1378
+ messagesEl.appendChild(msg);
1379
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1380
+ }
1381
+
1382
+ function addBotThought(text) {
1383
+ const msg = document.createElement('div');
1384
+ msg.className = 'cb-msg bot thought';
1385
+ msg.innerHTML = `<div class="cb-bubble">💭 ${text}</div>`;
1386
+ messagesEl.appendChild(msg);
1387
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1388
+ }
1389
+
1390
+ function addUserMsg(text) {
1391
+ const msg = document.createElement('div');
1392
+ msg.className = 'cb-msg user';
1393
+ msg.innerHTML = `<div class="cb-bubble">${text}</div>`;
1394
+ messagesEl.appendChild(msg);
1395
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1396
+ }
1397
+
1398
+ function addTyping() {
1399
+ const el = document.createElement('div');
1400
+ el.className = 'cb-typing';
1401
+ el.innerHTML = `<div class="cb-typing-dots"><div class="cb-tdot"></div><div class="cb-tdot"></div><div class="cb-tdot"></div></div>`;
1402
+ messagesEl.appendChild(el);
1403
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1404
+ return el;
1405
+ }
1406
+ </script>
1407
+
1408
+ <script>
1409
+ function switchSnippet(id) {
1410
+ document.querySelectorAll('.snippet').forEach(s => s.classList.remove('active'));
1411
+ document.querySelectorAll('.stab').forEach(t => t.classList.remove('stab-active'));
1412
+ document.getElementById('sn-' + id).classList.add('active');
1413
+ event.target.classList.add('stab-active');
1414
+ }
1415
+
1416
+ function copyInstall() {
1417
+ const txt = document.getElementById('install-cmd-text').textContent;
1418
+ navigator.clipboard.writeText(txt).then(() => {
1419
+ const lbl = document.getElementById('copy-label');
1420
+ lbl.textContent = 'copied!';
1421
+ setTimeout(() => lbl.textContent = 'copy', 1800);
1422
+ });
1423
+ }
1424
+
1425
+ function switchPlanner(mode) {
1426
+ document.getElementById('pm-h').classList.toggle('active', mode === 'h');
1427
+ document.getElementById('pm-w').classList.toggle('active', mode === 'w');
1428
+ document.querySelectorAll('.ptab').forEach((t, i) => {
1429
+ t.classList.remove('active-h', 'active-w');
1430
+ if (i === 0 && mode === 'h') t.classList.add('active-h');
1431
+ if (i === 1 && mode === 'w') t.classList.add('active-w');
1432
+ });
1433
+ }
1434
+
1435
+ const observer = new IntersectionObserver((entries) => {
1436
+ entries.forEach(e => {
1437
+ if (e.isIntersecting) e.target.style.animationPlayState = 'running';
1438
+ });
1439
+ }, { threshold: 0.08 });
1440
+
1441
+ document.querySelectorAll('.fade-up').forEach(el => {
1442
+ el.style.animationPlayState = 'paused';
1443
+ observer.observe(el);
1444
+ });
1445
+ </script>
1446
+
530
1447
  </body>
531
1448
  </html>