Instantly share code, notes, and snippets.
Last active
March 12, 2026 05:39
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save affandhia/2d50d683258e9ef656973133159bead8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>navigator.modelContext — Chrome Built-in AI Context API</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: | |
| system-ui, | |
| -apple-system, | |
| sans-serif; | |
| background: #0f0f0f; | |
| color: #e0e0e0; | |
| min-height: 100vh; | |
| padding: 24px; | |
| } | |
| h1 { | |
| font-size: 1.6rem; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| color: #fff; | |
| } | |
| .subtitle { | |
| font-size: 0.9rem; | |
| color: #888; | |
| margin-bottom: 24px; | |
| } | |
| .card { | |
| background: #1a1a1a; | |
| border: 1px solid #2a2a2a; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-bottom: 16px; | |
| } | |
| .card h2 { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| margin-bottom: 12px; | |
| color: #ccc; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 4px 10px; | |
| border-radius: 999px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .badge-ok { | |
| background: #1a3a1a; | |
| color: #4ade80; | |
| border: 1px solid #166534; | |
| } | |
| .badge-warn { | |
| background: #3a2a00; | |
| color: #fbbf24; | |
| border: 1px solid #92400e; | |
| } | |
| .badge-err { | |
| background: #3a1a1a; | |
| color: #f87171; | |
| border: 1px solid #991b1b; | |
| } | |
| .badge-info { | |
| background: #1a2a3a; | |
| color: #60a5fa; | |
| border: 1px solid #1e3a5f; | |
| } | |
| label { | |
| display: block; | |
| font-size: 0.8rem; | |
| color: #888; | |
| margin-bottom: 6px; | |
| } | |
| textarea, | |
| input[type='text'] { | |
| width: 100%; | |
| background: #111; | |
| border: 1px solid #333; | |
| border-radius: 8px; | |
| color: #e0e0e0; | |
| padding: 10px 12px; | |
| font-size: 0.9rem; | |
| font-family: inherit; | |
| resize: vertical; | |
| outline: none; | |
| } | |
| textarea:focus, | |
| input[type='text']:focus { | |
| border-color: #555; | |
| } | |
| .btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 8px 16px; | |
| border-radius: 8px; | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| border: none; | |
| transition: opacity 0.15s; | |
| } | |
| .btn:disabled { | |
| opacity: 0.4; | |
| cursor: not-allowed; | |
| } | |
| .btn-primary { | |
| background: #3b82f6; | |
| color: #fff; | |
| } | |
| .btn-primary:hover:not(:disabled) { | |
| background: #2563eb; | |
| } | |
| .btn-danger { | |
| background: #ef4444; | |
| color: #fff; | |
| } | |
| .btn-danger:hover:not(:disabled) { | |
| background: #dc2626; | |
| } | |
| .btn-ghost { | |
| background: #2a2a2a; | |
| color: #ccc; | |
| border: 1px solid #333; | |
| } | |
| .btn-ghost:hover:not(:disabled) { | |
| background: #333; | |
| } | |
| .output { | |
| background: #111; | |
| border: 1px solid #222; | |
| border-radius: 8px; | |
| padding: 12px; | |
| font-size: 0.85rem; | |
| line-height: 1.6; | |
| min-height: 60px; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| color: #a0e0a0; | |
| font-family: ui-monospace, monospace; | |
| } | |
| .output.thinking { | |
| color: #fbbf24; | |
| } | |
| .row { | |
| display: flex; | |
| gap: 8px; | |
| align-items: flex-start; | |
| flex-wrap: wrap; | |
| } | |
| .grid2 { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 12px; | |
| } | |
| .info-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 6px 0; | |
| border-bottom: 1px solid #222; | |
| font-size: 0.85rem; | |
| } | |
| .info-row:last-child { | |
| border-bottom: none; | |
| } | |
| .info-label { | |
| color: #888; | |
| } | |
| .info-val { | |
| color: #e0e0e0; | |
| font-family: ui-monospace, monospace; | |
| font-size: 0.8rem; | |
| } | |
| tabs { | |
| display: flex; | |
| gap: 4px; | |
| margin-bottom: 20px; | |
| flex-wrap: wrap; | |
| } | |
| .tab { | |
| padding: 7px 14px; | |
| border-radius: 8px; | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| background: #1a1a1a; | |
| border: 1px solid #2a2a2a; | |
| color: #888; | |
| transition: all 0.15s; | |
| } | |
| .tab.active { | |
| background: #3b82f6; | |
| border-color: #3b82f6; | |
| color: #fff; | |
| } | |
| .tab:hover:not(.active) { | |
| background: #222; | |
| color: #ccc; | |
| } | |
| .panel { | |
| display: none; | |
| } | |
| .panel.active { | |
| display: block; | |
| } | |
| .progress-bar { | |
| height: 4px; | |
| background: #222; | |
| border-radius: 2px; | |
| overflow: hidden; | |
| margin-top: 8px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: #3b82f6; | |
| border-radius: 2px; | |
| transition: width 0.3s; | |
| } | |
| select { | |
| background: #111; | |
| border: 1px solid #333; | |
| border-radius: 8px; | |
| color: #e0e0e0; | |
| padding: 8px 12px; | |
| font-size: 0.875rem; | |
| outline: none; | |
| } | |
| select:focus { | |
| border-color: #555; | |
| } | |
| .token-bar { | |
| height: 8px; | |
| background: #222; | |
| border-radius: 4px; | |
| overflow: hidden; | |
| margin-top: 4px; | |
| } | |
| .token-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #3b82f6, #8b5cf6); | |
| border-radius: 4px; | |
| transition: width 0.4s; | |
| } | |
| .history-msg { | |
| padding: 8px 12px; | |
| border-radius: 8px; | |
| margin-bottom: 8px; | |
| font-size: 0.85rem; | |
| line-height: 1.5; | |
| } | |
| .msg-user { | |
| background: #1e3a5f; | |
| color: #93c5fd; | |
| align-self: flex-end; | |
| } | |
| .msg-assistant { | |
| background: #1a2a1a; | |
| color: #86efac; | |
| } | |
| .msg-system { | |
| background: #2a1a3a; | |
| color: #c4b5fd; | |
| font-style: italic; | |
| font-size: 0.8rem; | |
| } | |
| .chat-history { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| margin-bottom: 12px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🤖 navigator.modelContext</h1> | |
| <p class="subtitle">Chrome 146+ — Provide context & tools to the browser's built-in AI assistant</p> | |
| <div id="apiStatus" class="card"> | |
| <h2>API Status</h2> | |
| <div id="statusContent"><span class="status-badge badge-info">⏳ Checking...</span></div> | |
| <div style="margin-top: 12px; font-size: 0.82rem; color: #888; line-height: 1.6"> | |
| <strong style="color: #ccc">What is this?</strong> <code>navigator.modelContext</code> lets web pages provide | |
| <strong>context</strong> and register <strong>tools</strong> that Chrome's built-in AI assistant (e.g. the AI | |
| sidebar) can use while on your page. It is <em>not</em> the same as <code>LanguageModel</code> — you don't | |
| prompt a model directly here. | |
| </div> | |
| </div> | |
| <tabs> | |
| <div class="tab active" onclick="switchTab('context')">📄 provideContext</div> | |
| <div class="tab" onclick="switchTab('tools')">🔧 registerTool</div> | |
| <div class="tab" onclick="switchTab('combined')">🚀 Combined Demo</div> | |
| <div class="tab" onclick="switchTab('api')">📋 API Explorer</div> | |
| </tabs> | |
| <!-- PROVIDE CONTEXT PANEL --> | |
| <div id="panel-context" class="panel active"> | |
| <div class="card"> | |
| <h2>📄 provideContext()</h2> | |
| <p style="font-size: 0.83rem; color: #888; margin-bottom: 12px"> | |
| Provide structured context to Chrome's AI assistant so it knows what your page is about. The AI sidebar can | |
| use this when the user opens it on your page. | |
| </p> | |
| <label>Page Context (describe what this page is about)</label> | |
| <textarea id="ctxText" rows="3"> | |
| This is a product page for the AeroX Pro headphones. Price: $299. Rating: 4.8/5. Key features: Active noise cancellation, 40h battery, Bluetooth 5.3, foldable design.</textarea | |
| > | |
| <br /> | |
| <div class="row" style="margin-top: 10px"> | |
| <button class="btn btn-primary" onclick="doProvideContext()">📤 provideContext()</button> | |
| <button class="btn btn-danger" onclick="doClearContext()">🗑 clearContext()</button> | |
| </div> | |
| <div class="output" id="ctxOut" style="margin-top: 12px; color: #60a5fa">Status will appear here...</div> | |
| </div> | |
| <div class="card"> | |
| <h2>💡 How it works</h2> | |
| <div style="font-size: 0.83rem; color: #888; line-height: 1.8"> | |
| <div class="info-row"> | |
| <span class="info-label">provideContext(params)</span | |
| ><span class="info-val">Sends context + tools to browser AI</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">params.context</span><span class="info-val">string — describe your page</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">params.tools</span | |
| ><span class="info-val">array — tools AI can call (required)</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">clearContext()</span><span class="info-val">Removes all provided context</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- REGISTER TOOL PANEL --> | |
| <div id="panel-tools" class="panel"> | |
| <div class="card"> | |
| <h2>🔧 registerTool() / unregisterTool()</h2> | |
| <p style="font-size: 0.83rem; color: #888; margin-bottom: 12px"> | |
| Register JavaScript functions as tools the browser AI can invoke. The AI sidebar can call these when it needs | |
| live data from your page. | |
| </p> | |
| <div class="grid2"> | |
| <div> | |
| <label>Tool Name</label> | |
| <input type="text" id="toolName" value="getCurrentPageData" /> | |
| </div> | |
| <div> | |
| <label>Description</label> | |
| <input type="text" id="toolDesc" value="Get live data from this page" /> | |
| </div> | |
| </div> | |
| <br /> | |
| <label>Tool Body (JS — return a JSON string)</label> | |
| <textarea id="toolBody" rows="5"> | |
| function execute(params) { | |
| return JSON.stringify({ | |
| pageTitle: document.title, | |
| url: location.href, | |
| timestamp: new Date().toISOString(), | |
| params: params | |
| }); | |
| }</textarea | |
| > | |
| <br /> | |
| <div class="row" style="margin-top: 10px"> | |
| <button class="btn btn-primary" onclick="doRegisterTool()">➕ registerTool()</button> | |
| <button class="btn btn-danger" onclick="doUnregisterTool()">➖ unregisterTool()</button> | |
| <button class="btn btn-ghost" onclick="registerPresets()">🎁 Register Presets</button> | |
| </div> | |
| <div class="output" id="toolRegOut" style="margin-top: 12px; color: #fbbf24">Status will appear here...</div> | |
| </div> | |
| <div class="card"> | |
| <h2>📋 Registered Tools</h2> | |
| <div id="registeredToolsList" style="font-size: 0.83rem; color: #888">No tools registered yet.</div> | |
| </div> | |
| </div> | |
| <!-- COMBINED DEMO PANEL --> | |
| <div id="panel-combined" class="panel"> | |
| <div class="card"> | |
| <h2>🚀 Full Demo — Context + Tools Together</h2> | |
| <p style="font-size: 0.83rem; color: #888; margin-bottom: 14px"> | |
| This simulates a real-world e-commerce page that provides page context AND registers tools for the browser AI. | |
| Open the Chrome AI sidebar after clicking "Activate" to see it in action. | |
| </p> | |
| <div id="demoStatus" style="font-size: 0.83rem; color: #888; margin-bottom: 12px">Not activated</div> | |
| <div class="row"> | |
| <button class="btn btn-primary" id="btnActivate" onclick="activateDemo()">🚀 Activate Page AI</button> | |
| <button class="btn btn-danger" onclick="deactivateDemo()">⏹ Deactivate</button> | |
| </div> | |
| <br /> | |
| <div class="output" id="demoLog" style="color: #a0e0a0; min-height: 100px"> | |
| Click Activate to set up context and tools... | |
| </div> | |
| </div> | |
| <div class="card" id="demoProduct" style="display: none"> | |
| <h2>🎧 AeroX Pro — Mock Product Page</h2> | |
| <div style="display: grid; grid-template-columns: auto 1fr; gap: 16px; align-items: start"> | |
| <div | |
| style=" | |
| width: 80px; | |
| height: 80px; | |
| background: #2a2a2a; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 2rem; | |
| " | |
| > | |
| 🎧 | |
| </div> | |
| <div> | |
| <div style="font-size: 1.1rem; font-weight: 700; color: #fff">AeroX Pro Headphones</div> | |
| <div style="color: #fbbf24; font-size: 0.9rem">★★★★★ 4.8/5 (2,341 reviews)</div> | |
| <div style="font-size: 1.3rem; font-weight: 700; color: #4ade80; margin-top: 4px">$299.00</div> | |
| <div style="font-size: 0.8rem; color: #888; margin-top: 4px">In stock · Free shipping</div> | |
| </div> | |
| </div> | |
| <div style="margin-top: 12px; font-size: 0.83rem; color: #aaa; line-height: 1.7"> | |
| <strong style="color: #ccc">Features:</strong> Active noise cancellation · 40h battery · Bluetooth 5.3 · | |
| Foldable · USB-C charging · Hi-Res Audio certified | |
| </div> | |
| <div style="margin-top: 10px; font-size: 0.78rem; color: #666"> | |
| 🤖 AI tools registered: getProductDetails, getReviews, checkStock, compareProducts | |
| </div> | |
| </div> | |
| </div> | |
| <!-- API EXPLORER PANEL --> | |
| <div id="panel-api" class="panel"> | |
| <div class="card"> | |
| <h2>📋 Live API Explorer</h2> | |
| <div id="apiExplorer"> | |
| <div class="info-row"> | |
| <span class="info-label">navigator.modelContext</span><span id="xMC" class="info-val">—</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Prototype</span><span id="xProto" class="info-val">—</span> | |
| </div> | |
| <div class="info-row"> | |
| <span class="info-label">Methods</span><span id="xMethods" class="info-val">—</span> | |
| </div> | |
| </div> | |
| <br /> | |
| <label>Run custom JS on navigator.modelContext</label> | |
| <textarea id="customCode" rows="5"> | |
| // Try any of these: | |
| // await navigator.modelContext.provideContext({ tools: [], context: 'hello' }) | |
| // await navigator.modelContext.registerTool({ name: 'test', description: 'test', execute: () => '42' }) | |
| // await navigator.modelContext.clearContext() | |
| await navigator.modelContext.provideContext({ tools: [], context: 'Custom context from explorer' })</textarea | |
| > | |
| <button class="btn btn-primary" style="margin-top: 10px" onclick="runCustomCode()">▶ Run</button> | |
| <div class="output" id="customOut" style="margin-top: 10px; color: #c4b5fd">Result will appear here...</div> | |
| </div> | |
| </div> | |
| <script> | |
| const mc = navigator.modelContext; | |
| const registeredTools = new Set(); | |
| function switchTab(name) { | |
| document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); | |
| document.querySelectorAll('.panel').forEach((p) => p.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| document.getElementById('panel-' + name).classList.add('active'); | |
| } | |
| async function detectAPI() { | |
| const el = document.getElementById('statusContent'); | |
| if (!mc) { | |
| el.innerHTML = '<span class="status-badge badge-err">✗ navigator.modelContext not found</span>'; | |
| return; | |
| } | |
| const proto = Object.getPrototypeOf(mc).constructor.name; | |
| const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(mc)).filter((k) => k !== 'constructor'); | |
| el.innerHTML = | |
| '<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px">' + | |
| '<span class="status-badge badge-ok">✓ navigator.modelContext present</span>' + | |
| '<span class="status-badge badge-info">Prototype: ' + | |
| proto + | |
| '</span></div>' + | |
| '<div class="info-row"><span class="info-label">Methods</span><span class="info-val">' + | |
| methods.join(', ') + | |
| '</span></div>' + | |
| '<div class="info-row"><span class="info-label">Own keys</span><span class="info-val">' + | |
| JSON.stringify(Object.keys(mc)) + | |
| '</span></div>'; | |
| const xMC = document.getElementById('xMC'); | |
| const xProto = document.getElementById('xProto'); | |
| const xMethods = document.getElementById('xMethods'); | |
| if (xMC) xMC.textContent = '[object ModelContext]'; | |
| if (xProto) xProto.textContent = proto; | |
| if (xMethods) xMethods.textContent = methods.join(', '); | |
| } | |
| detectAPI(); | |
| async function doProvideContext() { | |
| const out = document.getElementById('ctxOut'); | |
| const ctx = document.getElementById('ctxText').value.trim(); | |
| out.textContent = 'Calling provideContext()...'; | |
| try { | |
| await mc.provideContext({ context: ctx, tools: [] }); | |
| out.textContent = | |
| '✅ provideContext() succeeded!\n\nContext sent:\n' + | |
| ctx + | |
| '\n\nNow open the Chrome AI sidebar on this page.'; | |
| } catch (e) { | |
| out.textContent = '✗ Error: ' + e.message; | |
| } | |
| } | |
| async function doClearContext() { | |
| const out = document.getElementById('ctxOut'); | |
| try { | |
| await mc.clearContext(); | |
| out.textContent = '✅ clearContext() succeeded!'; | |
| } catch (e) { | |
| out.textContent = '✗ Error: ' + e.message; | |
| } | |
| } | |
| async function doRegisterTool() { | |
| const out = document.getElementById('toolRegOut'); | |
| const name = document.getElementById('toolName').value.trim(); | |
| const desc = document.getElementById('toolDesc').value.trim(); | |
| const body = document.getElementById('toolBody').value.trim(); | |
| if (!name || !desc) { | |
| out.textContent = 'Name and description required.'; | |
| return; | |
| } | |
| let execFn; | |
| try { | |
| execFn = new Function('return ' + body)(); | |
| } catch (e) { | |
| out.textContent = 'JS parse error: ' + e.message; | |
| return; | |
| } | |
| try { | |
| await mc.registerTool({ name, description: desc, execute: execFn }); | |
| registeredTools.add(name); | |
| out.textContent = '✅ registerTool("' + name + '") succeeded!'; | |
| updateToolsList(); | |
| } catch (e) { | |
| out.textContent = '✗ Error: ' + e.message; | |
| } | |
| } | |
| async function doUnregisterTool() { | |
| const out = document.getElementById('toolRegOut'); | |
| const name = document.getElementById('toolName').value.trim(); | |
| try { | |
| await mc.unregisterTool(name); | |
| registeredTools.delete(name); | |
| out.textContent = '✅ unregisterTool("' + name + '") succeeded!'; | |
| updateToolsList(); | |
| } catch (e) { | |
| out.textContent = '✗ Error: ' + e.message; | |
| } | |
| } | |
| async function registerPresets() { | |
| const out = document.getElementById('toolRegOut'); | |
| const presets = [ | |
| { | |
| name: 'getCurrentTime', | |
| description: 'Get the current date and time', | |
| execute: () => JSON.stringify({ time: new Date().toLocaleString(), iso: new Date().toISOString() }), | |
| }, | |
| { | |
| name: 'getPageInfo', | |
| description: 'Get info about the current page', | |
| execute: () => JSON.stringify({ title: document.title, url: location.href }), | |
| }, | |
| { | |
| name: 'getWeather', | |
| description: 'Get mock weather for a location', | |
| execute: (p) => | |
| JSON.stringify({ city: (p || {}).city || 'Unknown', weather: 'Sunny 22C', humidity: '60%' }), | |
| }, | |
| { | |
| name: 'calculate', | |
| description: 'Evaluate a math expression', | |
| execute: (p) => { | |
| try { | |
| return JSON.stringify({ | |
| result: Function( | |
| 'return (' + String((p || {}).expression || '0').replace(/[^0-9+\-*/.() ]/g, '') + ')', | |
| )(), | |
| }); | |
| } catch (e) { | |
| return JSON.stringify({ error: 'invalid' }); | |
| } | |
| }, | |
| }, | |
| ]; | |
| const log = []; | |
| for (const p of presets) { | |
| try { | |
| try { | |
| await mc.unregisterTool(p.name); | |
| } catch (_) {} | |
| await mc.registerTool(p); | |
| registeredTools.add(p.name); | |
| log.push('✅ ' + p.name); | |
| } catch (e) { | |
| log.push('✗ ' + p.name + ': ' + e.message); | |
| } | |
| } | |
| out.textContent = log.join('\n'); | |
| updateToolsList(); | |
| } | |
| function updateToolsList() { | |
| const el = document.getElementById('registeredToolsList'); | |
| if (!registeredTools.size) { | |
| el.textContent = 'No tools registered yet.'; | |
| return; | |
| } | |
| el.innerHTML = [...registeredTools] | |
| .map( | |
| (n) => | |
| '<div class="info-row"><span class="info-label" style="color:#4ade80">✓ ' + | |
| n + | |
| '</span>' + | |
| '<button class="btn btn-ghost" style="padding:3px 10px;font-size:0.75rem" onclick="quickUnregister(\'' + | |
| n + | |
| '\')">Remove</button></div>', | |
| ) | |
| .join(''); | |
| } | |
| async function quickUnregister(name) { | |
| try { | |
| await mc.unregisterTool(name); | |
| registeredTools.delete(name); | |
| updateToolsList(); | |
| } catch (e) {} | |
| } | |
| async function activateDemo() { | |
| const log = document.getElementById('demoLog'); | |
| const status = document.getElementById('demoStatus'); | |
| const btn = document.getElementById('btnActivate'); | |
| btn.disabled = true; | |
| const lines = []; | |
| const add = (t) => { | |
| lines.push(t); | |
| log.textContent = lines.join('\n'); | |
| }; | |
| add('Registering tools...'); | |
| const demoTools = [ | |
| { | |
| name: 'getProductDetails', | |
| description: 'Get details about the AeroX Pro headphones', | |
| execute: () => | |
| JSON.stringify({ | |
| name: 'AeroX Pro', | |
| price: 299, | |
| rating: 4.8, | |
| reviews: 2341, | |
| features: ['ANC', '40h battery', 'Bluetooth 5.3', 'USB-C', 'Foldable'], | |
| inStock: true, | |
| }), | |
| }, | |
| { | |
| name: 'getReviews', | |
| description: 'Get customer reviews for this product', | |
| execute: (p) => | |
| JSON.stringify({ | |
| reviews: [ | |
| { user: 'Alice', rating: 5, text: 'Best headphones I ever owned!' }, | |
| { user: 'Bob', rating: 4, text: 'Great ANC, slightly heavy' }, | |
| { user: 'Carol', rating: 5, text: 'Battery lasts forever' }, | |
| ].slice(0, (p || {}).limit || 3), | |
| }), | |
| }, | |
| { | |
| name: 'checkStock', | |
| description: 'Check stock and shipping info', | |
| execute: (p) => | |
| JSON.stringify({ | |
| color: (p || {}).color || 'black', | |
| inStock: true, | |
| shipsIn: '1-2 days', | |
| freeShipping: true, | |
| }), | |
| }, | |
| { | |
| name: 'compareProducts', | |
| description: 'Compare AeroX Pro with competitors', | |
| execute: () => | |
| JSON.stringify({ | |
| comparison: [ | |
| { name: 'AeroX Pro', price: 299, battery: '40h', anc: true }, | |
| { name: 'SonyXM5', price: 349, battery: '30h', anc: true }, | |
| { name: 'AirPodsMax', price: 549, battery: '20h', anc: true }, | |
| ], | |
| }), | |
| }, | |
| ]; | |
| for (const t of demoTools) { | |
| try { | |
| try { | |
| await mc.unregisterTool(t.name); | |
| } catch (_) {} | |
| await mc.registerTool(t); | |
| registeredTools.add(t.name); | |
| add(' ✅ ' + t.name); | |
| } catch (e) { | |
| add(' ✗ ' + t.name + ': ' + e.message); | |
| } | |
| } | |
| add('\nProviding page context...'); | |
| try { | |
| await mc.provideContext({ | |
| context: | |
| 'This is a product page for AeroX Pro wireless headphones. Price $299. Rating 4.8/5 stars with 2341 reviews. Features: ANC, 40h battery, Bluetooth 5.3, foldable. In stock with free shipping.', | |
| tools: demoTools, | |
| }); | |
| add(' ✅ Context provided!'); | |
| } catch (e) { | |
| add(' ✗ provideContext: ' + e.message); | |
| } | |
| updateToolsList(); | |
| document.getElementById('demoProduct').style.display = 'block'; | |
| status.innerHTML = '<span class="status-badge badge-ok">✅ Active — Open Chrome AI sidebar</span>'; | |
| add('\n\u2389 Done! Open Chrome AI sidebar and ask:'); | |
| add(' • "What are the features of this product?"'); | |
| add(' • "Show me some reviews"'); | |
| add(' • "Compare to Sony XM5"'); | |
| add(' • "Is it in stock?"'); | |
| btn.disabled = false; | |
| } | |
| async function deactivateDemo() { | |
| try { | |
| await mc.clearContext(); | |
| } catch (e) {} | |
| for (const name of [...registeredTools]) { | |
| try { | |
| await mc.unregisterTool(name); | |
| registeredTools.delete(name); | |
| } catch (e) {} | |
| } | |
| document.getElementById('demoProduct').style.display = 'none'; | |
| document.getElementById('demoStatus').textContent = 'Deactivated'; | |
| document.getElementById('demoLog').textContent = '✅ Context cleared and all tools unregistered.'; | |
| updateToolsList(); | |
| } | |
| async function runCustomCode() { | |
| const code = document.getElementById('customCode').value; | |
| const out = document.getElementById('customOut'); | |
| out.textContent = 'Running...'; | |
| try { | |
| const AsyncFn = Object.getPrototypeOf(async function () {}).constructor; | |
| const result = await new AsyncFn('navigator', code)(navigator); | |
| out.textContent = result === undefined ? '✅ Done (undefined)' : '✅ ' + JSON.stringify(result, null, 2); | |
| } catch (e) { | |
| out.textContent = '✗ ' + e.message; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment