npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

qt-human

v3.2.0

Published

A JavaScript SDK for 3D digital human rendering and interaction

Downloads

414

Readme

全特数字人 JSSDK 库

npm version license TypeScript

一个用于 3D 数字人渲染和交互的 JavaScript SDK,提供对话、播报、语音识别、动作控制等功能。

最新版本: v3.0.2-alpha - 查看更新日志 | 发布说明

特性

  • 🎭 3D 数字人渲染 - 基于 Three.js 的高性能 3D 渲染
  • 💬 AI 对话 - 支持流式对话,实时响应
  • 🗣️ 语音合成 (TTS) - 文本转语音,支持多种音色和模型
  • 🎤 语音识别 (ASR) - 语音转文本,支持实时识别
  • 🎬 动作控制 - 丰富的动画和表情控制
  • 📡 Gateway 服务 - 统一的 WebSocket 网关,集成 LLM、TTS、ASR
  • 📦 TypeScript 支持 - 完整的类型定义,提供良好的开发体验
  • 🎯 事件驱动 - 完善的事件系统,方便状态监听

安装

# 使用 npm
npm install qt-human

# 使用 yarn
yarn add qt-human

# 使用 pnpm
pnpm add qt-human

快速开始

基础使用

import Human, { EmitEvent } from 'qt-human'

// 1. 创建数字人实例
const human = new Human({
  token: 'your-auth-token',
  container: 'container-id', // DOM 元素 ID 或 HTMLElement
  characterId: 'your-character-id'
})

// 2. 监听准备就绪事件
human.on(EmitEvent.HUMAN_READY, () => {
  console.log('数字人已准备就绪')
  
  // 3. 让数字人说话
  human.speak('你好,我是数字人助手')
})

// 4. 监听错误事件
human.on(EmitEvent.HUMAN_ERROR, (error) => {
  console.error('发生错误:', error)
})

TypeScript 使用

import Human, { EmitEvent } from 'qt-human'
import type { HumanConfig } from 'qt-human'

// 使用类型定义
const config: HumanConfig = {
  token: 'your-auth-token',
  container: 'container-id',
  characterId: 'your-character-id',
  audio: {
    volume: 0.8,
    muted: false
  },
  render: {
    fps: 60,
    quality: 'high'
  }
}

const human = new Human(config)

// 类型安全的事件监听
human.on(EmitEvent.HUMAN_READY, () => {
  console.log('Ready!')
})

配置选项

HumanConfig

interface HumanConfig {
  // 必填:授权 Token
  token: string
  
  // 可选:渲染容器(DOM ID 或 HTMLElement)
  // 如果不提供,则只加载模型不渲染
  container?: string | HTMLElement
  
  // 可选:数字人 ID
  characterId?: string
  
  // 可选:服务配置
  server?: {
    api?: string        // API 服务地址
    gateway?: string    // Gateway WebSocket 地址(推荐)
    wss?: string        // 旧版 WebSocket 地址(将被废弃)
  }
  
  // 可选:音频配置
  audio?: {
    volume?: number   // 音量 (0-1),默认 1.0
    muted?: boolean   // 是否静音,默认 false
  }
  
  // 可选:渲染配置
  render?: {
    fps?: number                          // 帧率,默认 30
    quality?: 'low' | 'medium' | 'high'  // 渲染质量
  }
  
  // 可选:模式
  mode?: 'normal' | 'voice' | 'render-only'
  
  // 可选:语音模式配置
  voiceModeConfig?: {
    keepActive?: boolean  // 是否保持激活状态
  }
  
  // 可选:加载动画元素
  loading?: string | HTMLElement
  
  // 可选:加载完成后是否销毁加载动画
  loadingDestroy?: boolean
  
  // 可选:调试模式
  debug?: boolean
  
  // 可选:其他配置
  options?: {
    fps?: number  // BlendShapes 每秒执行的帧数
  }
}

Gateway 服务集成

从 v3.0 开始,SDK 统一使用 Gateway WebSocket 服务来处理 LLM 对话、TTS 语音合成和 ASR 语音识别功能。

const human = new Human({
  token: 'your-auth-token',
  container: 'container-id',
  server: {
    // Gateway WebSocket 地址(推荐使用)
    gateway: 'wss://qt-gateway.quantekeji.com/api/ws',
    // API 服务地址
    api: 'https://qtworld-api.quantekeji.com'
  }
})

// 监听 Gateway 连接状态
human.on(EmitEvent.GATEWAY_CONNECTED, () => {
  console.log('Gateway 已连接')
})

human.on(EmitEvent.GATEWAY_DISCONNECTED, () => {
  console.log('Gateway 已断开')
})

