# Website voice guide (SPA + speech-synced highlights)

**Canonical doc** (published at `https://cdn.talktopc.com/docs/site-voice-guide.md`).

For AI agents: also available as MCP tool `website_voice_guide_docs` and resource `talktopc://website-voice-guide`.

Use when building a voice/chat widget on a **marketing website** that navigates pages, highlights on-page answers **in sync with speech**, and optionally fills a contact form.

Trigger phrases: site guide, website agent, highlights, voice tour, navigate the site, SpeaCart-style guide, talktopc.com-style guide.

## Reference implementations

| Layer | Project | Key files |
|-------|---------|-----------|
| Next.js | `speakart_front` | `SpeaCartAgent.tsx`, `speakart-tools.ts` |
| React Router | `smart_terminal_browser` | `SiteGuideWidget.jsx`, `siteGuideTools.js` |
| Agent template | TalkToPC Site Guide | `agent_75f1dd1da` — `export_agent` for prompt + tool shapes |

Widget: `https://cdn.talktopc.com/agent-widget.js`  
MCP playbook: [mcp.md](./mcp.md) (section 10 summary)

---

## Architecture (three layers)

1. **Page markers** — `data-highlight="stable-key"` on every block the agent may discuss
2. **Client tool handlers** — `registerToolHandler(name, fn)` once after `new TTPChatWidget(...)`
3. **Backend client tools + agent prompt** — tool **names must match** handler names exactly

Load the widget **once** in the site shell layout. SPA route changes must **not** destroy or rebuild the widget.

---

## Part A — MCP (agent + tools)

Do in this order. Requires platform MCP (`https://mcp.talktopc.com/mcp`) with a developer `sk_…` key.

### 1. Read this doc

Call `website_voice_guide_docs` or read this file before `create_tool` / `create_agent`.

### 2. Create client tools (`create_tool`)

Use a consistent prefix, e.g. `acme_navigate`, `acme_highlight`, `acme_read_page`.

| Tool suffix | tool_type | wait_for_response | speech_timing | Purpose |
|-------------|-----------|-------------------|---------------|---------|
| `_navigate` | client | **true** (default) | immediate | SPA route change before highlight/form |
| `_read_page` | client | true | immediate | Return headings + `[{key,text}]` highlights + form state |
| `_highlight` | client | **false** | **sync_with_speech** | Pulse-highlight block as agent speaks |
| `_fill_contact_field` | client | true | immediate | Optional — one field per call |
| `_submit_contact` | client | true | immediate | Optional — after explicit user confirm |

**`_highlight` is mandatory for speech-synced highlights.** Example `toolData`:

```json
{
  "name": "acme_highlight",
  "tool_type": "client",
  "wait_for_response": false,
  "speech_timing": "sync_with_speech",
  "description": "Highlight the on-page block that matches the sentence you just spoke. Speak that one sentence first, then call this tool. The highlight is pinned to that spoken line and appears as it is heard. Call once per topic. Prefer a known highlight key. You may pass a short exact snippet. Navigate first if needed (call acme_navigate, then speak and highlight). Fire-and-forget — do not wait for a result. Do not invent text that is not on the page.",
  "parameters": {
    "type": "object",
    "properties": {
      "key": { "type": "string", "description": "Stable id from data-highlight / site map" },
      "text": { "type": "string", "description": "Short exact phrase from the page when no key fits" }
    }
  }
}
```

Do **not** put `page` on the highlight tool — navigation is a separate `_navigate` call so speech sync stays aligned.

### 3. Create agent + attach tools

```
create_agent { config: { name, agentLanguage, model, voiceId, firstMessage, systemPrompt, ... } }
attach_tool × N
patch_agent { agentId, changes: { singleClientToolFlight: false } }
```

Export **TalkToPC Site Guide** (`agent_75f1dd1da`) and mirror its `systemPrompt` structure:

- **HIGHLIGHT EVERYTHING** — every spoken fact with a matching key gets `_highlight`
- Pattern: **speak one sentence → call `_highlight`** (never highlight before speaking)
- **SITE MAP** — every page + highlight key with a one-line summary
- Honesty: only facts from `_read_page`, highlight result, or site map

---

## Part B — Frontend (host site)

MCP creates backend tools + prompt. **The embedding site must implement handlers.**

### DO

- Mount widget once in root layout (not per-page)
- Load script once: `https://cdn.talktopc.com/agent-widget.js`
- `registerToolHandler` immediately after `new TTPChatWidget(...)`
- Navigate with SPA router (`router.push` / `navigate(href)`) in `_navigate` handler
- Mark content with `data-highlight="key"` on all pages (40+ keys typical)
- CSS pulse class for highlights (e.g. `.site-highlight`)
- After SPA navigation while call is active: `widget.maximize(); widget.showVoice();`
- Highlight handler: sync DOM lookup when element exists; `scrollIntoView` + add class

### DO NOT (common bugs)

| Bug | Symptom |
|-----|---------|
| **`widget.updateConfig()` on route change** | Rebuilds DOM; **panel closes mid-call** while audio continues |
| **Destroy/recreate widget on pathname change** | Same — widget resets to closed launcher |
| **Auto-navigate inside `_highlight` handler** | Highlight fires late; desyncs from speech |
| **`_highlight` with `immediate` or `wait_for_response: true`** | Highlight does not ride the spoken sentence |

### Handler sketch

```javascript
widget.registerToolHandler('acme_navigate', async ({ page }) => { /* router.push */ });

widget.registerToolHandler('acme_read_page', async () => ({
  ok: true,
  page, locale,
  headings: [...document.querySelectorAll('h1,h2')].map(el => el.textContent.trim()),
  highlights: [...document.querySelectorAll('[data-highlight]')].map(el => ({
    key: el.getAttribute('data-highlight'),
    text: el.textContent.trim().slice(0, 180)
  }))
}));

widget.registerToolHandler('acme_highlight', async ({ key, text }) => {
  // find [data-highlight=key] or text snippet → add class → scrollIntoView
});
```

Expose `window.__siteDebug = { navigate, readPage, highlight }` for manual testing.

---

## Verify

1. Start voice call on the site
2. Ask agent to explain a section on another page
3. Panel stays open; page navigates; highlight pulses **in sync** with speech
4. Console: `await window.__siteDebug.highlight({ key: 'hero-subtitle' })`

---

## vs Visual Assistant (`visualAssistant`)

Built-in `visualAssistant.allowHighlight` uses generic DOM selectors. **Site guides** use custom `_highlight` + `data-highlight` keys + site map in the prompt for stable, branded tours.

Prefer custom tools for marketing sites; use Visual Assistant for ad-hoc pages without markers.
