---
title: "CDN / Thẻ Script"
description: "Thêm tiện ích giọng nói ThunderPhone vào bất kỳ trang web nào mà không cần bundler"
---

Bản dựng CDN đóng gói React nội bộ, vì vậy bạn có thể sử dụng widget trên trang web tĩnh, WordPress, Webflow hoặc bất kỳ trang nào mà bạn có thể thêm HTML. Không cần npm, bundler hay framework.

## URL CDN

<CodeGroup>
```html Latest (recommended)
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
```

```html Pinned version
<!-- Each stable release also publishes an immutable /widget/vX.Y.Z/ path
     matching the @thunderphone/widget npm version (v1.1.2 at the time of
     writing). If you pin, you own updating the URL for new releases. -->
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/v1.1.2/style.css" />
<script src="https://cdn.thunderphone.com/widget/v1.1.2/widget.js"></script>
```
</CodeGroup>

<Tip>
  Các URL `latest` là những URL mà trình tạo đoạn mã nhúng của dashboard tạo ra. Chúng được lưu vào bộ nhớ đệm trong 5 phút và tự động nhận các bản phát hành ổn định mới. Các đường dẫn `/widget/vX.Y.Z/` được ghim là bất biến và được lưu vào bộ nhớ đệm dài hạn -- nếu ghim, hãy khớp phiên bản với bản phát hành npm `@thunderphone/widget` mà bạn phát triển dựa trên đó.
</Tip>

---

## Cách sử dụng cơ bản

```html
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
</head>
<body>

  <div id="thunderphone"></div>

  <script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
  <script>
    ThunderPhone.mount({
      element: '#thunderphone',
      publishableKey: 'pk_live_your_publishable_key',
    })
  </script>

</body>
</html>
```

Widget được gắn vào phần tử đích và hiển thị dưới dạng **thanh có vị trí cố định ở một góc của khung nhìn** (mặc định là `bottom-right`, được kiểm soát bằng tùy chọn `position`). Phần tử gắn chỉ là gốc React -- vị trí của nó trong trang không ảnh hưởng đến vị trí widget xuất hiện.

---

## Tùy chọn gắn

`ThunderPhone.mount()` chấp nhận các tùy chọn giống thành phần React, cùng với thuộc tính `element`:

| Tùy chọn | Kiểu | Bắt buộc | Mặc định | Mô tả |
|--------|------|----------|---------|-------------|
| `element` | `string \| HTMLElement` | Có | -- | Bộ chọn CSS (ví dụ: `'#thunderphone'`) hoặc tham chiếu phần tử DOM. |
| `publishableKey` | `string` | Có | -- | Khóa API có thể công khai (`pk_live_...`). Tác nhân AI được xác định tự động từ cấu hình widget của khóa. |
| `theme` | `'light' \| 'dark'` | Không | `'light'` | Bảng màu. |
| `primaryColor` | `string` | Không | `'#000000'` (light) / `'#ffffff'` (dark) | Chuỗi màu CSS dùng làm màu nhấn. |
| `title` | `string` | Không | `'Voice assistant'` | Văn bản hiển thị trên thanh widget. |
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | Không | `'bottom-right'` | Vị trí cố định trong khung nhìn. |
| `apiBase` | `string` | Không | `'https://api.thunderphone.com/v1'` | Ghi đè URL cơ sở API. |
| `language` | `string` | Không | -- | Ghi đè ngôn ngữ theo phiên -- mã ngôn ngữ hoặc ngôn ngữ địa phương như `en`, `es` hoặc `fr-FR`. Khi không được đặt, ngôn ngữ đã cấu hình của tác nhân AI sẽ được áp dụng. Yêu cầu `language` trong [ghi đè trình duyệt](/vi/guides/embed-web-widget) của khóa. |
| `voice` | `string` | Không | -- | Ghi đè giọng nói theo phiên -- tên giọng nói như `maria`. Khi không được đặt, giọng nói đã cấu hình của tác nhân AI sẽ được áp dụng. Yêu cầu `voice` trong [ghi đè trình duyệt](/vi/guides/embed-web-widget) của khóa. |
| `context` | `string` | Không | -- | Ngữ cảnh thực tế của trang hoặc trang web theo phiên được truyền cho tác nhân AI. Bị cắt ngắn ở phía máy chủ còn 12.000 ký tự. Yêu cầu `context` trong [ghi đè trình duyệt](/vi/guides/embed-web-widget) của khóa. |
| `onConnect` | `() => void` | Không | -- | Được gọi khi phiên giọng nói kết nối. |
| `onDisconnect` | `() => void` | Không | -- | Được gọi khi phiên kết thúc. |
| `onError` | `(error) => void` | Không | -- | Được gọi khi xảy ra lỗi. Lỗi có các trường `error` (mã) và `message`. |
| `ringtone` | `boolean \| string` | Không | `false` | Phát nhạc chuông trong khi kết nối. `true` cho nhạc chuông mặc định hoặc chuỗi URL cho âm thanh tùy chỉnh. |