human.on(EmitEvent.GATEWAY_ERROR, (error) => {
  console.error('Gateway 错误:', error)
})

API 文档

核心方法

setCharacter

加载并显示指定的数字人模型。

setCharacter(characterId: string, configuration?: Partial<HumanConfig>): Promise<boolean>

参数:

  • characterId - 数字人 ID(从 qtworld 平台获取)
  • configuration - 可选的配置参数,用于覆盖初始配置

返回值:

  • Promise<boolean> - 加载成功返回 true

示例:

await human.setCharacter('your-character-id')

// 使用自定义配置
await human.setCharacter('your-character-id', {
  audio: { volume: 0.5 },
  render: { fps: 60 }
})

speak

让数字人播报指定的文本内容。

speak(text: string, callback?: Function, opt?: { [key: string]: any }): Promise<StreamAudioPlayInfo>

参数:

  • text - 要播报的文本内容
  • callback - 可选的回调函数,接收播放事件
  • opt - 可选配置(保留参数)

返回值:

  • Promise<StreamAudioPlayInfo> - 播放信息,包含音频和 BlendShapes 数据

示例:

// 基础使用
await human.speak('你好,欢迎使用数字人服务')

// 使用回调监听播放事件
await human.speak('你好', (event) => {
  console.log('播放事件:', event)
})

askStream

进行 AI 流式对话,支持实时响应和语音播报。

askStream(
  params: AskParams,
  callback: (result: Result<ChatMessage[]>) => void
): Promise<Result<AskStreamResponse>>

参数:

  • params - 对话参数
    {
      reqId?: string                    // 请求 ID(可选)
      message: Message[]                // 消息列表
      conversation_id?: string          // 会话 ID(可选)
      response_mode?: 'streaming' | 'blocking'  // 响应模式
      files?: ChatFile[]                // 文件列表(可选)
    }
  • callback - 回调函数,接收流式消息

返回值:

  • Promise<Result<AskStreamResponse>> - 完整的对话响应

示例:

// 单轮对话
await human.askStream(
  {
    message: [
      { role: 'user', content: '你好,请介绍一下自己' }
    ]
  },
  (result) => {
    if (result.code === 0) {
      console.log('收到消息:', result.data)
    }
  }
)

// 多轮对话(带上下文)
await human.askStream(
  {
    message: [
      { role: 'user', content: '今天天气怎么样?' },
    ],
    conversation_id: 'conv-123'
  },
  (result) => {
    console.log('AI 回复:', result.data)
  }
)

stop

停止当前的语音播报和动作。

stop(): Promise<boolean>

返回值:

  • Promise<boolean> - 停止成功返回 true

示例:

// 停止当前播报
await human.stop()

音频控制

muteAudio

控制音频的静音状态。

muteAudio(isMute: boolean = true): void

参数:

  • isMute - true 表示静音,false 表示取消静音

示例:

// 静音
human.muteAudio(true)

// 取消静音
human.muteAudio(false)

muteAction

控制动作的启用状态。

muteAction(isMute: boolean = true): void

参数:

  • isMute - true 表示禁用动作,false 表示启用动作

示例:

// 禁用动作(只播放语音,不播放动画)
human.muteAction(true)

// 启用动作
human.muteAction(false)

语音识别

startVoice2Text

开始录音并进行语音识别。

startVoice2Text(callback?: Voice2TextCallback): Promise<Result<RecorderPermission>>

参数:

  • callback - 可选的回调函数,接收识别结果

返回值:

  • Promise<Result<RecorderPermission>> - 录音权限结果

示例:

// 开始录音
const result = await human.startVoice2Text((data) => {
  console.log('识别结果:', data.text)
  console.log('是否最终结果:', data.is_final)
})

if (result.code === 0) {
  console.log('录音已开始')
}

stopVoice2Text

停止录音并获取最终的识别结果。

stopVoice2Text(callback?: Voice2TextCallback): Promise<Result<null>>

参数:

  • callback - 可选的回调函数,接收最终识别结果

返回值:

  • Promise<Result<null>> - 停止结果

示例:

// 停止录音
await human.stopVoice2Text((data) => {
  console.log('最终识别结果:', data.text)
})

动作和视角控制

playAction

播放指定的动画动作。

playAction(code: string, opts?: PlayActionOptions): Promise<void>

参数:

  • code - 动画代码
  • opts - 动画选项
    {
      loop?: THREE.AnimationActionLoopStyles  // 循环模式
      repetitions?: number                     // 重复次数
    }

示例:

// 播放单次动画
await human.playAction('wave')

