---
title: "React 组件"
description: "在 React 应用中嵌入 ThunderPhone 语音小组件"
---

`ThunderPhoneWidget` 组件会渲染一个具有玻璃拟态风格的通话栏，内置静音、结束通话和显示连接状态的控件。这是在 React 应用中添加语音 AI 的最快方式。

## 安装

```bash
npm install @thunderphone/widget
```

## 基本用法

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
    />
  )
}
```

<Warning>
  您**必须**导入该小组件的 CSS 文件，才能正确渲染。否则，小组件将不会应用样式。
</Warning>

---

## 属性

该组件通过 `ThunderPhoneWidgetProps` 接受以下属性：

| 属性 | 类型 | 必填 | 默认值 | 说明 |
|------|------|----------|---------|-------------|
| `publishableKey` | `string` | 是 | -- | 控制台中 **Web 小部件** 提供的可发布 API 密钥（`pk_live_...`）。系统会根据该密钥的小部件配置自动确定智能体。 |
| `theme` | `'light' \| 'dark'` | 否 | `'light'` | 配色方案。向小部件根元素应用 `tp--light` 或 `tp--dark` 类。 |
| `primaryColor` | `string` | 否 | `'#000000'`（浅色）/ `'#ffffff'`（深色） | 用作强调色的 CSS 颜色字符串（通话按钮、波形图、活动指示器）。 |
| `title` | `string` | 否 | `'Voice assistant'` | 显示在小部件栏中的文本。 |
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | 否 | `'bottom-right'` | 小部件在视口中的固定位置。 |
| `apiBase` | `string` | 否 | `'https://api.thunderphone.com/v1'` | 覆盖 API 基础 URL。 |
| `language` | `string` | 否 | -- | 按会话覆盖语言——语言代码或区域设置，例如 `en`、`es` 或 `fr-FR`。未设置时，将使用智能体配置的语言。需要在该密钥的 [浏览器覆盖](/zh/guides/embed-web-widget) 下配置 `language`。 |
| `voice` | `string` | 否 | -- | 按会话覆盖语音——语音名称，例如 `maria`。未设置时，将使用智能体配置的语音。需要在该密钥的 [浏览器覆盖](/zh/guides/embed-web-widget) 下配置 `voice`。 |
| `context` | `string` | 否 | -- | 按会话传递给智能体的事实性页面或网站上下文（例如，访客正在查看的页面详情）。服务端会截断至 12,000 个字符。需要在该密钥的 [浏览器覆盖](/zh/guides/embed-web-widget) 下配置 `context`。 |
| `onConnect` | `() => void` | 否 | -- | 语音会话成功连接时调用。 |
| `onDisconnect` | `() => void` | 否 | -- | 会话结束时调用。 |
| `onError` | `(error) => void` | 否 | -- | 发生错误时调用。`error` 对象包含 `error`（代码）和 `message` 字段。 |
| `className` | `string` | 否 | -- | 应用于小部件容器的额外 CSS 类名。 |
| `ringtone` | `boolean \| string` | 否 | `false` | 连接期间播放铃声。`true` 使用默认铃声，或使用 URL 字符串指定自定义音频。 |

---

## 示例

### 深色主题与自定义颜色

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      theme="dark"
      primaryColor="#8b5cf6"
      title="Talk to our AI"
    />
  )
}
```

### 自定义位置

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      position="bottom-left"
    />
  )
}
```

### 按会话设置语言、语音和上下文

当通话开始时，`language`、`voice` 和 `context` 属性会转发至会话请求（`POST /widget/session`），覆盖该会话中智能体已配置的默认值。在控制台中为该小组件的 **浏览器覆盖项** 下启用您使用的每个属性（或在密钥的 `allowed_overrides` 中启用）。新建小组件默认不接受任何覆盖项；携带未启用覆盖项的会话请求会因 `400 override_not_allowed` 而失败。请参阅[浏览器覆盖项](/zh/guides/embed-web-widget)。

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function PricingPageWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      language="es"
      voice="maria"
      context="Page: Pricing. Plans: Starter $29/mo, Pro $99/mo. Annual billing saves 20%."
    />
  )
}
```

使用 `context` 向智能体提供访客当前所在页面的事实信息——产品详情、定价或页面专属常见问题。智能体会将其视为页面内容，而非指令。服务端会将其截断为最多 12,000 个字符。

### 使用事件回调

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function SupportWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      onConnect={() => {
        console.log('Voice session connected')
        analytics.track('widget_call_started')
      }}
      onDisconnect={() => {
        console.log('Voice session ended')
        analytics.track('widget_call_ended')
      }}
      onError={(error) => {
        console.error(`Widget error: ${error.error} - ${error.message}`)
      }}
    />
  )
}
```

### 使用自定义样式

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function BrandedWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      primaryColor="#4a90d9"
      className="my-custom-widget"
    />
  )
}
```

```css
.my-custom-widget .tp-button--end {
  background-color: #e74c3c;
}
```

有关所有可用 CSS 类和自定义属性，请参阅[样式指南](/zh/widget/styling)。

### 使用铃声

在建立连接期间播放电话铃声：

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function PhoneWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      ringtone={true}
    />
  )
}
```

传入音频文件 URL 以使用自定义铃声：

```tsx
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  ringtone="https://example.com/my-ringtone.mp3"
/>
```

当组件处于 `connecting` 状态时，铃声会循环播放；智能体接通后会平滑淡出。

### 使用自定义 API 基础地址

<Tip>
  仅当您使用自行托管或代理 API 端点时，才需要设置 `apiBase`。默认值指向 `https://api.thunderphone.com/v1`。
</Tip>

```tsx
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  apiBase="https://your-proxy.example.com/v1"
/>
```

---

## 错误处理

触发 `onError` 回调时，它会接收一个包含两个字段的错误对象：

| 字段 | 类型 | 描述 |
|-------|------|-------------|
| `error` | `string` | 机器可读的错误代码 |
| `message` | `string` | 人类可读的错误描述 |

常见错误代码包括不允许的域名、未找到智能体以及无效的 API 密钥。

---

## 后续步骤

<CardGroup cols={2}>
  <Card title="无头 Hook" icon="code" href="/zh/widget/headless-hook">
    需要完全掌控 UI？请改用 `useThunderPhone` Hook。
  </Card>
  <Card title="样式设置" icon="palette" href="/zh/widget/styling">
    使用 CSS 自定义属性自定义颜色、尺寸和布局。
  </Card>
</CardGroup>
