feat: 添加华为交互数字人SDK集成示例项目

添加完整的华为交互数字人SDK集成示例项目,包括:
- 项目配置文件package.json
- 详细说明文档README.md
- 主页面index.html
- 核心逻辑脚本main.js
- 样式文件main.css
- SDK类型定义文件HwICSUiSdk.d.ts
- 相关图片资源
This commit is contained in:
2026-04-29 16:58:23 +08:00
commit bd98ae0083
14 changed files with 1942 additions and 0 deletions

195
README.md Normal file
View File

@@ -0,0 +1,195 @@
# 华为交互数字人 - HwICSUiSdk 标准集成项目
这是一个基于 HwICSUiSdk 的华为交互数字人标准集成示例项目,展示了如何在前端项目中集成和使用数字人交互功能。
## 📁 项目结构
```
antiwear-web/
├── hwICSUiSdk/ # SDK 目录(已提供)
│ ├── HwICSUiSdk.esm.js # ESM 模块入口
│ ├── HwICSUiSdk.d.ts # TypeScript 类型定义
│ ├── HwICSUiSdk.css # SDK 样式
│ └── ...
├── scripts/
│ └── main.js # 主逻辑代码
├── styles/
│ └── main.css # 自定义样式
├── index.html # 主页面
└── README.md # 项目说明
```
## 🚀 快速开始
### 1. 环境要求
- 现代浏览器(Chrome、Firefox、Edge、Safari 最新版本)
- 支持 WebRTC 和 WebSocket
- 需要麦克风权限(用于语音交互)
### 2. 配置参数
#### 默认参数(已预填)
| 参数 | 默认值 |
|------|--------|
| 服务器地址 | `metastudio-api.cn-north-4.myhuaweicloud.com` |
| 机器人ID | `2c9084bf9c9cc1c0019db2c4ca2a5c89` |
#### 需要填写的参数
| 参数 | 说明 | 必填 |
|------|------|------|
| 一次性授权码 | 用于身份验证的临时授权码 | 是 |
> **如何获取授权码**:通过华为云 MetaStudio 控制台或 API 获取一次性授权码(有效期5分钟)
### 3. 启动项目
由于使用了 ES Modules,需要通过本地服务器运行:
```bash
# 使用 Python 3
python -m http.server 8080
# 或使用 Node.js 的 http-server
npx http-server -p 8080
# 或使用 VS Code Live Server 插件
```
然后在浏览器中访问 `http://localhost:8080`
### 4. 使用流程
1. **获取授权码**:通过华为云控制台获取一次性授权码
2. **填写配置**:在页面中输入授权码
3. **初始化 SDK**:点击 "初始化 SDK" 开始使用
## 📖 功能说明
### 核心功能
1. **SDK 初始化与销毁**
- 初始化:配置服务器地址、机器人ID等参数
- 销毁:释放资源,清理数字人实例
2. **对话控制**
- 开始对话:启动数字人交互会话
- 停止对话:结束当前会话
3. **语音交互**
- 开始说话:启动语音识别,用户语音输入
- 停止说话:结束语音识别
4. **文本交互**
- 发送文本消息给数字人
5. **其他控制**
- 打断说话:立即中断数字人当前说话
- 静音/取消静音:控制远程音频
### 事件监听
SDK 提供了丰富的事件回调:
| 事件 | 说明 |
|------|------|
| ERROR | 错误事件 |
| JOB_INFO_CHANGE | 任务信息变更 |
| SPEAKING_START | 数字人开始说话 |
| SPEAKING_STOP | 数字人停止说话 |
| SPEECH_RECOGNIZED | 语音识别结果 |
| SEMANTIC_RECOGNIZED | 语义识别结果 |
| ENTER_SLEEP | 进入休眠状态 |
| ENTER_ACTIVE | 进入激活状态 |
| JOB_END | 任务结束 |
## 🔧 开发指南
### 引入 SDK
```javascript
import { HwICSUiSdk, ICSEventKey, InteractionMode, ConfigKey } from './hwICSUiSdk/HwICSUiSdk.esm.js';
```
### 初始化 SDK
```javascript
await HwICSUiSdk.create({
serverAddress: 'your-server-address',
containerId: 'digitalHumanBox',
robotId: 'your-robot-id',
onceCode: 'your-once-code',
config: {
[ConfigKey.ENABLE_CAPTION]: true,
[ConfigKey.ENABLE_CHAT_BTN]: true
}
});
```
### 注册事件监听
```javascript
HwICSUiSdk.addEventListeners({
[ICSEventKey.SPEAKING_START]: () => {
console.log('数字人开始说话');
},
[ICSEventKey.SPEECH_RECOGNIZED]: (data) => {
console.log('识别结果:', data);
}
});
```
### 开始对话
```javascript
await HwICSUiSdk.startChat({
interactionMode: InteractionMode.AUDIO
});
```
### 发送文本消息
```javascript
await HwICSUiSdk.sendTextQuestion({
text: '你好,数字人!'
});
```
## ⚙️ 配置选项
SDK 支持多种配置项:
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| ENABLE_CAPTION | boolean | true | 启用字幕显示 |
| ENABLE_CHAT_BTN | boolean | true | 启用聊天按钮 |
| ENABLE_HOT_ISSUES | boolean | true | 启用热点问题 |
| SHOW_LANGUAGE_UI | boolean | true | 显示语言切换UI |
| ENABLE_DEVICE_SETTING | boolean | true | 启用设备设置 |
| ENABLE_NETWORK_CHECK | boolean | true | 启用网络检查 |
| AUTO_PLAY_REMOTE_VIDEO | boolean | true | 自动播放远程视频 |
| DEFAULT_MUTE_REMOTE_AUDIO | boolean | false | 默认静音远程音频 |
## 📝 注意事项
1. **HTTPS 要求**:在生产环境中,WebRTC 功能需要在 HTTPS 环境下运行
2. **浏览器权限**:首次使用需要授权麦克风和摄像头权限
3. **资源释放**:页面卸载时务必调用 `destroy()` 方法释放资源
4. **错误处理**:建议对所有异步操作添加错误处理
## 🔍 调试技巧
1. 使用浏览器开发者工具查看控制台日志
2. 调整日志级别查看详细信息:
```javascript
HwICSUiSdk.setLogLevel('debug'); // debug | info | warn | error | none
```
3. 查看网络面板监控 WebSocket 连接
## 📄 许可证
本项目仅供学习和参考使用。
## 🤝 技术支持
如有问题,请参考 HwICSUiSdk 官方文档或联系技术支持。

11
hwICSUiSdk/HwICSUiSdk.css Normal file

File diff suppressed because one or more lines are too long

478
hwICSUiSdk/HwICSUiSdk.d.ts vendored Normal file
View File

