# How to Embed JavaScript and HTML in Google AI Edge Gallery Agent Skills

> Learn to embed JavaScript and HTML in Google AI Edge Gallery Agent Skills. Build interactive web interfaces for LLM tool calls using native webviews.

- Repository: [google-ai-edge/gallery](https://github.com/google-ai-edge/gallery)
- Tags: how-to-guide
- Published: 2026-04-06

---

**Agent Skills in the Google AI Edge Gallery are self-contained web applications that expose a global `window['ai_edge_gallery_get_result']` function to bridge LLM tool calls with interactive HTML, CSS, and JavaScript interfaces running inside a native webview.**

Agent Skills in the Google AI Edge Gallery embed JavaScript and HTML to create on-device interactive experiences that run inside a native webview. By implementing a minimal JavaScript bridge and bundling standard web assets, developers can deploy complex interfaces—from audio synthesizers to data visualizations—without writing native code. This architecture keeps all execution local while providing full access to modern browser capabilities.

## The Three-Component Architecture

According to the `google-ai-edge/gallery` source code, every Agent Skill follows a strict three-file bootstrap pattern that separates the **entry point**, the **JavaScript bridge logic**, and the **user interface**.

### The Entry Point (index.html)

The [`index.html`](https://github.com/google-ai-edge/gallery/blob/main/index.html) file serves as a minimal bootloader that loads the JavaScript entry point. It contains no UI elements, only a `script` tag pointing to [`index.js`](https://github.com/google-ai-edge/gallery/blob/main/index.js).

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My Skill</title>
  </head>
  <body>
    <script src="index.js"></script>
  </body>
</html>

```

This file resides in the `scripts/` directory alongside [`index.js`](https://github.com/google-ai-edge/gallery/blob/main/index.js), as seen in [`skills/featured/virtual-piano/scripts/index.html`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/scripts/index.html).

### The Result Provider (index.js)

The [`index.js`](https://github.com/google-ai-edge/gallery/blob/main/index.js) file defines the mandatory global function `window['ai_edge_gallery_get_result']` that the Gallery runtime invokes when the LLM calls the skill's `run_js` tool. This function must return a JSON string containing either a `webview` object with a `url` property or an error message.

```javascript
window['ai_edge_gallery_get_result'] = async (dataStr) => {
  const webviewUrl = `ui.html?v=${Date.now()}`;
  return JSON.stringify({
    webview: { url: webviewUrl },
    result: 'Success. Open the preview card to interact.'
  });
};

```

The implementation in [`skills/featured/virtual-piano/scripts/index.js`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/scripts/index.js) demonstrates appending a timestamp to bust the cache when the skill is re-run.

### The Web UI (ui.html)

The actual interface lives in a separate HTML file—typically [`ui.html`](https://github.com/google-ai-edge/gallery/blob/main/ui.html) or [`webview.html`](https://github.com/google-ai-edge/gallery/blob/main/webview.html)—referenced by the URL returned from the result provider. This file can use any web technology supported by the device's browser engine, including **Web Audio API**, **Canvas**, **WebGL**, and standard **CSS3** layouts.

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Simple Tone</title>
  </head>
  <body>
    <button id="play">Play C4</button>
    <script>
      const audio = new Audio('assets/40.mp3');
      document.getElementById('play').onclick = () => {
        audio.currentTime = 0;
        audio.play();
      };
    </script>
  </body>
</html>

```

This example, based on [`skills/featured/virtual-piano/assets/ui.html`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/assets/ui.html), loads an MP3 from the local `assets/` directory using the Web Audio API.

## Project Structure and Asset Bundling

Agent Skills require specific directory conventions to ensure the Gallery app packages all dependencies. Static assets such as images, audio files, and additional scripts must live in an `assets/` folder referenced with relative paths.

| Component | Purpose | Example Path |
|-----------|---------|--------------|
| [`index.html`](https://github.com/google-ai-edge/gallery/blob/main/index.html) | Bootstrap loader for the JavaScript bridge | [`skills/featured/virtual-piano/scripts/index.html`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/scripts/index.html) |
| [`index.js`](https://github.com/google-ai-edge/gallery/blob/main/index.js) | Implements `ai_edge_gallery_get_result` and returns the UI URL | [`skills/featured/virtual-piano/scripts/index.js`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/scripts/index.js) |
| [`ui.html`](https://github.com/google-ai-edge/gallery/blob/main/ui.html) | Full interactive interface using HTML, CSS, and JavaScript | [`skills/featured/virtual-piano/assets/ui.html`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/assets/ui.html) |
| `assets/` | Directory containing bundled MP3, PNG, or JS dependencies | `skills/featured/virtual-piano/assets/` |
| [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) | Metadata file declaring the skill name and description | [`skills/featured/virtual-piano/SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/SKILL.md) |

The Gallery app performs **no server-side processing** for skill UIs. All code executes on-device, ensuring privacy while maintaining access to the complete web stack.

## Declaring the Skill

Each skill requires a [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) file at the root of its directory to register with the Gallery's discovery system. This file contains YAML frontmatter defining the skill's metadata.

```yaml
---
name: virtual-piano
description: An interactive piano using Web Audio API.
---

```

See [`skills/featured/virtual-piano/SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/SKILL.md) for the production template used by featured skills.

## Summary

- **Agent Skills** are self-contained web applications that run inside the Google AI Edge Gallery's native webview without server-side processing.
- The JavaScript bridge requires implementing `window['ai_edge_gallery_get_result']` in [`index.js`](https://github.com/google-ai-edge/gallery/blob/main/index.js) to return a JSON string with a `webview.url` pointing to the UI.
- The **Web UI** ([`ui.html`](https://github.com/google-ai-edge/gallery/blob/main/ui.html)) can leverage any browser technology, including **Web Audio API**, **Canvas**, and **WebGL**, using assets bundled in the local `assets/` directory.
- File structure must follow the convention: `scripts/` for bootstrap files, `assets/` for static content, and [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) for discovery metadata.

## Frequently Asked Questions

### What JavaScript APIs are available inside an Agent Skill?

Any API supported by the device's WebView engine is available. The `virtual-piano` skill demonstrates the **Web Audio API** for audio playback, while other built-in skills use **Canvas** for rendering. Since the Gallery uses a full browser engine, standards like **WebGL**, **localStorage**, and **Fetch API** (for local resources) work without polyfills.

### Can Agent Skills load external resources from the internet?

No. The architecture is designed for privacy-first, on-device execution. While the WebView supports standard browser capabilities, skills should bundle all assets locally in the `assets/` directory and use relative paths (e.g., `assets/image.png`). The Gallery does not whitelist external domains for skill execution.

### How does the Gallery app communicate with the JavaScript code?

The Gallery runtime invokes the global function `window['ai_edge_gallery_get_result']` when the LLM calls the skill's `run_js` tool. This function receives the LLM's input as a string parameter and must return a JSON string containing either a `webview` object with a `url` property or an error message. This one-way bridge pattern keeps the contract simple and stateless.

### Why does the index.js example append a timestamp to the URL?

The `?v=${Date.now()}` pattern implements cache busting. According to the [`skills/featured/virtual-piano/scripts/index.js`](https://github.com/google-ai-edge/gallery/blob/main/skills/featured/virtual-piano/scripts/index.js) implementation, this ensures that when a user re-runs the skill, the Gallery loads a fresh instance of the UI rather than displaying a cached version from the previous invocation.