import type { AvatarConfig } from '../types'import { generateContainerId, getPromiseState } from '../utils'import { SDK_CONFIG, APP_CONFIG } from '../constants'interface AvatarCallbacks { onSubtitleOn: (text: string) => void onSubtitleOff: () => void onStateChange: (state: string) => void onVoiceStateChange?: (status: string) => void}class AvatarService { private containerId: string constructor() { this.containerId = generateContainerId() } getContainerId(): string { return this.containerId } async connect(config: AvatarConfig, callbacks: AvatarCallbacks): Promise { const { appId, appSecret } = config const { onSubtitleOn, onSubtitleOff, onStateChange, onVoiceStateChange } = callbacks // 检查容器是否存在 const containerEl = document.getElementById(this.containerId) if (!containerEl) { console.error(`[AvatarService] 容器 #${this.containerId} 不存在!`) throw new Error(`容器 #${this.containerId} 不存在,请确保 AvatarRender 组件已渲染`) } // 构建网关URL const url = new URL(SDK_CONFIG.GATEWAY_URL) url.searchParams.append('data_source', SDK_CONFIG.DATA_SOURCE) url.searchParams.append('custom_id', SDK_CONFIG.CUSTOM_ID) // Promise管理连接状态 let resolve: (value: boolean) => void let reject: (reason?: any) => void const connectPromise = new Promise((res, rej) => { resolve = res reject = rej }) // SDK构造选项 const constructorOptions = { containerId: `#${this.containerId}`, appId, appSecret, enableDebugger: false, gatewayServer: url.toString(), onProxyWidgetEvent: (event: any) => { console.log('SDK事件:', event) }, onStateChange, onMessage: async (error: any) => { const state = await getPromiseState(connectPromise) const plainError = new Error(error.message) if (state === 'pending') { reject(plainError) } }, onVoiceStateChange: (status: string) => { // 当状态为 'end' 时,表示数字人停止说话 if (status.includes('end')) { onVoiceStateChange?.(status) } }, } // 创建SDK实例 const avatar = new window.XmovAvatar(constructorOptions) // 等待初始化 await new Promise(resolve => { setTimeout(resolve, APP_CONFIG.AVATAR_INIT_TIMEOUT) }) // 初始化SDK await avatar.init({ onDownloadProgress: (progress: number) => { console.log(`初始化进度: ${progress}%`) if (progress >= 100) { resolve(true) } }, onClose: () => { onStateChange('') console.log('SDK连接关闭') } }) // 等待连接完成(设置超时避免永久挂起) const connectTimeout = new Promise((_, rej) => { setTimeout(() => rej(new Error('SDK连接超时')), 15000) }) try { await Promise.race([connectPromise, connectTimeout]) console.log('[AvatarService] 连接成功') } catch (error) { console.warn('SDK连接等待结束:', error) // 超时不抛错,可能已经初始化完成但没触发100%回调 } // 连接成功后,注入CSS隐藏SDK字幕 this.injectSubtitleKiller() return avatar } private injectSubtitleKiller(): void { // 注入全局CSS,隐藏SDK自带的字幕元素 console.log('[AvatarService] 字幕隐藏已禁用,排查数字人渲染问题') return } disconnect(avatar: any): void { if (!avatar) return // 移除注入的CSS const killerStyle = document.getElementById('xmov-subtitle-killer') if (killerStyle) killerStyle.remove() try { avatar.stop() avatar.destroy() } catch (error) { console.error('断开连接时出错:', error) } }}export const avatarService = new AvatarService()关键配置项说明:enableDebugger: false:生产环境关闭调试模式,避免控制台输出过多 SDK 内部日志gatewayServer:魔珐星云网关功能器地址,支持自定义指标源,是参数流架构的核心入口onDownloadProgress:初始化进度回调,100% 时表示资源加载完成,连接就绪onVoiceStateChange:语音状态回调,当数字人说话结束时触发,用于流式播报的段落控制getPromiseState:检查连接 Promise 的状态,避免在已 resolve 的情况下重复 reject4.4 llm.ts - 大语言模型服务基于 OpenAI SDK 封装,支持火山引擎豆包大模型。提供普通对话和流式对话两种模式,流式模式支持实时逐字输出。
全程不超过 5 分钟。第二层(摘要):每周 3-5 篇。不是每天都要写摘要,是读完确认有价值的才写。
而且这里的风景太美了,我会推荐欧洲的朋友来”。有专家表示,欧洲高温成为“现在就去中国”的催化剂。
C# 的 NativeAOT(Ahead-of-Time)编译模式,正在从底层运行时层面破解这一困局。
10+ 可用 X | Y) def parse_value(value: str) -> Union[int, float, str]: try: return int(value) except ValueError: try: return float(value) except ValueError: return value # Python 3.