@@ -0,0 +1,478 @@
export declare class HwICSUiSdk {
static version: string;
static buildTime: string;
static checkBrowserSupport(strictCheckBrowser?: boolean): Promise<boolean>;
static checkTransparentBgSupport(): Promise<boolean>;
static create(param: CreateParam): Promise<void>;
static setConfig(config: ConfigMap): Promise<void>;
static setLogLevel(logLevel: LogLevel): void;
static addEventListeners(eventMap: EventMap): void;
static destroy(): Promise<void>;
static getJobInfo(): Promise<JobInfo>;
static startSpeak(): Promise<UISdkResult>;
static stopSpeak(): Promise<UISdkResult>;
static startUserSpeak(param?: StartUserSpeakParam): Promise<UISdkResult>;
static stopUserSpeak(): Promise<UISdkResult>;
static unmuteRemoteAudio(): Promise<boolean>;
static muteRemoteAudio(): Promise<boolean>;
static interruptSpeaking(): Promise<UISdkResult>;
static sendTextQuestion(param: TextQuestionParam): Promise<TextQuestionResult>;
static startChat(param: {
interactionMode: InteractionMode;
wakeupType?: WakeupType;
preCheckPermissions?: boolean;
}): Promise<ChatResult>;
static stopChat(): Promise<ChatResult>;
static activeInteractionMode(): Promise<InteractionModeResult>;
static interactionModeSwitch(param: {
interactionMode: InteractionMode;
}): Promise<InteractionModeResult>;
static getLanguageInfo(): LanguageInfo;
static changeLanguage(param: {
langCode: LanguageCode;
}): Promise<UISdkResult>;
static sendDrivenText(param: TextDrivenParam): Promise<ChatResult>;
static startLocalWakeupRecord(): Promise<void>;
static stopLocalWakeupRecord(): Promise<void>;
static initResourcePath(param: ResourcePath): Promise<void>;
static setThirdPartyConfig(config: ThirdPartyConfig): Promise<UISdkResult>;
static sendCustomLocalAudio(audioData: AudioData): Promise<void>;
static playRemoteVideo(): Promise<UISdkResult | void>;
static startStreamRecord(): Promise<void>;
static stopStreamRecord(): Promise<void>;
static subscribe(): Promise<void>;
static unSubscribe(): Promise<void>;
}
export declare enum RetCodes {
SUCCESS = "MSS.00000000",
NORMAL_FAILED = "MSS.00000001",
SERVER_ERROR = "MSS.00000002",
ERROR_PARAM = "MSS.00000003",
AUTH_FAILED = "MSS.00000004",
DIALOG_NOT_EXIST = "MSS.47010001",
RPC_ACTION_NOT_EXIST = "MSS.47010002",
ICS_EXCEPTION = "MSS.47010003",
REQUEST_TOO_FREQUENTLY = "MSS.47010004",
GET_DAC_ASSERT_FAIL = "MSS.47010005",
GET_DHM_STYLE_FAIL = "MSS.47010006",
GET_SFS_CACHE_FAIL = "MSS.47010007",
TENANT_CLEANUP_JOB_STOPPED = "MSS.47010008",
NO_PERMISSION_USE = "MSS.47010009",
CHAT_SERVICE_IS_ACTIVED = "MSS.47010010",
CHAT_SERVICE_IS_INACTIVED = "MSS.47010011",
DIALOG_HAS_FINISH = "MSS.47010012",
CHAT_SERVICE_CONCURRENCY_NOT_ENOUGH = "MSS.47010013",
DIALOG_STATE_UNAVAILABLE = "MSS.47010014",
SEND_EVENT_CALLBACK_FAIL = "MSS.47010015",
DIALOG_WAIT_IS_NULL = "MSS.47010016",
SKILL_NAME_EXIST = "MSS.47010020",
SKILL_IDENTIFY_EXIST = "MSS.47010021",
SKILL_NOT_EXIST = "MSS.47010022",
SKILL_NO_PERMISSION = "MSS.47010023",
INTENT_NAME_EXIST = "MSS.47010024",
INTENT_IDENTIFY_CREATE_FAILED = "MSS.47010025",
INTENT_COUNT_EXCEED_MAX_VALUE = "MSS.47010026",
INTENT_NOT_EXIST = "MSS.47010027",
INTENT_NO_PERMISSION = "MSS.47010028",
QUESTION_EXIST = "MSS.47010029",
QUESTION_NOT_EXIST = "MSS.47010030",
QUESTION_NO_PERMISSION = "MSS.47010031",
QUESTION_BATCH_LIST_EMPTY = "MSS.47010032",
ROBOT_NAME_EXIST = "MSS.47010033",
ROBOT_NOT_EXIST = "MSS.47010034",
ROBOT_NO_PERMISSION = "MSS.47010035",
EXPORT_SKILL_FAILED = "MSS.47010036",
START_SMART_CHAT_FAILED = "MSS.47010037",
STOP_SMART_CHAT_FAILED = "MSS.47010038",
SHOW_SMART_CHAT_FAILED = "MSS.47010039",
DIALOG_CONCURRENT_EXCEED_MAX_VALUE = "MSS.47010040",
ACTIVE_CODE_AUTH_FAILED = "MSS.47010043",
ACTIVE_CODE_AUTH_LOCKED = "MSS.47010044",
ACTIVE_CODE_AUTH_EXPIRED = "MSS.47010045",
ONCE_CODE_AUTH_FAILED = "MSS.47010046",
ONCE_CODE_AUTH_LOCKED = "MSS.47010047",
GENERATE_SLIDE_VERIFICATON_FAILED = "MSS.47010048",
SLIDE_VERIFICATON_NOT_EXIST = "MSS.47010049",
SLIDE_VERIFICATON_VERIFY_FAILED = "MSS.47010050",
SLIDE_VERIFICATON_VERIFY_FAILED_AND_EXCEED_MAX_TIMES = "MSS.47010051",
DIALOG_NO_PERMISSION = "MSS.47010063",
FAILED_TO_QUERY_TENANT_ID = "MSS.47010065",
HOT_QUESTION_NOT_EXIST = "MSS.47010066",
WS_INTERNAL_ERROR = "MSS.47010100",
MESSAGE_PARSE_ERROR = "MSS.47010101",
WS_ROBOT_ID_IS_EMPTY = "MSS.47010102",
UNKNOWN_ACTION = "MSS.47010103",
CONNECT_AIUI_TIMEOUT = "MSS.47010104",
CONNECT_APP_TIMEOUT = "MSS.47010104",
AUTHTOKEN_INVALID = "MSS.47010105",
QUERYPARAM_INVALID = "MSS.47010106",
CLIENT_ERROR_MESSAGE = "MSS.47010107",
WEBCLIENT_START_CHAT_WORKER_DISCONNECT = "MSS.47010111",
CHANNEL_RECORED_NOT_FOUND = "MSS.47010119",
INTERACTIVE_NUMBER_IS_INSUFFICIENT = "MSS.47010120",
TOKEN_NUMBER_IS_INSUFFICIENT = "MSS.47010121",
RPC_CALL_FAILED = "MSS.47010122",
AIUI_UNAUTHORIZED = "MSS.47010124",
SPARK_UNAUTHORIZED = "MSS.47010125",
MOBVOI_QUANTITY_NOT_ENOUGH = "MSS.47010126",
MOBVOI_UNAUTHORIZED = "MSS.47010127",
MOBVOI_ROLE_ID_IS_INVALID = "MSS.47010128",
THIRD_PARTY_LANGUAGE_MODEL_EMPTY = "MSS.47010130",
THIRD_PARTY_LANGUAGE_MODEL_URL_EMPTY = "MSS.47010131",
THIRD_PARTY_LANGUAGE_MODEL_URL_INVALID = "MSS.47010134",
IFLY_APPLICATION_SCENE_CONFIG_ERROR = "MSS.47010136",
SIS_AUTHORIZATION_FAILED = "MSS.47010141",
CURRENT_LANGUAGE_NOT_SUPPORT_RECOGNITION = "MSS.47010143",
ICS_NOT_IN_TEXT_MODE = "MSS.47010144",
ICS_TEXT_QUESTION_LENGTH_LIMITED = "MSS.47010145",
ICS_TEXT_QUESTION_IS_EMPTY = "MSS.47010146",
ICS_TEXT_QUESTION_NOT_IN_WAIT_QUESTION_STATUS = "MSS.47010147",
ICS_INVALID_INTERACTION_MODE = "MSS.47010148",
MOBVOI_REQUEST_FLOW_LIMITED = "MSS.47010150",
UPLOAD_LOG_COUNT_EXCEED_MAX_COUNT = "MSS.47010151",
ICS_EXTEND_PARAM_INVALID = "MSS.47010152",
PACIFY_WORDS_EXIST = "MSS.47010154",
PACIFY_WORDS_COUNT_EXCEED_MAX_VALUE = "MSS.47010155",
PACIFY_WORDS_NOT_EXIST = "MSS.47010156",
PACIFY_WORDS_NO_PERMISSION = "MSS.47010157",
PACIFY_WORDS_INTENT_NOT_SUPPORT = "MSS.47010158",
ICS_ROBOT_ROOM_ID_EMPTY = "MSS.47010163",
SINGLE_TENANT_WEBSOCKET_LINKS_EXCEEDS = "MSS.47015005",
WEBSOCKET_LINKS_EXCEEDS = "MSS.47015006",
ICS_UNMATCHED_APP_TYPE_AIUI = "MSS.47015008",
ICS_UNMATCHED_APP_TYPE_SPARK = "MSS.47015009",
ICS_THIRD_PARTY_DRIVER_NOT_ALLOWED_WEB_START_CHAT = "MSS.47015010",
ICS_MEDIA_SERVICE_ADDR_IS_EMPTY = "MSS.47015011",
ICS_MEDIA_NEGOTIATE_FAILED = "MSS.47015012",
ICS_SIS_FROZEN_ERROR = "MSS.47015015",
ICS_THIRD_PARTY_DRIVE_NOT_ALLOWED_THIS_WEB_MESSAGE = "MSS.47015017",
ICS_MOBVOI_ASR_UNAUTHORIZED = "MSS.47015018",
ICS_ASR_WAIT_VOICE_TIME_OUT = "MSS.47015019",
ICS_SIS_SERVICE_NOT_SUBSCRIBE = "MSS.47015028",
ICS_AUDIO_REPORT_TASK_EXIST = "MSS.47015029",
ICS_CREATE_AUDIO_RECORD_TASK_FAILED = "MSS.47015030",
ICS_AUDIO_RECORD_NOT_ENABLE = "MSS.47015031"
}
export declare enum MPSRetCodes {
SUCCESS = "00000000",
FAILED = "00000001",
INTERNAL_ERROR = "00000002",
INVALID_PARAMETER = "00000003",
ILLEGAL_ACCESS = "00000004",
MPS_NAME_INCLUDE_INVALID_SYMBOL = "20010001",
NO_MATCH_TEMPLATE = "20010003",
REFRESH_ASSET_FAILED = "20010010",
NO_ENOUGH_RESOURCE = "20010035",
NATS_PUBLISH_FAILURE = "20010040",
NATS_SUBSCRIBE_FAILURE = "20010041",
NATS_DELETE_SUBJECT_FAILURE = "20010042",
NATS_INTERNAL_ERROR = "20010043",
DIGITAL_HUMAN_NOT_AVAILABLE = "20010044",
MPS_ROOM_INFO_NOT_FOUND = "20010045",
ROOM_ID_JOB_EXIST = "20010046",
ASSET_NOT_EXIST = "20010047",
ASSET_INFO_INVALID = "20010050",
INVALID_COMMAND = "20010053",
QUOTA_INSUFFICIENT = "20010056",
REQUEST_TMS_UNAVAILABLE = "20010060",
LIVE_QUOTA_INSUFFICIENT = "20010065",
MPS_CHANGE_FINISHED_JOB_STATE_FAILED = "20010066",
AUDIO_NOT_FOUND_URL = "20010067",
MPS_FIELD_VERIFY_FAIL = "20010070",
MPS_ASSET_ID_UNAVAILABLE = "20010071",
MPS_JOB_NOT_EXISTED = "20010074",
CHAT_QUOTA_INSUFFICIENT = "20010075",
MPS_TASK_EXPIRED = "20010076",
CONVERSATION_RESOURCE_FULL = "20010089",
TONE_RESOURCE_ERROR = "20010131",
CHAT_JOBS_NUM_IS_OVER_CONCURRENCY = "20010135",
CHAT_SUM_CONCURRENCY_OR_JOBS_NUM_IS_OVER_RESOURCES_NUM = "20010139",
MPS_ASSET_ID_INVALID = "20010140",
MPS_JOB_HEARTBEAT_FAILED = "20010146",
MPS_ROOM_NAME_DUPLICATE = "20010148"
}
export declare enum WorkerRetCodes {
DHICIS_ERROR = "47000001",
DHICIS_INFER_SERVICE_ERROR = "47000002",
DHICIS_TASK_READY_ERROR = "47000003",
DHICIS_CREATE_INFER_MODEL_ERROR = "47000004",
DHICIS_WORKER_JOIN_ROOM_FAIL = "47000005",
DHICIS_MANAGER_SERVICE_ERROR = "47000006",
DHICIS_JOB_STOP_BY_COMMAND = "47000007",
DHICIS_FSM_STATE_TIME_OUT = "47000009",
DHICIS_ICS_ERROR = "47000010",
DHICIS_RTSA_ROOM_EMPTY = "47000011",
DHICIS_RTSA_REPORT_LEAVE_ROOM = "47000012",
DHICIS_MODEL_FILE_DOWN_FAIL = "47000013",
DHICIS_BACKGROUND_FILE_DOWN_FAIL = "47000014",
DHICIS_WORKER_JOIN_ROOM_TIMEOUT = "47000015",
DHICIS_JOIN_ROOM_TASK_FAIL = "47000016",
DHICIS_TTS_PREHEAT_TASK_FAIL = "47000017",
DHICIS_DATA_PACKAGE_IS_NOT_ZIP = "47000018",
DHICIS_TOO_MANY_ENTRIES_IN_PACKAGE = "47000019",
DHICIS_PACKAGE_TOO_LARGE = "47000020",
DHICIS_FILE_IN_PACKAGE_TOO_LARGE = "47000021",
DHICIS_UNEXPECTED_DIRECTORY_IN_PACKAGE = "47000022",
DHICIS_PACKAGE_ENTRY_ILLEGAL_NAME = "47000023",
DHICIS_FILE_NAME_INVALID = "47000024",
DHICIS_JOB_INFO_PARA_INVALID = "47000025",
DHICIS_FSM_STATE_ERROR = "47000027",
DHICIS_TTS_CONVERT_FAIL = "47000029",
DHICIS_NO_IDLE_WORK = "47002000",
DHICIS_NO_RESOURCE = "47002001",
DHICIS_RESOURCE_LIMITED = "47002002"
}
export declare enum WBWRetCodes {
WBW_AGENT_ROLE_NOT_EXISTED = "47050001",
WBW_LLM_NOT_EXISTED = "47050002",
WBW_LLM_INVALID_MODEL = "47050003",
WBW_LLM_ACCESS_FAILED = "47050004"
}
export declare enum WBMRetCodes {
WBM_LLM_CONFIG_NOT_EXIST = "47030103",
WBM_LLM_CONFIG_NO_PERMISSION = "47030104",
WBM_PLUGIN_CONFIG_NOT_EXIST = "47030114",
WBM_PLUGIN_CONFIG_NO_PERMISSION = "47030115"
}
export declare enum WBCRetCodes {
WBC_ROLE_NOT_EXIST = "47040041",
WBC_CONFIG_NO_PERMISSION = "47040042"
}
export declare enum TTSCRetCodes {
TTS_ACCESS_CONFIG_ERROR = "20050038",
TTS_INSUFFICIENT_ACCOUNT_BALANCE = "20050039",
TTS_INVALID_TEXT = "20050040",
TTS_NO_ACCESS_CONFIG = "20050029",
TTS_TO_LONG_WORD_TEXT = "20051001",
TTS_VOICE_ASSET_UNACTIVATED = "20050042",
TTS_UNSUPPORTED_LANGUAGE_TEXT = "20050064"
}
export declare enum SDKErrorCode {
MPS_JOB_STATE_NULL = "999000001",
MPS_JOB_STATE_ERROR = "999000002",
RTC_PROMISE_ERROR = "999100001",
RTC_CATCH_ERROR = "999100002",
RTC_KICK_OUT = "999100003",
RTC_AUDIO_DEVICE_ERROR = "999100004",
RTC_PEER_LEAVE = "999100005",
RTC_KICK_ROOM = "999100006",
RTC_NOT_INIT = "999100007",
RTC_JOIN_TIMEOUT = "999100008",
WEBSOCKET_CONNECT_ERROR = "999200001",
WEBSOCKET_RESPONSE_TIMEOUT = "999200002",
WEBCLIENT_WEBSOCKET_CHANNEL_ABNORMAL = "999200003",
WEBSOCKET_NETWORK_ERROR = "999200004",
WEBSOCKET_KICK_OUT = "999200005",
WEBSOCKET_CONNECT_TIMEOUT = "999200006",
UI_NO_PARENT = "999300001",
UI_PARAM_ERROR = "999300002",
UI_LANGUAGE_LIST_EMPTY = "999300003",
UI_LANGUAGE_CODE_NOT_SUPPORT = "999300004",
METHOD_PROHIBITED_EXECUTION = "999400001",
METHOD_NOT_ALLOW_NOT_AUTH = "999400002",
WORKER_JOB_STOP_BY_MUTE_OVERTIME = "999400003",
METHOD_REPEAT_CALLS = "999400004",
JOB_NOT_READY = "999400005"
}
export declare enum ICSEventKey {
ERROR = "error",
JOB_INFO_CHANGE = "jobInfoChange",
SPEAKING_START = "speakingStart",
SPEAKING_STOP = "speakingStop",
SPEECH_RECOGNIZED = "speechRecognized",
SEMANTIC_RECOGNIZED = "semanticRecognized",
ENTER_SLEEP = "enterSleep",
ENTER_ACTIVE = "enterActive",
LANGUAGE_INFO_CHANGE = "languageInfoChange",
JOB_END_MENTION = "jobEndMention",
JOB_END = "jobEnd"
}
export declare enum WakeupType {
VOICE = "VOICE",
RADAR = "RADAR",
INFRARED = "INFRARED",
CAMERA = "CAMERA"
}
export declare enum LanguageCode {
ZH_CN = "zh_CN",
EN_US = "en_US"
}
export declare enum VoiceLanguageCode {
CN = "CN",
EN = "EN"
}
export declare enum ConfigKey {
ENABLE_CAPTION = "enableCaption",
ENABLE_CHAT_BTN = "enableChatBtn",
ENABLE_HOT_ISSUES = "enableHotIssues",
ENABLE_BUSINESS_TRACK = "enableBusinessTrack",
ENABLE_WEAK_ERROR_INFO = "enableWeakErrorInfo",
ENABLE_JOB_CACHE = "enableJobCache",
USE_DEFAULT_BACKGROUND = "useDefaultBackground",
ENABLE_NETWORK_CHECK = "enableNetworkCheck",
ENABLE_DEVICE_SETTING = "enableDeviceSetting",
SHOW_LANGUAGE_UI = "showLanguageUI",
DESTROY_BEFORE_CLOSE = "destroyBeforeClose",
ENABLE_LOCAL_WAKEUP = "enableLocalWakeup",
ENABLE_VAD_INTERRUPT = "enableVadInterrupt",
FIRST_CREATE_LOCAL_STREAM = "firstCreateLocalStream",
ENABLE_STORAGE_CACHE = "enableStorageCache",
ENABLE_MEDIA_VIEWER = "enableMediaViewer",
DEFAULT_MUTE_REMOTE_AUDIO = "defaultMuteRemoteAudio",
AUTO_PLAY_REMOTE_VIDEO = "autoPlayRemoteVideo",
ENABLE_COLLECT_AUDIO_DEMAND = "enableCollectAudioDemand",
ENABLE_ORIGIN_CUT_ALGORITHM = "enableOriginCutAlgorithm",
ENABLE_VERBATIM = "enableVerbatim",
USE_WORKER_WAKEUP = "useWorkerWakeup",
PRE_CHECK_PERMISSIONS = "preCheckPermissions"
}
export declare enum SparkRTCErrorCode {
RTC_ERR_CODE_SUCCESS = "0",
RTC_ERR_CODE_RTC_SDK_ERROR = "90000001",
RTC_ERR_CODE_WAIT_RSP_TIMEOUT = "90000004",
RTC_ERR_CODE_INVALID_PARAMETER = "90000005",
RTC_ERR_CODE_INVALID_OPERATION = "90100001",
RTC_ERR_CODE_NOT_SUPPORT_MEDIA_DEVICES = "90100002",
RTC_ERR_CODE_NO_AVAILABLE_DEVICES = "90100003",
RTC_ERR_CODE_NO_AVAILABLE_VIDEO_INPUT_DEVICES = "90100004",
RTC_ERR_CODE_NO_AVAILABLE_AUDIO_INPUT_DEVICES = "90100005",
RTC_ERR_CODE_NO_AVAILABLE_AUDIO_OUTPUT_DEVICES = "90100006",
RTC_ERR_CODE_STATUS_ERROR = "90100007",
RTC_ERR_CODE_WEBSOCKET_NOT_CONNECTED = "90100008",
RTC_ERR_CODE_WAIT_CONFIG_FAIL = "90100009",
RTC_ERR_CODE_PUBLISH_RESPONSE_FAIL = "90100010",
RTC_ERR_CODE_REGION_NOT_COVERED = "90100011",
RTC_ERR_CODE_WEBSOCKET_CONNECT_TIMEOUT = "90100012",
RTC_ERR_CODE_WEBSOCKET_RECONNECT_TIMEOUT = "90100013",
RTC_ERR_CODE_WEBSOCKET_NOT_OPEN = "90100014",
RTC_ERR_CODE_WEBSOCKET_INTERRUPTED = "90100015",
RTC_ERR_CODE_WEBSOCKET_CONNECT_ERROR = "90100016",
RTC_ERR_CODE_CAPTURE_PERMISSION_DENIED = "90100017",
RTC_ERR_CODE_CAPTURE_OVER_CONSTRAINED = "90100018",
RTC_ERR_CODE_CAPTURE_DEVICE_NOT_FOUND = "90100019",
RTC_ERR_CODE_CAPTURE_DEVICE_NOT_READABLE = "90100020",
RTC_ERR_CODE_PLAY_NOT_ALLOW = "90100021",
RTC_ERR_CODE_ROLE_NO_PERMISSION = "90100022",
RTC_ERR_CODE_ANSWER_SDP_INVALID = "90100023",
RTC_ERR_CODE_MEDIA_UPSTREAM_UNSUPPORTED = "90100024",
RTC_ERR_CODE_WEBRTC_UNSUPPORTED = "90100025",
RTC_ERR_CODE_MEDIA_NETWORK_ERROR = "90100026",
RTC_ERR_CODE_CLIENT_RELAY_ROOM_OVER_MAXNUM = "90100027",
RTC_ERR_CODE_CLIENT_RELAY_JOINER_OVER_MAXNUM = "90100028",
RTC_ERR_CODE_ROOM_STREAM_STATUS_PAUSED = "90100029",
RTC_ERR_CODE_SIGNATURE_EXPIRED = "90100030",
RTC_ERR_CODE_SIGNATURE_INVALID = "90100031",
RTC_ERR_CODE_WINDOW_OR_NAVIGATOR_UNSUPPORTED = "90100032",
RTC_ERR_CODE_CHROME_VERSION_UNSUPPORTED = "90100033",
RTC_ERR_CODE_WEBSOCKET_UNSUPPORTED = "90100034",
RTC_ERR_CODE_WEBRTC_AVC_ENCODE_UNSUPPORTED = "90100035",
RTC_ERR_CODE_WEBRTC_AVC_DECODE_UNSUPPORTED = "90100036",
RTC_ERR_CODE_WEBRTC_WECHAT_UNSUPPORTED = "90100037",
RTC_ERR_CODE_AUDIO_ERROR = "90100038",
RTC_ERR_CODE_RTC_ACS = "90100100",
RTC_ERR_CODE_RTC_CONTROL_ERROR = "90100200",
RTC_ERR_CODE_SFU_ERROR = "90100600",
RTC_WEBSOCKET_ERR_NEED_REJOIN = "4005"
}
export declare enum InteractionMode {
AUDIO = "AUDIO",
TEXT = "TEXT"
}
export type HttpErrorCode = MPSRetCodes | RetCodes | WorkerRetCodes | WBWRetCodes | SDKErrorCode | TTSCRetCodes;
export declare enum MediaType {
VIDEO = "VIDEO",
IMAGE = "IMAGE"
}
export type EventMap = {
[key in ICSEventKey]?: Function;
};
export type ConfigMap = {
[key in ConfigKey]?: boolean;
};
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
export interface InnerInitParam {
robotId?: string;
serverAddress: string;
taskUrl?: string;
config?: ConfigMap;
containerId: string;
logLevel?: LogLevel;
eventListeners?: EventMap;
extendParamStr?: string;
}
export interface CreateParam extends InnerInitParam {
onceCode?: string;
}
export interface UISdkResult {
result: boolean;
errorCode?: SparkRTCErrorCode | SDKErrorCode | HttpErrorCode;
errorMsg?: string;
}
export interface InteractionModeResult extends UISdkResult {
interactionMode?: InteractionMode;
}
export interface TextQuestionResult extends UISdkResult {
chatId: string;
}
export interface ChatResult extends UISdkResult {
chatId: string;
sessionId?: string;
}
export interface JobInfo {
jobId: string;
websocketAddr: string;
jobInfo?: SmartChat | SmartChatState | null;
}
export interface LanguageInfo extends UISdkResult {
languageList?: Array<VoiceLanguageItem>;
currentLanguage?: LanguageCode;
}
export interface JobEndMentionInfo {
jobId: string;
restTime: number;
}
export interface JobEndInfo {
jobId: string;
reason: string;
}
export interface TextDrivenParam {
text: string;
isLast?: boolean;
}
export interface TextQuestionParam {
text: string;
questionParam?: string;
}
export interface ResourcePath {
wasmPath: string;
dataPath: string;
initModel?: boolean;
useWorker?: boolean;
}
export interface UHanConfig {
origin?: string;
enable?: boolean;
sleepDelay?: number;
showVideoUi?: boolean;
host?: string;
}
export interface CustomRecordConfig {
enable?: boolean;
}
export interface ThirdPartyConfig {
uHan?: UHanConfig;
uHanV2?: UHanConfig;
customRecord?: CustomRecordConfig;
}
export interface AudioData {
base64Data?: string;
uint8Arr?: Uint8Array;
time?: number;
}
export interface VoiceLanguageItem {
language: LanguageCode;
language_desc: string;
}
export interface StartUserSpeakParam {
preCheckPermissions?: boolean;
}