// 循环播放动画
await human.playAction('idle', {
  loop: 2201,  // THREE.LoopRepeat
  repetitions: Infinity
})

setAngleView

设置相机视角。

setAngleView(config: AngleViewConfig): void

参数:

  • config - 视角配置
    {
      camera?: {
        position?: [number, number, number]
        rotation?: [number, number, number]
        fov?: number
        duration?: number  // 动画持续时间(毫秒)
      }
      controls?: {
        target?: [number, number, number]
        enabled?: boolean
        duration?: number  // 动画持续时间(毫秒)
      }
    }

示例:

// 切换到正面视角
human.setAngleView({
  camera: {
    position: [0, 1.6, 2],
    duration: 1000
  }
})

资源管理

updateToken

更新授权 Token。

updateToken(token: string): void

参数:

  • token - 新的授权 Token

示例:

// 更新 Token
human.updateToken('new-auth-token')

destroy

销毁数字人实例,释放所有资源。

destroy(): Promise<void>

示例:

// 销毁实例
await human.destroy()

其他方法

getModelInfo

获取当前加载的模型信息。

getModelInfo(): ModelInfo | null

返回值:

  • ModelInfo | null - 模型信息对象,如果未加载则返回 null

speakByAudioShapes

使用自定义音频和 BlendShapes 数据驱动数字人说话。

speakByAudioShapes(uris: string[], shapes: number[][][]): Promise<boolean>

参数:

  • uris - 音频 URL 数组
  • shapes - BlendShapes 数据数组

示例:

await human.speakByAudioShapes(
  ['https://example.com/audio1.wav', 'https://example.com/audio2.wav'],
  [
    [[0, 0, ...], [0.1, 0.2, ...]],  // 第一段音频的 BlendShapes
    [[0, 0, ...], [0.1, 0.2, ...]]   // 第二段音频的 BlendShapes
  ]
)

startRender / stopRender

控制渲染的启动和停止。

startRender(): Promise<boolean>
stopRender(): Promise<boolean>

示例:

// 开始渲染
await human.startRender()

// 停止渲染
await human.stopRender()

事件系统

Human 类基于 Emittery 实现了完善的事件系统,您可以监听各种状态变化。

事件列表

生命周期事件

| 事件 | 说明 | 数据类型 | |------|------|---------| | HUMAN_READY | 数字人加载完成,可以开始交互 | void | | HUMAN_ERROR | 发生错误 | ErrorEvent | | HUMAN_LOAD_PROGRESS | 模型加载进度更新 | LoadProgressEvent | | HUMAN_ACTION_PENDING | 有待执行的动作 | string[] | | CORE_BUNDLES_LOADED | 核心模型包加载完成 | any[] |

音频事件

| 事件 | 说明 | 数据类型 | |------|------|---------| | AUDIO_PLAY | 音频开始播放 | AudioPlayEvent | | AUDIO_PAUSE | 音频暂停 | AudioPlayEvent |

语音识别事件

| 事件 | 说明 | 数据类型 | |------|------|---------| | VOICE2TEXT | 语音识别结果 | Voice2TextEvent | | RECORDER_START | 开始录音 | void | | RECORDER_STOP | 结束录音 | void | | RECORDER_MESSAGE | 录音消息 | any | | RECORDER_SENDING | 发送录音数据 | void |

对话和 TTS 事件

| 事件 | 说明 | 数据类型 | |------|------|---------| | CHAT_MESSAGE_BS_PLAY_START | 对话消息的 BlendShapes 动画开始播放 | object | | CHAT_MESSAGE_BS_PLAY_END | 对话消息的 BlendShapes 动画结束播放 | object |

Gateway 服务事件

| 事件 | 说明 | 数据类型 | |------|------|---------| | GATEWAY_CONNECTED | Gateway WebSocket 连接成功 | void | | GATEWAY_DISCONNECTED | Gateway WebSocket 断开连接 | void | | GATEWAY_RECONNECTING | Gateway WebSocket 正在重连 | void | | GATEWAY_MESSAGE | Gateway 接收到消息 | GatewayMessageEvent | | GATEWAY_ERROR | Gateway 发生错误 | ErrorEvent |

渲染事件

| 事件 | 说明 | 数据类型 | |------|------|---------| | RENDER_READY | 渲染服务初始化完成 | void | | RENDER_ERROR | 渲染服务发生错误 | ErrorEvent | | MODEL_LOAD_PROGRESS | 模型加载进度更新 | ModelLoadEvent | | MODEL_LOADED | 模型加载完成 | ModelLoadEvent | | MODEL_LOAD_FAILED | 模型加载失败 | ErrorEvent | | ANIMATION_START | 动画播放开始 | AnimationEvent | | ANIMATION_END | 动画播放结束 | AnimationEvent |

