qt-human
v3.2.0
Published
A JavaScript SDK for 3D digital human rendering and interaction
Downloads
414
Readme
全特数字人 JSSDK 库
一个用于 3D 数字人渲染和交互的 JavaScript SDK,提供对话、播报、语音识别、动作控制等功能。
特性
- 🎭 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
- 访问 qtworld 平台
- 登录您的账号
- 在数字人管理页面复制您的数字人 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)注意事项
安全性
不要在前端代码中硬编码
secretKeysecretKey是敏感信息,应该存储在服务端环境变量中- 建议通过服务端代理接口获取 Token
Token 管理
- Token 有效期通常为 2 小时
- 建议实现 Token 自动刷新机制
- 不要将 Token 存储在不安全的地方(如 localStorage)
访问者 ID 唯一性
visitorId需要保证唯一性- 建议使用用户 ID 或其他唯一标识
使用建议
等待模型加载完成
- 在调用
speak、askStream等方法前,确保监听到HUMAN_READY事件 - 模型加载需要一定时间,请耐心等待
- 在调用
资源管理
- 在组件销毁时调用
destroy()方法释放资源 - 避免创建多个 Human 实例导致内存泄漏
- 在组件销毁时调用
错误处理
- 始终监听
HUMAN_ERROR事件 - 对异步方法使用 try-catch 捕获错误
- 始终监听
性能优化
- 根据需要调整
fps参数(默认 30) - 使用
muteAction禁用动画可以提升性能 - 在不需要渲染时,不传入
container参数
- 根据需要调整
网络要求
- 确保网络连接稳定
- Gateway WebSocket 需要支持 WSS 协议
- 建议在良好的网络环境下使用
常见问题
Q: Token 过期后会发生什么?
A: Token 过期后,API 调用会失败。建议实现 Token 自动刷新机制,或在错误回调中处理 Token 过期的情况。
Q: 可以同时创建多个 Human 实例吗?
A: 技术上可以,但不推荐。多个实例会占用大量内存和 GPU 资源,建议只创建一个实例并复用。
Q: 如何处理网络断开的情况?
A: SDK 内置了 Gateway 自动重连机制。您可以监听 GATEWAY_DISCONNECTED 和 GATEWAY_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,请注意以下变更:
- 配置变更:
// v2.x
const human = new Human({
token: 'xxx',
server: {
wss: 'ws://...' // 旧版配置
}
})
// v3.0
const human = new Human({
token: 'xxx',
server: {
gateway: 'wss://...' // 新版配置
}
})- 事件名称变更:
// v2.x
human.on('ready', () => {})
// v3.0
import { EmitEvent } from 'qt-human'
human.on(EmitEvent.HUMAN_READY, () => {})- 类型导入:
// 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
支持
如有问题或建议,请联系:
- 邮箱: [email protected]
- 平台: qtworld