File diff suppressed because one or more lines are too long

1
hwICSUiSdk/HwICSUiSdk.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
hwICSUiSdk/images/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

1
hwICSUiSdk/modelData.js Normal file

File diff suppressed because one or more lines are too long

6
hwICSUiSdk/package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "HwICSUiSdk",
"version": "5.0.4",
"main": "HwICSUiSdk.js",
"types": "HwICSUiSdk.d.ts"
}

1
hwICSUiSdk/wasmData.js Normal file

File diff suppressed because one or more lines are too long

143
index.html Normal file
View File

@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>华为交互数字人 - HwICSUiSdk 演示</title>
<link rel="stylesheet" href="./styles/main.css">
</head>
<body>
<div class="app-container">
<!-- 头部 -->
<header class="header">
<div class="logo">
<span class="logo-icon">🎭</span>
<h1>华为交互数字人</h1>
</div>
<div class="status-bar">
<span id="connectionStatus" class="status disconnected">未连接</span>
<span id="sdkVersion" class="version"></span>
</div>
</header>
<!-- 主内容区 -->
<main class="main-content">
<!-- 配置面板 -->
<section class="config-panel">
<h2>📋 连接配置</h2>
<!-- 基本配置 -->
<div class="config-section">
<h3>基本配置</h3>
<div class="form-group">
<label for="serverAddress">服务器地址</label>
<input type="text" id="serverAddress" placeholder="请输入服务器地址" value="metastudio-api.cn-north-4.myhuaweicloud.com">
</div>
<div class="form-group">
<label for="robotId">机器人ID</label>
<input type="text" id="robotId" placeholder="请输入机器人ID" value="2c9084bf9c9cc1c0019db2c4ca2a5c89">
</div>
</div>
<!-- 授权码配置 -->
<div class="config-section">
<h3>授权码</h3>
<div class="form-group">
<label for="onceCode">一次性授权码</label>
<input type="text" id="onceCode" placeholder="请输入一次性授权码" value="">
<small class="field-hint">通过华为云控制台获取,有效期5分钟,只能使用一次</small>
</div>
</div>
<div class="form-actions main-actions">
<button id="initBtn" class="btn btn-primary">初始化 SDK</button>
<button id="destroyBtn" class="btn btn-danger" disabled>销毁 SDK</button>
</div>
</section>
<!-- 数字人显示区 -->
<section class="digital-human-container">
<h2>🎬 数字人显示</h2>
<div id="digitalHumanBox" class="digital-human-box">
<div class="placeholder">
<span class="placeholder-icon">🤖</span>
<p>请先初始化 SDK 并创建数字人</p>
</div>
</div>
<!-- 控制按钮 -->
<div class="control-panel">
<div class="control-group">
<h3>对话控制</h3>
<div class="button-row">
<button id="startChatBtn" class="btn btn-success" disabled>
<span class="icon">▶️</span> 开始对话
</button>
<button id="stopChatBtn" class="btn btn-warning" disabled>
<span class="icon">⏹️</span> 停止对话
</button>
</div>
</div>
<div class="control-group">
<h3>语音交互</h3>
<div class="button-row">
<button id="startSpeakBtn" class="btn btn-info" disabled>
<span class="icon">🎤</span> 开始说话
</button>
<button id="stopSpeakBtn" class="btn btn-secondary" disabled>
<span class="icon">🛑</span> 停止说话
</button>
</div>
</div>
<div class="control-group">
<h3>文本交互</h3>
<div class="text-input-row">
<input type="text" id="textInput" placeholder="输入文本消息..." disabled>
<button id="sendTextBtn" class="btn btn-primary" disabled>
<span class="icon">📤</span> 发送
</button>
</div>
</div>
<div class="control-group">
<h3>其他控制</h3>
<div class="button-row">
<button id="interruptBtn" class="btn btn-warning" disabled>
<span class="icon">⚡</span> 打断说话
</button>
<button id="toggleAudioBtn" class="btn btn-secondary" disabled>
<span class="icon">🔊</span> 静音/取消静音
</button>
</div>
</div>
</div>
</section>
<!-- 日志面板 -->
<section class="log-panel">
<h2>📜 事件日志</h2>
<div class="log-toolbar">
<button id="clearLogBtn" class="btn btn-small">清空日志</button>
<select id="logLevel">
<option value="debug">Debug</option>
<option value="info" selected>Info</option>
<option value="warn">Warn</option>
<option value="error">Error</option>
</select>
</div>
<div id="logContainer" class="log-container"></div>
</section>
</main>
<!-- 底部 -->
<footer class="footer">
<p>Powered by HwICSUiSdk | 华为交互数字人解决方案</p>
</footer>
</div>
<!-- SDK 引入 -->
<script type="module" src="./scripts/main.js"></script>
</body>
</html>