事件使用示例

import Human, { EmitEvent } from 'qt-human'

const human = new Human({
  token: 'your-token',
  container: 'container-id'
})

// 监听加载进度
human.on(EmitEvent.HUMAN_LOAD_PROGRESS, (data) => {
  const percentage = (data.progress / data.total * 100).toFixed(2)
  console.log(`加载进度: ${percentage}%`)
  
  // 更新进度条
  updateProgressBar(percentage)
})

// 监听准备就绪
human.on(EmitEvent.HUMAN_READY, () => {
  console.log('数字人已准备就绪')
  
  // 隐藏加载动画
  hideLoading()
  
  // 开始交互
  human.speak('你好,我已经准备好了')
})

// 监听错误
human.on(EmitEvent.HUMAN_ERROR, (error) => {
  console.error('错误代码:', error.code)
  console.error('错误消息:', error.message)
  
  // 显示错误提示
  showErrorMessage(error.message)
})

// 监听音频播放状态
human.on(EmitEvent.AUDIO_PLAY, () => {
  console.log('音频开始播放')
  // 显示播放图标
  showPlayingIcon()
})

human.on(EmitEvent.AUDIO_PAUSE, () => {
  console.log('音频已暂停')
  // 隐藏播放图标
  hidePlayingIcon()
})

// 监听 Gateway 连接状态
human.on(EmitEvent.GATEWAY_CONNECTED, () => {
  console.log('Gateway 服务已连接')
  showConnectionStatus('connected')
})

human.on(EmitEvent.GATEWAY_DISCONNECTED, () => {
  console.log('Gateway 服务已断开')
  showConnectionStatus('disconnected')
})

human.on(EmitEvent.GATEWAY_ERROR, (error) => {
  console.error('Gateway 错误:', error)
  showConnectionStatus('error')
})

// 监听语音识别结果
human.on(EmitEvent.VOICE2TEXT, (data) => {
  if (data.isFinal) {
    console.log('最终识别结果:', data.text)
    // 显示最终结果
    displayFinalText(data.text)
  } else {
    console.log('临时识别结果:', data.text)
    // 显示临时结果
    displayTempText(data.text)
  }
})

// 监听对话消息播放
human.on(EmitEvent.CHAT_MESSAGE_BS_PLAY_START, (data) => {
  console.log('开始播放对话:', data.content)
})

human.on(EmitEvent.CHAT_MESSAGE_BS_PLAY_END, (data) => {
  console.log('对话播放结束')
  if (data.isEnd) {
    console.log('所有对话播放完成')
  }
})

类型定义

// 加载进度事件
interface LoadProgressEvent {
  code: 'download' | 'loaded' | 'loading'
  progress: number
  total: number
  url?: string
}

// 错误事件
interface ErrorEvent {
  code: string | number
  msg: string
  details?: any
}

// 音频播放事件
interface AudioPlayEvent {
  playing: boolean
  duration?: number
  currentTime?: number
}

// 语音识别事件
interface Voice2TextEvent {
  isFinal: boolean
  text: string
  confidence?: number
}

// Gateway 消息事件
interface GatewayMessageEvent {
  type: 'chat' | 'tts' | 'asr' | 'error'
  event: string
  data: any
  requestId?: string
}

使用示例

基础对话示例

import Human, { EmitEvent } from 'qt-human'

