# @boldvideo/bold-js 中文文档

规范地址：https://npmcn.com/package/%40boldvideo/bold-js/

版本：2\.5\.0
npm 元数据更新时间：2026-10-24T07:29:12.477Z
中文译文更新时间：2026-11-08T10:23:37.660Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40boldvideo/bold-js
原文对照：https://npmcn.com/package/%40boldvideo/bold-js/?lang=original
项目主页：https://github.com/boldvideo/bold-js#readme
源码仓库：https://github.com/boldvideo/bold-js

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：336 kB，8 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=20\.0\.0
- 运行依赖：1 个
- 首次发布：2023-03-05T05:45:34.995Z
- 最新版本发布：2026-10-24T07:29:12.113Z
- 近 30 天下载量：2663（2026-10-09 至 2026-11-07，npm 官方下载统计）
- 已发布版本数：73（近 12 个月 52 个）

## 中文简介

[![Bold Logo](https://boldvideo.com/bold-js-github-header.svg)](https://boldvideo.com/?utm_source=github.com&utm_medium=readme&utm_campaign=bold-js)

# @ boldvid (韩语)

## 安装

```sh
npm install @boldvideo/bold-js
```

也可以使用：`pnpm add @boldvideo/bold-js`、`yarn add @boldvideo/bold-js`、`bun add @boldvideo/bold-js`

## README

[![Bold Logo](https://boldvideo.com/bold-js-github-header.svg)](https://boldvideo.com/?utm_source=github.com&utm_medium=readme&utm_campaign=bold-js)

# @boldvideo/ bold-js (英语)

用于交互的 JavaScript SDK [粗体 API](https://boldvideo.com/?utm_source=github.com&utm_medium=readme&utm_campaign=bold-js)给自己的商业视频平台提供动力.

 [![Bold JS](https://img.shields.io/npm/v/@boldvideo/bold-js/latest.svg?style=flat-square)](https://npmjs.com/package/@boldvideo/bold-js)[![npm](https://img.shields.io/npm/dt/@boldvideo/bold-js.svg?style=flat-square)](https://npmjs.com/package/@boldvideo/bold-js)

 [![Follow @veryboldvideo](https://img.shields.io/badge/Follow-%40veryboldvideo-09b3af?style=appveyor&logo=twitter)](https://twitter.com/intent/follow?screen_name=veryboldvideo)[![Try Bold Video](https://img.shields.io/badge/Try%20Bold-Free-09b3af?style=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAmCAYAAADTGStiAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGFSURBVHgBxZg9SwNBEIZ34xUpVLCwEQQRtRARxV+g4M8QLO0sBPtgZS129gr+AbEWWyshOUSCkipBjB8cBHPrM4GVQ84qZuaFJTebj+feyczu3fmxEIIbXjnjjZEy7hm3feeunfdPf33B/xO4TBk/fMoZHXMCHU1wVBP3m8Cb2mDRI/AN4K9xouJ0NA9ovzih5Vj0jutZXHcl0HIsmkicW4uBJtiR2kUr8KQJGPVMwJ62sgJ//hxrtROQNsvnDO30JbGaY9xeROggVnLcY/FYAPwcJ7Qc7xahKmAAe33vz0vmRysK6rASQs2FUC3Oq1U1xZVSWVukvCWxWlXjbgnYFc6nVMEiXK+wQx0MjhX346gPWmtOe5MQjQPdsQBLylctUi3gholjnE6bgFHVCpxZgR+s/uOGVTvdWLTTCyvXurpj3J7IfbOqY0BpLrcx3mea22Id6LZAJdYA56T3COhy8dFE4kYkHN7xcgnwDGD79/sJH6i54SQ1ItfLXZx1GC2CehmsqG96m37o1gSKagAAAABJRU5ErkJggg==)](https://https//app.boldvideo.io/register?utm_source=github.com&utm_medium=readme&utm_campaign=bold-js)

* * *

## 安装

```bash
npm install @boldvideo/bold-js
```

## 快速启动

```typescript
import { createClient } from '@boldvideo/bold-js';

const bold = createClient('your-api-key');

// Fetch videos
const videos = await bold.videos.list();

// AI-powered recommendations
const recs = await bold.ai.recommendations({ 
  topics: ['sales', 'negotiation'],
  stream: false 
});
console.log(recs.guidance);
```

* * *

## API 参考

### 视频

```typescript
// List latest videos (default: 12)
const videos = await bold.videos.list();

// With limit (backwards compatible)
const videos = await bold.videos.list(20);

// With filters
const videos = await bold.videos.list({ 
  limit: 20,
  tag: 'sales',
  collectionId: 'col_123',
  viewerId: 'viewer_123'  // Include watch progress
});

// Paginated index (uses /videos endpoint)
const videos = await bold.videos.list({ 
  page: 2,
  tag: 'sales',
  collectionId: 'col_123'
});

// Get a single video by ID or slug
const video = await bold.videos.get('video-id');
const videoBySlug = await bold.videos.get('my-video-slug');

// Search videos
const results = await bold.videos.search('pricing strategies');
```

### 安全播放

视频 `playbackPolicy: 'signed'` 需要一个播放符号。 赤马流 和图像 URL 为这些视频返回 403 。 使用独立的助手代替 手建 Mux URL 时:

```typescript
import { createClient, streamUrl, muxPlayerProps, thumbnailUrl } from '@boldvideo/bold-js';

const bold = createClient('YOUR_API_KEY'); // Keep your tenant API key server-side.
const { data: video } = await bold.videos.get('video-id');
const src = streamUrl(video); // For an HLS player.
const playerProps = muxPlayerProps(video); // Spread into <MuxPlayer {...playerProps} />.
const poster = thumbnailUrl(video, { width: 640, height: 360, time: 0, fitMode: 'crop' });
```

`streamUrl` 保存 API 的 URL, 返回 Mux HLS URL, 并附带任何已提供的内容 `playbackToken`。 。 。 . `muxPlayerProps` 返回刚刚 `{ playbackId }` 公共录像; 已签名的视频还可获得回放/音标和明确的海报 从 `thumbnail`。 。 。 . `thumbnailUrl` 为公共视频构建 Mux 图像 URL 。 签名 视频返回存储的公众 `thumbnail` 不变 **忽略所有选项**数字 : 无法使用查询参数大小或查找已签名的 Mux 图像 .

帮助者接受 `Video`、引用、搜索来源、建议或自己的 带有所需骆驼Case字段的对象(`PlaybackSource`(a) 载有下列资料的大赦国际消息来源: 即使没有政策,标志也被视为签名;明确的公共政策需要 玩家道具和缩略图优先。 URL 帮助返回 `undefined` 当 缺少所需的 URL、 播放 ID 或签名缩略图; 玩家道具省略 字段。 它们的格式是提供的数据,而不是验证证书:没有签名的视频 符号不能通过构建的赤网址播放。 对于一个没有缩略图或 故事板符号,重排完整视频以获取可用的字段.

最后一个 **12小时**。 。 。 . `Video.playbackTokenExpiresAt` 是可选的无效 ISO8601 倍贴. 重新调试视频 `bold.videos.get()` 以新的迹象发誓; 没有刷新端点或自动刷新定时器。 脚本字段无效 公共视频。 不登录标志或标志化的 URL .

### 播放列表

```typescript
// List all playlists
const playlists = await bold.playlists.list();

// Get a single playlist with videos
const playlist = await bold.playlists.get('playlist-id');
```

### 设置

```typescript
// Fetch channel settings, menus, and featured playlists
const settings = await bold.settings();

// Access portal hero configuration
if (settings.portal.hero.type === 'custom') {
  // Render custom hero section
}

// Menu items with external link handling
settings.menuItems.forEach(item => {
  // item.blank: true opens in a new window/tab
  // item.isExt: true indicates an external URL
  // item.icon: optional icon path (can be null)
});
```

* * *

## 查看器 API

管理外部用户并跟踪其视频监视进度. 理想的课程平台 整合与粗体视频.

### 查看器管理

```typescript
// Create a viewer (e.g., when user signs up)
const { data: viewer } = await bold.viewers.create({
  name: 'John Doe',
  externalId: 'user_123',  // Your platform's user ID
  email: 'john@example.com',
  traits: { plan: 'pro', company_name: 'Acme Inc' }
});

// Find viewer by external ID (common for syncing users)
const { data: viewer } = await bold.viewers.lookup({ externalId: 'user_123' });

// Or find by email
const { data: viewer } = await bold.viewers.lookup({ email: 'john@example.com' });

// Update viewer
await bold.viewers.update(viewer.id, { 
  traits: { plan: 'enterprise' }  // Note: traits are replaced, not merged
});

// List all viewers
const { data: viewers } = await bold.viewers.list();
```

### 进展跟踪

```typescript
// Save progress as video plays (call every 5-10 seconds)
await bold.viewers.saveProgress(viewerId, videoId, {
  currentTime: 120,  // seconds
  duration: 600      // total video duration
});

// Mark video complete by setting currentTime = duration
await bold.viewers.saveProgress(viewerId, videoId, {
  currentTime: 600,
  duration: 600
});

// Get progress for a specific video
const { data: progress } = await bold.viewers.getProgress(viewerId, videoId);
console.log(`${progress.percentage}% complete`);

// List all progress for a viewer (e.g., for a course dashboard)
const { data: progress, meta } = await bold.viewers.listProgress(viewerId, {
  collectionId: 'course-collection-id',  // Filter to a course
  completed: false  // Only in-progress videos
});
console.log(`Completed ${meta.completed} of ${meta.total} videos`);
```

* * *

## 通知首选项

阅读或部分更新查看器的电子邮件并按下通知频道 租户 API 键客户端 :

```typescript
const preferences = await bold.notifications.getPreferences('viewer-uuid');
console.log(preferences.channels.email, preferences.channels.push);

// Only push is changed; the existing email preference is preserved.
await bold.notifications.updatePreferences('viewer-uuid', {
  channels: { push: true }
});
```

* * *

## 会话管理

使用 BOLD 会话管理作为无头密码共享预防层. 您的应用程序仍然拥有登录,会员,产品访问,以及员工/管理人员检查. 只有在您的应用决定了查看者可以访问保护后才能拨打 BOLD 内容.

### 运行时会话

运行时会话呼叫使用客户的上游JWT,而不是BOLD API键. 这个 当上游JWT已经存在时,工厂对于浏览器/本地运行时间代码是安全的 该运行时间.

```typescript
import { createAuthClient } from '@boldvideo/bold-js';

const auth = createAuthClient({
  tenantSlug: 'hrtu',
  upstreamJwt: outsetaJwt
});

const result = await auth.sessions.create({
  deviceId: browserDeviceId,
  platform: 'web',
  userAgent: navigator.userAgent
});

let sessionId: string | undefined;

if ('challengeRequired' in result) {
  // Show an email-code challenge UI, then verify the code.
  const verified = await auth.challenges.verify(result.challengeId, '123456');
  sessionId = verified.sessionId ?? undefined;
} else if (result.sessionId) {
  sessionId = result.sessionId;
}

if (sessionId) {
  localStorage.setItem('bold_session_id', sessionId);
}

if (sessionId) {
  // Register the session for push notifications.
  await auth.notifications.registerDevice(sessionId, {
    provider: 'expo', // 'expo' | 'fcm' | 'apns'
    token: expoPushToken
  });

  const verification = await auth.sessions.verify(sessionId);
  if (!verification.valid) {
    // session_not_found, session_revoked, or session_expired
  }
}
```

对于刷新过的JWT,通过每个调用:

```typescript
await auth.sessions.verify(sessionId, { upstreamJwt: freshJwt });
```

观众自我管理方法需要政策支持:

```typescript
const { data: sessions } = await auth.sessions.list();
await auth.sessions.revoke(sessionId);
await auth.sessions.revokeOthers(currentSessionId);
```

当账户没有会话管理时, 设备注册 返回已输入错误 :

```typescript
import { SessionManagementUnavailableError } from '@boldvideo/bold-js';

try {
  await auth.notifications.registerDevice(sessionId, {
    provider: 'fcm',
    token: fcmToken
  });
} catch (error) {
  if (error instanceof SessionManagementUnavailableError) {
    console.log(error.status, error.code, error.retryable);
  }
}
```

### 服务器系统会话管理

客户服务器可以检查 BOLD 设备状态,撤销会话,并管理 每查看器设备限制 超标或豁免 上游外部ID使用 普通 BOLD 租户 API 密钥。 保留此 API 密钥服务器侧 。 别放 浏览器代码中的 BOLD 租户密钥或管理密钥 .

```typescript
import { createClient } from '@boldvideo/bold-js';

const bold = createClient(process.env.BOLD_TENANT_API_KEY!);

const { data: state } =
  await bold.sessionManagement.getViewerSessionManagementStateByExternalId(
    outsetaPersonUid
  );

if (state.canUpdateDeviceLimitOverride) {
  await bold.sessionManagement.setViewerDeviceLimitOverrideByExternalId(
    outsetaPersonUid,
    5
  );

  await bold.sessionManagement.clearViewerDeviceLimitOverrideByExternalId(
    outsetaPersonUid
  );
}

if (state.canUpdateSessionManagementExemption) {
  await bold.sessionManagement.setViewerSessionManagementExemptionByExternalId(
    outsetaPersonUid,
    true
  );
}

const { data: sessions } =
  await bold.sessionManagement.listViewerSessionsByExternalId(outsetaPersonUid);

for (const session of sessions) {
  if (session.travelVerdict === 'impossible_travel') {
    // Bold flagged this sign-in as physically implausible.
  }
}

await bold.sessionManagement.revokeAllViewerSessionsByExternalId(
  outsetaPersonUid,
  { reason: 'outseta:membership_changed' }
);
```

`setViewerDeviceLimitOverrideByExternalId` 需要一个正的安全整数. 使用 `clearViewerDeviceLimitOverrideByExternalId` 明确清除.

每届会议的项目都包含 `travelVerdict: 'impossible_travel' | null` - 粗体货 权威的不可能旅行信号,由这些管理员范围显示 届会管理方法。 这是一个 **仅标签**: SDK 永远不会返回 坐标,生IP,或导出距离/速度. 没错 `null` 当 签入时没有标出标记(以及字段前的后端).

服务器侧会话管理方法无法更新 JWT 配置, 账户 特性标记、会话 TTL 或电子邮件模板设置。 这些仍然是BOLD操作员 控制系统.

* * *

## 社区 API

以文章、评论和反应建立社区特色。 所有写操作都需要一个 `viewerId` (表演动作的观众).

### 员额

```typescript
// List posts (optionally filter by category)
const { data: posts } = await bold.community.posts.list({ 
  category: 'announcements',
  limit: 20,
  offset: 0,
  viewerId: 'viewer-uuid'  // Include viewerReacted in response
});

// Get a single post with comments
const { data: post } = await bold.community.posts.get('post-id', 'viewer-uuid');

// Create a post and notify viewers by their customer external IDs.
const { data: newPost, mentions } = await bold.community.posts.create('viewer-uuid', {
  content: 'Hello community! **Markdown** supported.',
  category: 'general',
  mentions: ['customer-user-123']
});
console.log(mentions?.skipped); // malformed or unresolved external IDs

// Update a post (owner or admin only)
await bold.community.posts.update('viewer-uuid', 'post-id', {
  content: 'Updated content'
});

// Delete a post (owner or admin only)
await bold.community.posts.delete('viewer-uuid', 'post-id');

// Pin or unpin a post (server only). Use a community moderation key: a
// single-purpose key that can pin and unpin posts and nothing else, so it is
// safe to keep in your server's environment. Ask Bold support to issue one.
// Check that the acting member may moderate first; authorship is not enough.
const admin = createClient(process.env.BOLD_API_KEY, {
  adminApiKey: process.env.BOLD_COMMUNITY_MODERATION_KEY,
});
const { data: pinned } = await admin.community.posts.setPinned('post-id', true);
console.log(pinned.pinned, pinned.pinnedAt); // re-pinning keeps the original pinnedAt
await admin.community.posts.setPinned('post-id', false);

// React to a post (toggle like/unlike)
const reaction = await bold.community.posts.react('viewer-uuid', 'post-id');
console.log(reaction.reacted, reaction.reactionsCount);
```

### 评论

```typescript
// Create a comment on a post
const { data: comment } = await bold.community.comments.create(
  'viewer-uuid',
  'post-id',
  { content: 'Great post!', mentions: ['customer-user-456'] }
);

// Reply to a comment (nested)
const { data: reply } = await bold.community.comments.create(
  'viewer-uuid',
  'post-id',
  { content: 'I agree!', parentId: 'parent-comment-id' }
);

// Delete a comment (owner or admin only)
await bold.community.comments.delete('viewer-uuid', 'comment-id');

// React to a comment (toggle)
const reaction = await bold.community.comments.react('viewer-uuid', 'comment-id');

// Read nested reaction state on comments/replies (viewerId enables viewerHasReacted)
const { data: post } = await bold.community.posts.get('post-id', 'viewer-uuid');
for (const thread of post.comments.items ?? []) {
  console.log(thread.createdAt, thread.reactions.count, thread.reactions.viewerHasReacted);
}
```

### 引用收件箱

```typescript
const { data: mentionItems, meta } = await bold.community.mentions.list(
  'viewer-uuid',
  { page: 1, pageSize: 20 }
);

const { data: unread } =
  await bold.community.mentions.unreadCount('viewer-uuid');

await bold.community.mentions.markRead('viewer-uuid', {
  ids: mentionItems.map((mention) => mention.id)
});

// Or mark every unread mention as read.
await bold.community.mentions.markRead('viewer-uuid', { all: true });
```

可选 `mentions` 只有在创建员额/评论时才返回结果 请求包括: `mentions` 阵列。 提供最多25个粗体工艺 外部 ID ; 格式不正确、 未解决、 或顶级以外的 ID 出现在 `skipped`, (中文(简体) ), 重复和自我记忆则默默地被省略。 从未跳过的身份 失败内容创建 .

提及 `excerpt` 价值观是简单、无卫生的内容。 逃脱或消毒 它们在渲染为 HTML 之前.

* * *

## AI 方法

聊天、搜索和建议都支持流(默认)和非流模式.

### 语音会议

在不管理WebRTC,麦克风音轨或字幕片段的情况下,与单一视频交谈. 语音需要账户的AI和语音功能,视频记录以及浏览器 在安全上下文(HTTPS,或用于开发的本地主机)中带有麦克风许可. 工厂在服务器渲染过程中同步且安全可调用;仅限 `start()` 使用浏览器 API. 不需要框架或额外的依赖.

测试准确公布的 npm 释放的操作员专用页面,请参见 [语音接收装置](https://github.com/boldvideo/bold-js/tree/main/docs/voice)。 。 。 . 其记录的合成检查不能确定真正的提供者或Safari/iPhone的接受.

使用账户设置来决定是否显示语音控制 。 失踪或失实 值保持语音隐藏;当会话开始时,经纪人仍然强制访问.

```typescript
const settings = await bold.settings();
const voiceEnabled = settings.data.account.voice?.enabled === true;
```

```typescript
const session = bold.ai.voice.createSession({
  videoId: 'lesson-slug',
  viewer: 'customer-123', // Optional viewer UUID or external ID
  onStatus: (status) => { statusLabel.textContent = status; },
  onCaptions: (turns) => {
    // Each turn has a stable id, speaker ('user' or 'assistant'), text,
    // startedAt/updatedAt in milliseconds, and seekable timestamp segments.
    captions.textContent = turns.map(turn => `${turn.speaker}: ${turn.text}`).join('\n');
  },
  onEnded: (reason) => console.log('Voice ended:', reason),
  onError: (error) => { errorLabel.textContent = error.message; },
});

// Start directly from a user gesture. Handle rejection even when using onError.
startButton.onclick = () => { void session.start().catch(() => {}); };
muteButton.onclick = () => session.setMuted(!session.muted);
endButton.onclick = () => { void session.end(); };

// Optional animation-frame polling: normalized RMS amplitudes, not volume settings.
const { input, output } = session.getAudioLevels();

// In your component unmount / video replacement callback, call session.dispose().
```

`createSession(options)` 返回 a **单用途** `VoiceSession`。创建新手柄 重新连接。 经纪人的请求发生在里面 `start()`,在麦克风许可后 和社民党谈判;来电者不供应社民党. 可选 `viewerProfile` 接受一个 字符串或字符串、数字和布尔值的对象,不改变而转发 它的钥匙。 查看器配置需要账户的查看器特性.

-   只读状态 : `status`, (中文(简体) ), `sessionId` (直到经纪人回复为止), `muted`。 。 。 .
-   状态 : `idle → connecting → live → ending → ended`; 远程关闭可能去 直接转到 `ended`。 。 。 .
-   `start(): Promise<void>` 解析何时存在,或何时取消 `end()`编号 :`dispose()`。 。 。 . 检查 `session.status` 如果后续代码需要实时连接。 设置失败 拒绝和呼叫 `onError`。并行开始分享一次尝试.
-   `setMuted(boolean)` 本地禁用麦克风音轨, 包括启动前的设定 .
-   `setPlaybackState({ playing, currentTime })` 哑声麦克风和辅助音频 视频播放时 恢复观众的麦克风哑弹选择, 与助理分享视频位置。 调用游戏,暂停,结束,和 寻求事件(不是每次时间更新). `currentTime` 必须是有限、非负性的 秒; 上下文以整秒精度被去除。 启动前更新为 立即应用到新媒体上;只有最新的上下文在直播时发送. 结束后的电话被忽略 。 此方法不操作您的视频播放器 .
-   `end(): Promise<void>` 立即停止麦克风/音频,最多三秒钟 以接近确认。 它是一能的;完成并不确认最后账单.
-   `dispose()` 默默地立即释放出一切,包括在设置或结束期间. SDK也处理上 `pagehide`;它无法检测框架卸载.
-   最终原因 : `user`, (中文(简体) ), `idle`, (中文(简体) ), `time`, (中文(简体) ), `server`, (中文(简体) ), `connection`, (中文(简体) ), `error`。 。 。 .

连接设置有60秒的最后期限 **经麦克风许可后**; 取消 并及时解决未解决的许可问题,并停止任何给予的麦克风音轨 等会 对付费的会话创建没有自动重复。 SDK执行 服务器在页面运行时的最大持续时间和闲置限制。 可听到的投入/产出或 笔录 dertas 和视频回放计数为活动; 输入的无声音频帧不计数 . 暂停视频重新启动闲置窗口; 回放从未延长最长持续时间 。 浏览器计时器 可以被节制或中止,因此后端限制仍然对计费具有权威性.

经纪人的失败是 `VoiceAPIError` 可选实例 `status`, (中文(简体) ), `code`, (中文(简体) ), `retryable`, (中文(简体) ), `retryAfter` (raw HTTP 头条), `resetsOn`,以及 `originalError`。这包括 `voice_allowance_exceeded` (402), (中文(简体) ), `voice_session_limit` (409个)和 `rate_limited` (429) (中文(简体) ). 麦克风、回放和WebRTC故障是普通的 `Error` 实例。 提供者错误 事件调用 `onError` 但不要自动结束一个活的会话.

**说明和寻找按钮.** 1500米以内同一发言者产生的碎片 同一回合,包括发言者重叠时. `turn.segments` 包含 `{ type: 'text', text }` 财务报告和审定财务报表 `{ type: 'timestamp', text, seconds }` 条目。 显示 a 时间戳作为按钮,其点击设置您的视频播放器 `currentTime` 改为 `seconds`。 。 。 . 两者 `m:ss` 财务报告和审定财务报表 `h:mm:ss` 获得支持,包括: `0:00`。说明文本不可信; 使用文本节点或您框架的逃避文本渲染, 而不是 `innerHTML`。 。 。 . 纯洁的助手 `appendVoiceCaption(turns, speaker, delta, now)` 财务报告和审定财务报表 `splitVoiceTimestamps(text)` 也被导出。 SDK不控制你的视频 玩家或注入特定应用程序的问候.

如果提供者要求代表团,SDK用固定的英语回复 只有录音记录: " 只有这种录像记录。 从它的回答, 或者说视频没有覆盖它。” 此协议响应也用非英语发送 届会; `VoiceSessionOptions` 不显示语言或指令覆盖.

**反应清净.** 保持手柄在回放, 创建它的效果,从 一个点击处理器。 为每个挂载/视频创建新手柄( 包括严格模式) 效果重放 ; 不要以效果开始麦克风捕获 .

```tsx
const voiceRef = useRef<VoiceSession | null>(null);

useEffect(() => {
  const session = bold.ai.voice.createSession({
    videoId,
    onStatus: setStatus,
    onCaptions: setCaptions, // useState<readonly VoiceCaptionTurn[]>([])
    onError: (error) => setError(error.message),
  });
  voiceRef.current = session;
  return () => {
    session.dispose();
    voiceRef.current = null;
  };
}, [bold, videoId]); // Keep the bold client stable across renders.

// In a button handler:
const startVoice = () => { void voiceRef.current?.start().catch(() => {}); };
```

导入 `VoiceSession` 财务报告和审定财务报表 `VoiceCaptionTurn` 作为类型从 `@boldvideo/bold-js`,以及 反应钩从 `react`。根据需要,将显示的状态/标题重置到视频更改上.

### 聊天

整个图书馆的谈话AI 深入的QQA 在你的整个视频库.

```typescript
// Streaming (default)
const stream = await bold.ai.chat({ prompt: 'How do I price my SaaS?' });

for await (const event of stream) {
  if (event.type === 'text_delta') process.stdout.write(event.delta);
  if (event.type === 'sources') console.log('Sources:', event.sources);
}

// Non-streaming
const response = await bold.ai.chat({ 
  prompt: 'What are the best closing techniques?',
  stream: false 
});
console.log(response.content);
```

**选项 :**

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `prompt` | `string` | 用户的问题( 需要) |
| `stream` | `boolean` | `true` (默认)SSE, `false` 给 JSON 的 |
| `videoId` | `string` | 如果提供的话, 视频的范围 而不是整个库 |
| `currentTime` | `number` | 当前播放位置( 只使用 `videoId`(中文(简体) ) |
| `conversationId` | `string` | 继续现有对话的通行证 |
| `collectionId` | `string` | 过滤到特定收藏 |
| `tags` | `string[]` | 通过标签过滤 |

### 建议

获得基于话题的AI动力视频建议——理想的个性化学习路径,考试准备,以及内容发现.

```typescript
// Streaming (default)
const stream = await bold.ai.recommendations({ 
  topics: ['contract law', 'ethics', 'client management'],
});

for await (const event of stream) {
  if (event.type === 'recommendations') {
    event.recommendations.forEach(rec => {
      console.log(`${rec.topic}:`);
      rec.videos.forEach(v => console.log(`  - ${v.title} (${v.relevance})`));
    });
  }
  if (event.type === 'text_delta') {
    process.stdout.write(event.delta); // AI guidance
  }
}

// Non-streaming
const response = await bold.ai.recommendations({ 
  topics: ['sales', 'marketing'],
  stream: false 
});
console.log(response.guidance);
console.log(response.recommendations);
```

**选项 :**

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `topics` | `string[]` | 寻找(需要)内容的主题 |
| `stream` | `boolean` | `true` (默认)SSE, `false` 给 JSON 的 |
| `limit` | `number` | 每个主题的最大视频( 默认: 5, 最大: 20) |
| `collectionId` | `string` | 过滤到特定收藏 |
| `tags` | `string[]` | 通过标签过滤 |
| `includeGuidance` | `boolean` | 包含 AI 学习路径描述( 默认: 真) |
| `context` | `AIContextMessage[]` | 先前的对话开始后续 |

### 搜索

以AI生成的简短摘要快速语义搜索.

```typescript
const stream = await bold.ai.search({ 
  prompt: 'pricing strategies',
  limit: 10 
});

for await (const event of stream) {
  if (event.type === 'sources') {
    console.log(`Found ${event.sources.length} results`);
  }
}
```

### AI 交互跟踪

`chat` (全图书馆或录像机), `ask`, (中文(简体) ), `coach`,以及 `search` 接受一个 选项 `requestId` UUID, 发送为 `request_id`。每次故意生成一次 用户动作并在重试此动作时重新使用。 新问题、后续行动或 故意重复搜索获得一个新的UUID. SDK 不生成ID或重试 自动请求.

`search` 还接受 `searchMode: 'preview' | 'settled'`编号 `search_mode`。 。 。 . 关闭它保存服务器的 `settled` 默认。 使用 `preview` 临时 搜索和 `settled` 最终提交的搜索文件; 处理最后提交的文件 以它自己的UUID作为新的行动.

```typescript
const action = {
  prompt: 'pricing strategies',
  requestId: crypto.randomUUID(),
  searchMode: 'settled' as const,
  stream: false as const,
};
const response = await bold.ai.search(action);
// If retrying this action, pass the same action.requestId.
console.log(response.interactionId);
```

贾森 `AIResponse` 和SSE 组合键 `message_complete` 事件曝光 `interactionId?: string | null` (自 `interaction_id`) (中文(简体) ). 搜索预览 可能返回 `null`; 旧服务器可能会省略. 这些ID支持 **分析 仅复制**,而不是回放或模式调用分解。 持久性 是最佳和同步的: 接收ID是 **不是一个持久的承认**。 。 。 .

这不会改变 `videos.search()`,该调用 `/videos?query=…`; SDK 会的 不覆盖单独的关键字 `/search` 终点.

聊天,视频聊天, `ask`编号 :`coach`,而AI搜索也接受可选归属: `channel: 'portal' | 'embed' | 'api' | 'mcp' | 'unknown'`, (中文(简体) ), `clientName: string`,以及 `clientVersion: string`。这些是作为顶级发送的 `channel`, (中文(简体) ), `client_name`,以及 `client_version`字段中省略;后端正常丢失或 频道值为错误 `unknown`。归属不影响 `requestId` 相去相减. 设定 `channel: 'portal'` 明确在端口服务器代理; 这些 标签是分析元数据,而不是授权信号.

### 视频扫描聊天

通过传递来谈论一个特定的视频 `videoId`只使用视频的笔录作为上下文.

```typescript
const stream = await bold.ai.chat({ 
  videoId: 'video-id',
  prompt: 'What is discussed at the 5 minute mark?' 
});

for await (const event of stream) {
  if (event.type === 'text_delta') process.stdout.write(event.delta);
}

// With playback context (coming soon)
const stream = await bold.ai.chat({ 
  videoId: 'video-id',
  prompt: 'What does she mean by that?',
  currentTime: 847  // seconds
});
```

### 获取对话历史

通过 ID 获取对话以显示消息历史 :

```typescript
const conversation = await bold.ai.getConversation('550e8400-e29b-41d4-a716-446655440000');

console.log(`Created: ${conversation.createdAt}`);
for (const msg of conversation.messages) {
  console.log(`${msg.role}: ${msg.content}`);
}
```

### 答题率

使用可选 `messageId` 从非流出 `AIResponse` 或流线 `message_complete` 用于评分助手回答的事件 :

```typescript
const answer = await bold.ai.chat({ prompt: 'How do I get started?', stream: false });
if (answer.messageId) {
  const feedback = await bold.ai.rateMessage(answer.messageId, {
    rating: 'down',
    reasons: ['too_short'],
    note: 'Please include an example.',
  });
  await bold.ai.clearRating(feedback.messageId);
}
```

`rating` 实值 `'up'` 或者说 `'down'`可选 `reasons` 接受 `'wrong'`, (中文(简体) ), `'not_in_video'`, (中文(简体) ), `'too_short'`, (中文(简体) ), `'off_topic'`,以及 `'other'`· ; `note` 是可选的. `rateMessage` 返回时 `MessageFeedback` 与 `messageId`, (中文(简体) ), `rating`, (中文(简体) ), `reasons`, (中文(简体) ), 和无效 `note`。 。 。 . `clearRating` 解析无值。 都抛出 `AIAPIError` 和404状态,当消息不在呼叫者的账户上.

### 多回合对话

使用 `context` 后续问题参数:

```typescript
const first = await bold.ai.search({ 
  prompt: 'How do indie designers find clients?',
  stream: false 
});

const followUp = await bold.ai.search({
  prompt: 'What about cold outreach specifically?',
  context: first.context,
  stream: false
});
```

* * *

## 对话启动器

获取对话启动提示, 可用时按收藏解决 返回账户一级启动人,否则:

```typescript
// Account-level starters (no collectionIds)
const { data: starters } = await bold.conversationStarters.list();

// Resolve starters for one or more collections
const { data: starters } = await bold.conversationStarters.list({
  collectionIds: ['collection-uuid-1', 'collection-uuid-2']
});

starters.forEach((starter) => {
  console.log(starter.text, starter.source, starter.collectionId);
});
```

* * *

## 分析

### 明确AI 约定(浏览器或服务器)

`bold.trackEngagement(options: TrackEngagementOptions): Promise<void>` 发送 `POST /api/v1/event` 独立于遗产 `trackEvent` 和它的油门. 导入 `TrackEngagementOptions` 从 `@boldvideo/bold-js`。 。 。 .

| SDK 字段 | 电线场 | 含义 |
| --- | --- | --- |
| `event` | `n` | `source_open` 或者说 `video_progress` |
| `interactionId` | `interaction_id` | AI 响应中所需的交互 UUID |
| `openId` | `playback_id` | 每个有意打开的源头都需要新的UUID; **不是一个 Mux 播放回放Id** |
| `videoId` | `vid` | 所需的源视频 ID |
| `viewer?` | `viewer` | 现有查看器 UUID 或外部 ID |
| `watchedSeconds` | `watched_seconds` | 所需经费 `video_progress`,缺席 `source_open`; 累计过期 **为此打开播放时长秒** |

```typescript
// On a deliberate source open, skip if the response has no interaction ID.
if (response.interactionId) {
  const open = {
    interactionId: response.interactionId,
    openId: crypto.randomUUID(),
    videoId: source.videoId,
  };
  void bold.trackEngagement({ ...open, event: 'source_open' });

  // Later, after the caller has measured 12.5 seconds spent playing:
  void bold.trackEngagement({ ...open, event: 'video_progress', watchedSeconds: 12.5 });
}
```

`source_open` 只建立开放; 进步需要开放。 维护 计数器,不包括暂停/缓冲时间。 不发送绝对 玩家头姿势 寻找三角洲 或每个样本增量 重排/重排 进度使用相同的 `openId`;后端保留了累积的最大. 生成 新鲜的 `openId` 当用户故意再次打开源时.

呼叫是最佳的 并且安全 开火和忘记: 运输错误被抓住, 并且不使用自动重试或共享节流阀。 一项坚定的承诺(包括 HTTP 202)为 **不是一个持久的承认**后端会验证房客,观众, 和源属性;它重复捕获/公开比赛最多30秒 不创建占位符交互。 服务器代理必须获得 API 密钥和 来自信任的租户/会话背景的浏览器引用,而不是浏览器的主张.

### 遗留的播放和页面浏览

跟踪视频事件和页面浏览分析.

```typescript
// Track video events (play, pause, complete, etc.)
bold.trackEvent({
  type: 'play',
  videoId: 'video-id',
  timestamp: 0
});

// Track page views
bold.trackPageView({
  path: '/videos/my-video',
  referrer: document.referrer
});
```

* * *

## 类型脚本

所有类型都导出为全类型脚本支持 :

```typescript
import type { 
  Video, 
  Playlist, 
  Settings,
  Portal,
  PortalHero,
  MenuItem,
  AIEvent,
  AIResponse,
  ChatOptions,
  SearchOptions,
  RecommendationsOptions,
  RecommendationsResponse,
  Recommendation,
  Conversation,
  ConversationMessage,
  Source,
  Viewer,
  ViewerProgress,
  ViewerLookupParams,
  ListProgressOptions,
  ListVideosOptions,
  ListVideosLatestOptions,
  ListVideosIndexOptions,
  NotificationChannels,
  NotificationPreferencesResponse,
  NotificationChannelUpdate,
  UpdateNotificationPreferencesData,
  NotificationProvider,
  RegisterDeviceData,
  RegisterDeviceResponse,
  // Community API
  Post,
  PostAuthor,
  Comment,
  ReactionResponse,
  Mention,
  MentionSkippedReport,
  CommunityPostCreateResponse,
  CommunityCommentCreateResponse,
  ListPostsOptions,
  ListMentionsOptions,
  MentionsUnreadCountResponse,
  MarkMentionsReadData,
  MarkMentionsReadResponse,
  CreatePostData,
  UpdatePostData,
  CreateCommentData,
  // Conversation Starters
  ConversationStarter,
  ConversationStarterSource,
  ListConversationStartersOptions
} from '@boldvideo/bold-js';
```

* * *

## 从v1.7.x移徙

### 断开: 现在所有响应类型都使用骆驼 Case

**所有API响应** (videos, playlist, setters, AI)现在被转换为使用平庸的TypeScript/JavaScript命名:

```typescript
// Before (v1.7.x and earlier)
video.playback_id
video.published_at
video.stream_url
video.meta_data
settings.featured_playlists
settings.menu_items
settings.theme_config
playlist.is_private

// After (v1.8.0)
video.playbackId
video.publishedAt
video.streamUrl
video.metaData
settings.featuredPlaylists
settings.menuItems
settings.themeConfig
playlist.isPrivate
```

### 方法变化

| 旧 | 新设 | 页:1 |
| --- | --- | --- |
| `bold.ai.ask(opts)` | `bold.ai.chat(opts)` | `ask()` 还在工作,但已经贬值 |
| `bold.ai.coach(opts)` | `bold.ai.chat(opts)` | `coach()` 还在工作,但已经贬值 |
| `bold.ai.chat(videoId, opts)` | `bold.ai.chat({ videoId, ...opts })` | 过 `videoId` 选项中 |
| `bold.ai.recommend(opts)` | `bold.ai.recommendations(opts)` | `recommend()` 还在工作,但已经贬值 |

### 类型重命名

| 旧类型 | 新类型 |
| --- | --- |
| `AskOptions` | `ChatOptions` |
| `RecommendOptions` | `RecommendationsOptions` |
| `RecommendResponse` | `RecommendationsResponse` |

旧类型仍然作为后向相容性的别名被导出.

* * *

## 相关链接

-   **[粗体 API 文档](https://docs.boldvideo.io/docs/api)**
-   **[GitHub 仓库](https://github.com/boldvideo/bold-js)**
-   **[npm 软件包](https://www.npmjs.com/package/@boldvideo/bold-js)**

## 捐款

见 [捐款额](https://npmcn.com/package/%40boldvideo/bold-js/CONTRIBUTING.md) 详细介绍如何为该项目作出贡献.

## 警卫

见 [警卫](https://npmcn.com/package/%40boldvideo/bold-js/SECURITY.md) 安全政策和报告脆弱性.

## 许可证

麻省理工学院

## 常见问题

### @boldvideo/bold-js 是什么？

@ boldvid \(韩语\)

### 如何安装 @boldvideo/bold-js？

在项目目录运行 npm install @boldvideo/bold-js，或使用 pnpm add / yarn add / bun add。

### @boldvideo/bold-js 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @boldvideo/bold-js 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @boldvideo/bold-js 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=20\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @boldvideo/bold-js 可以商用吗？

@boldvideo/bold-js 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @boldvideo/bold-js 的下载量有多少？

根据 npm 官方下载统计，@boldvideo/bold-js 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,663 次，日均约 89 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @boldvideo/bold-js 还在维护吗？

最新版本 2\.5\.0 发布于 2026/10/24（15 天前）；至今共发布 73 个版本，其中过去 12 个月发布了 52 个；首次发布于 2023/03/05。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