461
scripts/main.js Normal file
View File

@@ -0,0 +1,461 @@
/**
* 华为交互数字人 - HwICSUiSdk 集成示例
*
* 功能说明:
* 1. SDK 初始化和销毁
* 2. 数字人创建和显示
* 3. 语音/文本对话交互
* 4. 事件监听和日志记录
*/
// 导入 HwICSUiSdk
import { HwICSUiSdk, ICSEventKey, InteractionMode, ConfigKey } from '../hwICSUiSdk/HwICSUiSdk.esm.js';
// ===== 全局状态 =====
const state = {
isInitialized: false,
isChatting: false,
isSpeaking: false,
isMuted: false,
jobInfo: null,
currentInteractionMode: InteractionMode.AUDIO
};
// ===== DOM 元素引用 =====
const elements = {
// 配置输入
serverAddress: document.getElementById('serverAddress'),
robotId: document.getElementById('robotId'),
onceCode: document.getElementById('onceCode'),
// 按钮
initBtn: document.getElementById('initBtn'),
destroyBtn: document.getElementById('destroyBtn'),
startChatBtn: document.getElementById('startChatBtn'),
stopChatBtn: document.getElementById('stopChatBtn'),
startSpeakBtn: document.getElementById('startSpeakBtn'),
stopSpeakBtn: document.getElementById('stopSpeakBtn'),
sendTextBtn: document.getElementById('sendTextBtn'),
interruptBtn: document.getElementById('interruptBtn'),
toggleAudioBtn: document.getElementById('toggleAudioBtn'),
clearLogBtn: document.getElementById('clearLogBtn'),
// 输入框
textInput: document.getElementById('textInput'),
logLevel: document.getElementById('logLevel'),
// 显示区域
digitalHumanBox: document.getElementById('digitalHumanBox'),
logContainer: document.getElementById('logContainer'),
connectionStatus: document.getElementById('connectionStatus'),
sdkVersion: document.getElementById('sdkVersion')
};
// ===== 日志工具 =====
const logger = {
log(level, message, data = null) {
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
const entry = document.createElement('div');
entry.className = 'log-entry';
const levelClass = level.toLowerCase();
const displayMessage = data ? `${message} ${JSON.stringify(data, null, 2)}` : message;
entry.innerHTML = `
<span class="log-time">[${timestamp}]</span>
<span class="log-level ${levelClass}">${level}</span>
<span class="log-message">${displayMessage}</span>
`;
elements.logContainer.appendChild(entry);
elements.logContainer.scrollTop = elements.logContainer.scrollHeight;
console[level === 'ERROR' ? 'error' : level === 'WARN' ? 'warn' : 'log'](message, data);
},
debug(message, data) { this.log('DEBUG', message, data); },
info(message, data) { this.log('INFO', message, data); },
warn(message, data) { this.log('WARN', message, data); },
error(message, data) { this.log('ERROR', message, data); }
};
// ===== SDK 事件监听配置 =====
const eventListeners = {
[ICSEventKey.ERROR]: (error) => {
logger.error('SDK 错误', error);
updateConnectionStatus(false);
},
[ICSEventKey.JOB_INFO_CHANGE]: (jobInfo) => {
logger.info('任务信息变更', jobInfo);
state.jobInfo = jobInfo;
},
[ICSEventKey.SPEAKING_START]: () => {
logger.info('数字人开始说话');
state.isSpeaking = true;
updateUIState();
},
[ICSEventKey.SPEAKING_STOP]: () => {
logger.info('数字人停止说话');
state.isSpeaking = false;
updateUIState();
},
[ICSEventKey.SPEECH_RECOGNIZED]: (data) => {
logger.info('语音识别结果', data);
},
[ICSEventKey.SEMANTIC_RECOGNIZED]: (data) => {
logger.info('语义识别结果', data);
},
[ICSEventKey.ENTER_SLEEP]: () => {
logger.info('数字人进入休眠状态');
},
[ICSEventKey.ENTER_ACTIVE]: () => {
logger.info('数字人进入激活状态');
},
[ICSEventKey.LANGUAGE_INFO_CHANGE]: (langInfo) => {
logger.info('语言信息变更', langInfo);
},
[ICSEventKey.JOB_END_MENTION]: (data) => {
logger.warn('任务即将结束', data);
},
[ICSEventKey.JOB_END]: (data) => {
logger.info('任务已结束', data);
state.isChatting = false;
state.jobInfo = null;
updateConnectionStatus(false);
updateUIState();
}
};
// ===== UI 状态更新 =====
function updateUIState() {
const { isInitialized, isChatting, isSpeaking } = state;
elements.initBtn.disabled = isInitialized;
elements.destroyBtn.disabled = !isInitialized;
elements.startChatBtn.disabled = !isInitialized || isChatting;
elements.stopChatBtn.disabled = !isInitialized || !isChatting;
elements.startSpeakBtn.disabled = !isInitialized || !isChatting || isSpeaking;
elements.stopSpeakBtn.disabled = !isInitialized || !isChatting || !isSpeaking;
elements.textInput.disabled = !isInitialized || !isChatting;
elements.sendTextBtn.disabled = !isInitialized || !isChatting;
elements.interruptBtn.disabled = !isInitialized || !isChatting;
elements.toggleAudioBtn.disabled = !isInitialized;
if (isChatting) {
elements.digitalHumanBox.classList.add('active');
} else {
elements.digitalHumanBox.classList.remove('active');
}
}
function updateConnectionStatus(connected) {
const status = elements.connectionStatus;
if (connected) {
status.textContent = '已连接';
status.className = 'status connected';
} else {
status.textContent = '未连接';
status.className = 'status disconnected';
}
}
// ===== SDK 操作 =====
async function initializeSDK() {
try {
const serverAddress = elements.serverAddress.value.trim();
const robotId = elements.robotId.value.trim();
const onceCode = elements.onceCode.value.trim();
if (!serverAddress) {
logger.error('请输入服务器地址');
return;
}
logger.info('正在初始化 SDK...');
const isSupported = await HwICSUiSdk.checkBrowserSupport(true);
if (!isSupported) {
logger.error('当前浏览器不支持该功能');
return;
}
const logLevel = elements.logLevel.value;
HwICSUiSdk.setLogLevel(logLevel);
HwICSUiSdk.addEventListeners(eventListeners);
const createParam = {
serverAddress,
containerId: 'digitalHumanBox',
robotId: robotId || undefined,
onceCode: onceCode || undefined,
config: {
[ConfigKey.ENABLE_CAPTION]: true,
[ConfigKey.ENABLE_CHAT_BTN]: true,
[ConfigKey.ENABLE_HOT_ISSUES]: true,
[ConfigKey.ENABLE_WEAK_ERROR_INFO]: true,
[ConfigKey.SHOW_LANGUAGE_UI]: true,
[ConfigKey.ENABLE_DEVICE_SETTING]: true,
[ConfigKey.ENABLE_NETWORK_CHECK]: true,
[ConfigKey.AUTO_PLAY_REMOTE_VIDEO]: true,
[ConfigKey.DEFAULT_MUTE_REMOTE_AUDIO]: false
},
logLevel
};
await HwICSUiSdk.create(createParam);
state.isInitialized = true;
updateConnectionStatus(true);
updateUIState();
elements.sdkVersion.textContent = `v${HwICSUiSdk.version}`;
logger.info('SDK 初始化成功', { version: HwICSUiSdk.version });
} catch (error) {
logger.error('SDK 初始化失败', error);
state.isInitialized = false;
updateUIState();
}
}
async function destroySDK() {
try {
logger.info('正在销毁 SDK...');
await HwICSUiSdk.destroy();
state.isInitialized = false;
state.isChatting = false;
state.isSpeaking = false;
state.jobInfo = null;
updateConnectionStatus(false);
updateUIState();
elements.digitalHumanBox.innerHTML = `
<div class="placeholder">
<span class="placeholder-icon">🤖</span>
<p>请先初始化 SDK 并创建数字人</p>
</div>
`;
logger.info('SDK 已销毁');
} catch (error) {
logger.error('销毁 SDK 失败', error);
}
}
async function startChat() {
try {
logger.info('正在开始对话...');
const result = await HwICSUiSdk.startChat({
interactionMode: InteractionMode.AUDIO,
wakeupType: undefined,
preCheckPermissions: true
});
if (result.result) {
state.isChatting = true;
logger.info('对话已开始', { chatId: result.chatId, sessionId: result.sessionId });
updateUIState();
} else {
logger.error('开始对话失败', result);
}
} catch (error) {
logger.error('开始对话出错', error);
}
}
async function stopChat() {
try {
logger.info('正在停止对话...');
const result = await HwICSUiSdk.stopChat();
if (result.result) {
state.isChatting = false;
state.isSpeaking = false;
logger.info('对话已停止');
updateUIState();
} else {
logger.error('停止对话失败', result);
}
} catch (error) {
logger.error('停止对话出错', error);
}
}
async function startUserSpeak() {
try {
logger.info('开始语音识别...');
const result = await HwICSUiSdk.startUserSpeak({
preCheckPermissions: true
});
if (result.result) {
logger.info('语音识别已启动');
} else {
logger.error('启动语音识别失败', result);
}
} catch (error) {
logger.error('启动语音识别出错', error);
}
}
async function stopUserSpeak() {
try {
logger.info('停止语音识别...');
const result = await HwICSUiSdk.stopUserSpeak();
if (result.result) {
logger.info('语音识别已停止');
} else {
logger.error('停止语音识别失败', result);
}
} catch (error) {
logger.error('停止语音识别出错', error);
}
}
async function sendTextMessage() {
const text = elements.textInput.value.trim();
if (!text) {
logger.warn('请输入文本内容');
return;
}
try {
logger.info('发送文本消息...', { text });
const result = await HwICSUiSdk.sendTextQuestion({
text,
questionParam: undefined
});
if (result.result) {
logger.info('消息发送成功', { chatId: result.chatId });
elements.textInput.value = '';
} else {
logger.error('发送消息失败', result);
}
} catch (error) {
logger.error('发送消息出错', error);
}
}
async function interruptSpeaking() {
try {
logger.info('打断说话...');
const result = await HwICSUiSdk.interruptSpeaking();
if (result.result) {
logger.info('已打断说话');
} else {
logger.error('打断说话失败', result);
}
} catch (error) {
logger.error('打断说话出错', error);
}
}
async function toggleAudio() {
try {
if (state.isMuted) {
const result = await HwICSUiSdk.unmuteRemoteAudio();
if (result) {
state.isMuted = false;
elements.toggleAudioBtn.innerHTML = '<span class="icon">🔊</span> 静音';
logger.info('已取消静音');
}
} else {
const result = await HwICSUiSdk.muteRemoteAudio();
if (result) {
state.isMuted = true;
elements.toggleAudioBtn.innerHTML = '<span class="icon">🔇</span> 取消静音';
logger.info('已静音');
}
}
} catch (error) {
logger.error('切换音频状态出错', error);
}
}
function clearLogs() {
elements.logContainer.innerHTML = '';
logger.info('日志已清空');
}
// ===== 事件绑定 =====
function bindEvents() {
elements.initBtn.addEventListener('click', initializeSDK);
elements.destroyBtn.addEventListener('click', destroySDK);
elements.startChatBtn.addEventListener('click', startChat);
elements.stopChatBtn.addEventListener('click', stopChat);
elements.startSpeakBtn.addEventListener('click', startUserSpeak);
elements.stopSpeakBtn.addEventListener('click', stopUserSpeak);
elements.sendTextBtn.addEventListener('click', sendTextMessage);
elements.textInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendTextMessage();
}
});
elements.interruptBtn.addEventListener('click', interruptSpeaking);
elements.toggleAudioBtn.addEventListener('click', toggleAudio);
elements.clearLogBtn.addEventListener('click', clearLogs);
elements.logLevel.addEventListener('change', () => {
if (state.isInitialized) {
HwICSUiSdk.setLogLevel(elements.logLevel.value);
logger.info(`日志级别已切换为: ${elements.logLevel.value}`);
}
});
window.addEventListener('beforeunload', () => {
if (state.isInitialized) {
HwICSUiSdk.destroy();
}
});
}
// ===== 初始化 =====
function init() {
logger.info('应用初始化完成');
logger.info('HwICSUiSdk 版本', { version: HwICSUiSdk.version, buildTime: HwICSUiSdk.buildTime });
bindEvents();
updateUIState();
}
init();