// 创建数字人实例
const human = new Human({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

// 监听准备就绪
human.on(EmitEvent.HUMAN_READY, async () => {
  console.log('数字人已准备就绪')
  
  // 让数字人打招呼
  await human.speak('你好,我是数字人助手,有什么可以帮助你的吗?')
})

// 监听错误
human.on(EmitEvent.HUMAN_ERROR, (error) => {
  console.error('发生错误:', error)
})

AI 对话示例

import Human, { EmitEvent } from 'qt-human'

const human = new Human({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

// 单轮对话
async function chat(question: string) {
  const result = await human.askStream(
    {
      message: [
        { role: 'user', content: question }
      ]
    },
    (streamResult) => {
      // 处理流式消息
      if (streamResult.code === 0) {
        const messages = streamResult.data
        messages.forEach(msg => {
          if (msg.event === 'message') {
            console.log('AI 回复:', msg.answer)
          }
        })
      }
    }
  )
  
  if (result.code === 0) {
    console.log('对话完成')
  }
}

// 使用示例
human.on(EmitEvent.HUMAN_READY, () => {
  chat('今天天气怎么样?')
})

多轮对话示例

import Human, { EmitEvent } from 'qt-human'

const human = new Human({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

// 维护对话历史
const conversationHistory: Array<{ role: 'user' | 'assistant', content: string }> = []
let conversationId = ''

async function chatWithContext(userMessage: string) {
  // 添加用户消息到历史
  conversationHistory.push({
    role: 'user',
    content: userMessage
  })
  
  const result = await human.askStream(
    {
      message: conversationHistory,
      conversation_id: conversationId
    },
    (streamResult) => {
      if (streamResult.code === 0) {
        const messages = streamResult.data
        messages.forEach(msg => {
          if (msg.event === 'message') {
            // 保存会话 ID
            if (msg.conversation_id) {
              conversationId = msg.conversation_id
            }
          }
        })
      }
    }
  )
  
  if (result.code === 0) {
    // 添加 AI 回复到历史
    // 注意:实际的回复内容需要从流式消息中收集
    console.log('对话完成')
  }
}

// 使用示例
human.on(EmitEvent.HUMAN_READY, async () => {
  await chatWithContext('你好')
  await chatWithContext('今天天气怎么样?')
  await chatWithContext('那适合户外运动吗?')
})

语音识别示例

import Human, { EmitEvent } from 'qt-human'

const human = new Human({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

let recognizedText = ''

// 开始录音按钮
document.getElementById('start-record')?.addEventListener('click', async () => {
  recognizedText = ''
  
  const result = await human.startVoice2Text((data) => {
    if (data.is_final) {
      // 最终识别结果
      recognizedText = data.text
      console.log('最终识别结果:', data.text)
      
      // 显示识别结果
      document.getElementById('result')!.textContent = data.text
    } else {
      // 临时识别结果
      console.log('临时识别结果:', data.text)
      
      // 显示临时结果
      document.getElementById('temp-result')!.textContent = data.text
    }
  })
  
  if (result.code === 0) {
    console.log('开始录音')
  }
})

// 停止录音按钮
document.getElementById('stop-record')?.addEventListener('click', async () => {
  await human.stopVoice2Text((data) => {
    console.log('录音结束,最终结果:', data.text)
    
    // 使用识别结果进行对话
    if (data.text) {
      human.askStream(
        {
          message: [{ role: 'user', content: data.text }]
        },
        (result) => {
          console.log('AI 回复:', result)
        }
      )
    }
  })
})

音频和动作控制示例

import Human, { EmitEvent } from 'qt-human'

const human = new Human({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

// 静音控制
document.getElementById('mute-audio')?.addEventListener('click', () => {
  human.muteAudio(true)
  console.log('已静音')
})

document.getElementById('unmute-audio')?.addEventListener('click', () => {
  human.muteAudio(false)
  console.log('已取消静音')
})

// 动作控制
document.getElementById('mute-action')?.addEventListener('click', () => {
  human.muteAction(true)
  console.log('已禁用动作')
})

document.getElementById('unmute-action')?.addEventListener('click', () => {
  human.muteAction(false)
  console.log('已启用动作')
})

// 播放自定义动画
document.getElementById('play-wave')?.addEventListener('click', () => {
  human.playAction('wave')
})

// 停止当前播报
document.getElementById('stop')?.addEventListener('click', async () => {
  await human.stop()
  console.log('已停止')
})

视角控制示例

import Human, { EmitEvent } from 'qt-human'

const human = new Human({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

// 切换到正面视角
document.getElementById('front-view')?.addEventListener('click', () => {
  human.setAngleView({
    camera: {
      position: [0, 1.6, 2],
      fov: 50,
      duration: 1000  // 1秒过渡动画
    }
  })
})

// 切换到侧面视角
document.getElementById('side-view')?.addEventListener('click', () => {
  human.setAngleView({
    camera: {
      position: [2, 1.6, 1],
      fov: 50,
      duration: 1000
    }
  })
})

完整应用示例

import Human, { EmitEvent } from 'qt-human'

class DigitalHumanApp {
  private human: Human
  private conversationHistory: Array<{ role: 'user' | 'assistant', content: string }> = []
  private conversationId = ''
  
  constructor(config: { token: string, container: string, characterId: string }) {
    this.human = new Human(config)
    this.setupEventListeners()
  }
  
  private setupEventListeners() {
    // 监听加载进度
    this.human.on(EmitEvent.HUMAN_LOAD_PROGRESS, (data) => {
      const percentage = (data.progress / data.total * 100).toFixed(2)
      this.updateLoadingProgress(percentage)
    })
    
    // 监听准备就绪
    this.human.on(EmitEvent.HUMAN_READY, () => {
      this.hideLoading()
      this.human.speak('你好,我已经准备好了,有什么可以帮助你的吗?')
    })
    
    // 监听错误
    this.human.on(EmitEvent.HUMAN_ERROR, (error) => {
      this.showError(error.message)
    })
    
    // 监听音频播放状态
    this.human.on(EmitEvent.AUDIO_PLAY, () => {
      this.showPlayingIndicator()
    })
    
    this.human.on(EmitEvent.AUDIO_PAUSE, () => {
      this.hidePlayingIndicator()
    })
  }
  
  async chat(userMessage: string) {
    // 添加用户消息
    this.conversationHistory.push({
      role: 'user',
      content: userMessage
    })
    
    // 显示用户消息
    this.addMessageToUI('user', userMessage)
    
    let aiResponse = ''
    
    const result = await this.human.askStream(
      {
        message: this.conversationHistory,
        conversation_id: this.conversationId
      },
      (streamResult) => {
        if (streamResult.code === 0) {
          streamResult.data.forEach(msg => {
            if (msg.event === 'message') {
              aiResponse += msg.answer
              this.updateAIMessage(aiResponse)
              
              if (msg.conversation_id) {
                this.conversationId = msg.conversation_id
              }
            }
          })
        }
      }
    )
    
    if (result.code === 0) {
      // 添加 AI 回复到历史
      this.conversationHistory.push({
        role: 'assistant',
        content: aiResponse
      })
    }
  }
  
  async startVoiceInput() {
    await this.human.startVoice2Text((data) => {
      if (data.is_final) {
        // 使用识别结果进行对话
        this.chat(data.text)
      } else {
        // 显示临时识别结果
        this.showTempRecognitionResult(data.text)
      }
    })
  }
  
  async stopVoiceInput() {
    await this.human.stopVoice2Text()
  }
  
  async stop() {
    await this.human.stop()
  }
  
  async destroy() {
    await this.human.destroy()
  }
  
  // UI 辅助方法
  private updateLoadingProgress(percentage: string) {
    // 更新进度条
  }
  
  private hideLoading() {
    // 隐藏加载动画
  }
  
  private showError(message: string) {
    // 显示错误提示
  }
  
  private showPlayingIndicator() {
    // 显示播放指示器
  }
  
  private hidePlayingIndicator() {
    // 隐藏播放指示器
  }
  
  private addMessageToUI(role: 'user' | 'assistant', content: string) {
    // 添加消息到 UI
  }
  
  private updateAIMessage(content: string) {
    // 更新 AI 消息
  }
  
  private showTempRecognitionResult(text: string) {
    // 显示临时识别结果
  }
}

// 使用示例
const app = new DigitalHumanApp({
  token: 'your-auth-token',
  container: 'human-container',
  characterId: 'your-character-id'
})

// 发送消息
document.getElementById('send-btn')?.addEventListener('click', () => {
  const input = document.getElementById('message-input') as HTMLInputElement
  if (input.value.trim()) {
    app.chat(input.value.trim())
    input.value = ''
  }
})

// 语音输入
document.getElementById('voice-btn')?.addEventListener('click', () => {
  app.startVoiceInput()
})

// 停止录音
document.getElementById('stop-voice-btn')?.addEventListener('click', () => {
  app.stopVoiceInput()
})

获取 Token 和 Character ID

获取 Character ID

  1. 访问 qtworld 平台
  2. 登录您的账号
  3. 在数字人管理页面复制您的数字人 ID

获取授权 Token

方式一:通过授权接口获取(推荐)

接口信息:

  • 请求方式: POST
  • 接口地址: https://qtworld-api.quantekeji.com/human-auth/get-token

请求参数:

{
  secretKey: string    // 应用秘钥(从 qtworld 平台获取)
  visitorId: string    // 访问者 ID(需要唯一)
  visitorName: string  // 访问者名称
}

响应参数:

{
  code: number  // 状态码,0 表示成功
  data: string  // JWT Token
  msg: string   // 响应消息
}

示例代码:

import axios from 'axios'
import Human from 'qt-human'

async function initHuman() {
  try {
    // 1. 获取 Token(建议在服务端调用)
    const response = await axios.post(
      'https://qtworld-api.quantekeji.com/human-auth/get-token',
      {
        secretKey: 'your-secret-key',
        visitorId: 'unique-visitor-id',
        visitorName: 'visitor-name'
      }
    )
    
    if (response.data.code === 0) {
      const token = response.data.data
      
      // 2. 初始化 Human 对象
      const human = new Human({
        token,
        container: 'human-container',
        characterId: 'your-character-id'
      })
      
      return human
    } else {
      throw new Error(response.data.msg)
    }
  } catch (error) {
    console.error('初始化失败:', error)
    throw error
  }
}

// 使用
initHuman().then(human => {
  console.log('数字人初始化成功')
})

方式二:服务端代理(推荐用于生产环境)

为了保护 secretKey 不被暴露在前端,建议在服务端实现一个代理接口:

后端示例(Node.js/Express):

// server.js
const express = require('express')
const axios = require('axios')

const app = express()
app.use(express.json())

app.post('/api/get-human-token', async (req, res) => {
  try {
    const { visitorId, visitorName } = req.body
    
    // 从环境变量读取 secretKey
    const secretKey = process.env.HUMAN_SECRET_KEY
    
    const response = await axios.post(
      'https://qtworld-api.quantekeji.com/human-auth/get-token',
      {
        secretKey,
        visitorId,
        visitorName
      }
    )
    
    res.json(response.data)
  } catch (error) {
    res.status(500).json({
      code: -1,
      msg: error.message
    })
  }
})

app.listen(3000)

前端调用:

import axios from 'axios'
import Human from 'qt-human'

async function initHuman(visitorId: string, visitorName: string) {
  try {
    // 调用自己的后端接口
    const response = await axios.post('/api/get-human-token', {
      visitorId,
      visitorName
    })
    
    if (response.data.code === 0) {
      const token = response.data.data
      
      const human = new Human({
        token,
        container: 'human-container',
        characterId: 'your-character-id'
      })
      
      return human
    }
  } catch (error) {
    console.error('初始化失败:', error)
    throw error
  }
}

Token 管理

Token 过期处理

Token 通常有效期为 2 小时,过期后需要重新获取。您可以使用 updateToken 方法更新 Token:

// 定期刷新 Token
async function refreshToken(human: Human) {
  try {
    const response = await axios.post('/api/get-human-token', {
      visitorId: 'unique-visitor-id',
      visitorName: 'visitor-name'
    })
    
    if (response.data.code === 0) {
      const newToken = response.data.data
      human.updateToken(newToken)
      console.log('Token 已更新')
    }
  } catch (error) {
    console.error('Token 更新失败:', error)
  }
}

// 每 1.5 小时刷新一次 Token
setInterval(() => {
  refreshToken(human)
}, 90 * 60 * 1000)

注意事项

安全性

  1. 不要在前端代码中硬编码 secretKey

    • secretKey 是敏感信息,应该存储在服务端环境变量中
    • 建议通过服务端代理接口获取 Token
  2. Token 管理

    • Token 有效期通常为 2 小时
    • 建议实现 Token 自动刷新机制
    • 不要将 Token 存储在不安全的地方(如 localStorage)
  3. 访问者 ID 唯一性

    • visitorId 需要保证唯一性
    • 建议使用用户 ID 或其他唯一标识

使用建议

  1. 等待模型加载完成

    • 在调用 speakaskStream 等方法前,确保监听到 HUMAN_READY 事件
    • 模型加载需要一定时间,请耐心等待
  2. 资源管理

    • 在组件销毁时调用 destroy() 方法释放资源
    • 避免创建多个 Human 实例导致内存泄漏
  3. 错误处理

    • 始终监听 HUMAN_ERROR 事件
    • 对异步方法使用 try-catch 捕获错误
  4. 性能优化

    • 根据需要调整 fps 参数(默认 30)
    • 使用 muteAction 禁用动画可以提升性能
    • 在不需要渲染时,不传入 container 参数
  5. 网络要求

    • 确保网络连接稳定
    • Gateway WebSocket 需要支持 WSS 协议
    • 建议在良好的网络环境下使用

常见问题

Q: Token 过期后会发生什么?

A: Token 过期后,API 调用会失败。建议实现 Token 自动刷新机制,或在错误回调中处理 Token 过期的情况。

Q: 可以同时创建多个 Human 实例吗?

A: 技术上可以,但不推荐。多个实例会占用大量内存和 GPU 资源,建议只创建一个实例并复用。

Q: 如何处理网络断开的情况?

A: SDK 内置了 Gateway 自动重连机制。您可以监听 GATEWAY_DISCONNECTEDGATEWAY_CONNECTED 事件来处理断线重连的 UI 提示。

Q: 支持哪些浏览器?

A: 支持现代浏览器:

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

Q: 如何调试?

A: 在配置中设置 debug: true 可以开启调试模式,会输出更多日志信息。

const human = new Human({
  token: 'your-token',
  container: 'container-id',
  debug: true  // 开启调试模式
})

TypeScript 类型定义

SDK 提供完整的 TypeScript 类型定义,以下是主要类型的说明。

配置类型

import type { 
  HumanConfig,
  ServerConfig,
  AudioConfig,
  RenderConfig
} from 'qt-human'

API 类型

import type { 
  AskParams,
  ChatMessage,
  AskResponse,
  TTSParams,
  Voice2TextResult
} from 'qt-human'

事件类型

import { EmitEvent } from 'qt-human'
import type { 
  LoadProgressEvent,
  ErrorEvent,
  AudioPlayEvent,
  Voice2TextEvent,
  GatewayMessageEvent
} from 'qt-human'

// 使用事件类型
human.on(EmitEvent.HUMAN_LOAD_PROGRESS, (data: LoadProgressEvent) => {
  console.log(data.progress, data.total)
})

human.on(EmitEvent.HUMAN_ERROR, (error: ErrorEvent) => {
  console.log(error.code, error.message)
})

版本历史

v3.0.0 (当前版本)

重大变更:

  • 🔄 重构 TypeScript 类型系统,提供完整的类型定义
  • 🌐 统一使用 Gateway WebSocket 服务
  • 🎯 完善事件系统,新增多个事件类型
  • 📦 优化项目结构,符合 JSSDK 标准
  • 🗑️ 移除旧版 WebSocket 连接代码
  • ⚡ 优化 Three.js 渲染性能

新增功能:

  • Gateway 服务集成(LLM、TTS、ASR)
  • 流式对话支持
  • 完整的事件系统
  • TypeScript 类型导出

废弃功能:

  • 旧版 openVoiceInteraction 方法(请使用 Gateway ASR 功能)
  • 多个独立的 WebSocket 连接(统一使用 Gateway)

迁移指南:

如果您从 v2.x 升级到 v3.0,请注意以下变更:

  1. 配置变更:
// v2.x
const human = new Human({
  token: 'xxx',
  server: {
    wss: 'ws://...'  // 旧版配置
  }
})

// v3.0
const human = new Human({
  token: 'xxx',
  server: {
    gateway: 'wss://...'  // 新版配置
  }
})
  1. 事件名称变更:
// v2.x
human.on('ready', () => {})

// v3.0
import { EmitEvent } from 'qt-human'
human.on(EmitEvent.HUMAN_READY, () => {})
  1. 类型导入:
// v3.0 新增
import type { HumanConfig } from 'qt-human'

const config: HumanConfig = {
  // ...
}

测试

测试文档

测试工具

性能监控工具

打开 examples/performance-monitor.html 可以实时监控 SDK 的性能指标:

  • 渲染性能: 帧率、帧时间、渲染调用次数
  • 内存使用: JS 堆大小、堆使用率、内存增长
  • 网络性能: WebSocket 状态、连接延迟、消息数量
  • 系统信息: 浏览器、WebGL 版本、GPU 信息
# 启动开发服务器
npm run dev

# 打开浏览器访问
# http://localhost:5173/examples/performance-monitor.html

自动化测试运行器

打开 examples/test-runner.html 可以运行自动化测试套件:

  • 初始化测试
  • 模型加载测试
  • 播报功能测试
  • 对话功能测试
  • 错误处理测试
# 打开浏览器访问
# http://localhost:5173/examples/test-runner.html

性能标准

渲染性能

  • 帧率 ≥ 30 FPS (配置为 30) 或 ≥ 55 FPS (配置为 60)
  • 帧率稳定性:波动 < 10%
  • 无明显掉帧

内存使用

  • 初始内存 < 200 MB
  • 运行时内存增长 < 50 MB / 10分钟
  • 无明显内存泄漏
  • 销毁后内存释放 > 90%

网络性能

  • 连接延迟 < 500 ms
  • 消息延迟 < 200 ms
  • 重连成功率 > 95%
  • 数据传输流畅无卡顿

浏览器兼容性

| 浏览器 | 版本 | 状态 | |-------|------|------| | Chrome | 90+ | ✅ 完全支持 | | Firefox | 88+ | ✅ 完全支持 | | Safari | 14+ | ✅ 完全支持 | | Edge | 90+ | ✅ 完全支持 | | iOS Safari | 14+ | ✅ 完全支持 | | Android Chrome | 8+ | ✅ 完全支持 |

运行测试

# 安装依赖
npm install

# 构建项目
npm run build

# 启动开发服务器
npm run dev

# 打开测试页面
# - 基础功能: examples/basic-usage.html
# - 对话功能: examples/chat-conversation.html
# - 性能监控: examples/performance-monitor.html
# - 测试运行器: examples/test-runner.html

许可证

ISC

支持

如有问题或建议,请联系:

相关链接