---

## Dọn dẹp

`ThunderPhone.mount()` trả về một thực thể widget có các phương thức dọn dẹp:

```html
<script>
  var widget = ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
  })

  // Later, when you want to remove the widget:
  widget.unmount()

  // Or equivalently:
  widget.destroy()
</script>
```

Cả `unmount()` và `destroy()` đều thực hiện cùng một việc -- ngắt kết nối mọi phiên thoại đang hoạt động và xóa widget khỏi DOM. Sử dụng cách nào dễ đọc hơn trong mã của bạn.

<Note>
  Luôn dọn dẹp widget khi điều hướng đi trong ứng dụng một trang hoặc khi phần tử chứa bị xóa. Điều này đảm bảo các phiên thoại đang hoạt động được ngắt kết nối đúng cách.
</Note>

---

## Ví dụ

### Giao diện tối với màu tùy chỉnh

```html
<div id="thunderphone"></div>

<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    theme: 'dark',
    primaryColor: '#8b5cf6',
    title: 'Talk to our AI',
  })
</script>
```

### Vị trí tùy chỉnh

```html
<div id="thunderphone"></div>

<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    position: 'bottom-left',
  })
</script>
```

### Với callback sự kiện

```html
<div id="thunderphone"></div>

<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    onConnect: function () {
      console.log('Call started')
    },
    onDisconnect: function () {
      console.log('Call ended')
    },
    onError: function (error) {
      console.error('Widget error:', error.error, error.message)
    },
  })
</script>
```

### Với nhạc chuông

```html
<div id="thunderphone"></div>

<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    ringtone: true, // or a custom URL: 'https://example.com/ringtone.mp3'
  })
</script>
```

### Sử dụng tham chiếu phần tử DOM

```html
<div id="thunderphone"></div>

<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  var container = document.getElementById('thunderphone')

  ThunderPhone.mount({
    element: container,
    publishableKey: 'pk_live_your_publishable_key',
  })
</script>
```

### Tích hợp WordPress / CMS

Thêm nội dung này vào khối HTML tùy chỉnh hoặc chân trang của giao diện:

```html
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>

<div id="thunderphone-widget"></div>
<script>
  ThunderPhone.mount({
    element: '#thunderphone-widget',
    publishableKey: 'pk_live_your_publishable_key',
  })
</script>
```

<Tip>
  `<div>` có thể đặt ở bất kỳ đâu -- đây chỉ là điểm gắn. Bản thân widget hiển thị dưới dạng lớp phủ cố định ở một góc khung nhìn (được thiết lập bằng tùy chọn `position`) và không nằm trong luồng nội dung xung quanh.
</Tip>

---

## Khắc phục sự cố

<AccordionGroup>
  <Accordion title="Widget không xuất hiện">
    Đảm bảo cả tệp CSS và JS đều đã được tải. Kiểm tra console của trình duyệt để tìm lỗi mạng. Xác minh phần tử đích tồn tại trong DOM trước khi gọi `ThunderPhone.mount()`.
  </Accordion>

  <Accordion title="ThunderPhone chưa được định nghĩa">
    Script chưa được tải. Đảm bảo thẻ `<script>` cho `widget.js` xuất hiện trước lệnh gọi mount, hoặc bọc lệnh gọi mount trong trình lắng nghe `DOMContentLoaded`.
  </Accordion>

  <Accordion title="Lỗi miền không được phép">
    Thêm miền của bạn vào danh sách miền được phép của widget trên trang **Widget web** tại [app.thunderphone.com](https://app.thunderphone.com). `localhost` không tự động được phép trong môi trường production; hãy thêm vào danh sách để kiểm thử cục bộ.
  </Accordion>
</AccordionGroup>

---

## Bước tiếp theo

<CardGroup cols={2}>
  <Card title="Tạo kiểu" icon="palette" href="/vi/widget/styling">
    Tùy chỉnh giao diện widget bằng các thuộc tính tùy chỉnh CSS.
  </Card>
  <Card title="Thành phần React" icon="react" href="/vi/widget/react">
    Bạn đang dùng React? Việc tích hợp thành phần đơn giản hơn.
  </Card>
</CardGroup>