644
styles/main.css Normal file
View File

@@ -0,0 +1,644 @@
/* ===== CSS 变量定义 ===== */
:root {
/* 主色调 - 科技蓝 */
--primary-color: #2563eb;
--primary-dark: #1d4ed8;
--primary-light: #3b82f6;
--primary-gradient: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
/* 辅助色 */
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--info-color: #06b6d4;
/* 中性色 */
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--border-color: #475569;
/* 阴影 */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
--shadow-glow: 0 0 20px rgba(37, 99, 235, 0.3);
/* 间距 */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* 圆角 */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
/* 字体 */
--font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
/* ===== 基础样式重置 ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.6;
}
/* ===== 应用容器 ===== */
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== 头部 ===== */
.header {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) var(--spacing-xl);
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
box-shadow: var(--shadow-md);
}
.logo {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.logo-icon {
font-size: 2rem;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
.logo h1 {
font-size: 1.5rem;
font-weight: 600;
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #2563eb 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.status-bar {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.status {
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius-full);
font-size: 0.875rem;
font-weight: 500;
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.status::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
.status.connected {
background: rgba(16, 185, 129, 0.2);
color: var(--success-color);
}
.status.disconnected {
background: rgba(239, 68, 68, 0.2);
color: var(--danger-color);
}
.version {
color: var(--text-muted);
font-size: 0.875rem;
}
/* ===== 主内容区 ===== */
.main-content {
flex: 1;
padding: var(--spacing-xl);
display: grid;
grid-template-columns: 320px 1fr 380px;
gap: var(--spacing-xl);
max-width: 1600px;
margin: 0 auto;
width: 100%;
}
/* ===== 面板通用样式 ===== */
section {
background: var(--bg-secondary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
border: 1px solid var(--border-color);
box-shadow: var(--shadow-md);
}
section h2 {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: var(--spacing-lg);
color: var(--text-primary);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== 配置面板 ===== */
.config-panel {
height: fit-content;
}
.config-section {
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
}
.config-section:last-of-type {
border-bottom: none;
margin-bottom: 0;
}
.config-section h3 {
font-size: 0.875rem;
font-weight: 600;
color: var(--primary-light);
margin-bottom: var(--spacing-md);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.form-group {
margin-bottom: var(--spacing-md);
}
.form-group label {
display: block;
margin-bottom: var(--spacing-xs);
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-primary);
font-size: 0.875rem;
transition: all 0.2s ease;
}
.form-group input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
.form-group input::placeholder {
color: var(--text-muted);
}
.form-group input[readonly] {
background: var(--bg-primary);
cursor: default;
}
.input-with-status {
position: relative;
display: flex;
align-items: center;
}
.input-with-status input {
padding-right: 80px;
}
.field-status {
position: absolute;
right: var(--spacing-sm);
font-size: 0.75rem;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-weight: 500;
}
.field-status.success {
background: rgba(16, 185, 129, 0.2);
color: var(--success-color);
}
.field-status.error {
background: rgba(239, 68, 68, 0.2);
color: var(--danger-color);
}
.field-status.loading {
background: rgba(6, 182, 212, 0.2);
color: var(--info-color);
}
.field-hint {
display: block;
margin-top: var(--spacing-xs);
color: var(--text-muted);
font-size: 0.75rem;
}
.form-actions {
display: flex;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.form-actions.main-actions {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-lg);
border-top: 2px solid var(--border-color);
}
/* ===== 按钮样式 ===== */
.btn {
padding: var(--spacing-sm) var(--spacing-md);
border: none;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-xs);
white-space: nowrap;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--primary-gradient);
color: white;
}
.btn-primary:hover:not(:disabled) {
box-shadow: var(--shadow-glow);
}
.btn-success {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.btn-warning {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.btn-info {
background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%);
color: white;
}
.btn-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover:not(:disabled) {
background: var(--border-color);
}
.btn-small {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
}
/* ===== 数字人显示区 ===== */
.digital-human-container {
display: flex;
flex-direction: column;
}
.digital-human-box {
flex: 1;
min-height: 400px;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
border-radius: var(--radius-lg);
border: 2px dashed var(--border-color);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
margin-bottom: var(--spacing-lg);
}
.digital-human-box.active {
border-style: solid;
border-color: var(--primary-color);
box-shadow: var(--shadow-glow);
}
.digital-human-box::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(37, 99, 235, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(6, 182, 212, 0.1) 0%, transparent 50%);
pointer-events: none;
}
.placeholder {
text-align: center;
color: var(--text-muted);
}
.placeholder-icon {
font-size: 4rem;
display: block;
margin-bottom: var(--spacing-md);
opacity: 0.5;
}
/* ===== 控制面板 ===== */
.control-panel {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
.control-group h3 {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: var(--spacing-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.button-row {
display: flex;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
.button-row .btn {
flex: 1;
min-width: 100px;
}
.text-input-row {
display: flex;
gap: var(--spacing-sm);
}
.text-input-row input {
flex: 1;
padding: var(--spacing-sm) var(--spacing-md);
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-primary);
font-size: 0.875rem;
}
.text-input-row input:focus {
outline: none;
border-color: var(--primary-color);
}
.text-input-row input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ===== 日志面板 ===== */
.log-panel {
display: flex;
flex-direction: column;
max-height: calc(100vh - 200px);
}
.log-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}
.log-toolbar select {
padding: var(--spacing-xs) var(--spacing-sm);
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-primary);
font-size: 0.875rem;
}
.log-container {
flex: 1;
overflow-y: auto;
background: var(--bg-primary);
border-radius: var(--radius-md);
padding: var(--spacing-md);
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.8125rem;
line-height: 1.5;
}
.log-entry {
padding: var(--spacing-xs) 0;
border-bottom: 1px solid var(--bg-tertiary);
display: flex;
gap: var(--spacing-sm);
}
.log-entry:last-child {
border-bottom: none;
}
.log-time {
color: var(--text-muted);
white-space: nowrap;
}
.log-level {
font-weight: 600;
min-width: 50px;
}
.log-level.debug {
color: var(--text-muted);
}
.log-level.info {
color: var(--info-color);
}
.log-level.warn {
color: var(--warning-color);
}
.log-level.error {
color: var(--danger-color);
}
.log-message {
color: var(--text-primary);
word-break: break-all;
}
/* ===== 底部 ===== */
.footer {
background: var(--bg-secondary);
border-top: 1px solid var(--border-color);
padding: var(--spacing-md) var(--spacing-xl);
text-align: center;
color: var(--text-muted);
font-size: 0.875rem;
}
/* ===== 响应式设计 ===== */
@media (max-width: 1200px) {
.main-content {
grid-template-columns: 1fr;
max-width: 800px;
}
.digital-human-box {
min-height: 300px;
}
.log-panel {
max-height: 400px;
}
}
@media (max-width: 640px) {
.header {
flex-direction: column;
gap: var(--spacing-md);
text-align: center;
}
.main-content {
padding: var(--spacing-md);
}
.form-actions {
flex-direction: column;
}
.button-row {
flex-direction: column;
}
.text-input-row {
flex-direction: column;
}
}
/* ===== 滚动条样式 ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-tertiary);
border-radius: var(--radius-md);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: var(--radius-md);
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* ===== 动画效果 ===== */
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.log-entry {
animation: slideIn 0.2s ease-out;
}