/** * 华为交互数字人 - HwICSUiSdk 集成示例 */ import { HwICSUiSdk } from '../hwICSUiSdk/HwICSUiSdk.esm.js'; import { fetchOnceCode } from './api.js'; const 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' }; const InteractionMode = { AUDIO: 'AUDIO', TEXT: 'TEXT' }; const ConfigKey = { ENABLE_CAPTION: 'enableCaption', ENABLE_CHAT_BTN: 'enableChatBtn', ENABLE_HOT_ISSUES: 'enableHotIssues', ENABLE_WEAK_ERROR_INFO: 'enableWeakErrorInfo', SHOW_LANGUAGE_UI: 'showLanguageUI', ENABLE_DEVICE_SETTING: 'enableDeviceSetting', ENABLE_NETWORK_CHECK: 'enableNetworkCheck', AUTO_PLAY_REMOTE_VIDEO: 'autoPlayRemoteVideo', DEFAULT_MUTE_REMOTE_AUDIO: 'defaultMuteRemoteAudio', ENABLE_LOCAL_WAKEUP: 'enableLocalWakeup', USE_WORKER_WAKEUP: 'useWorkerWakeup' }; const state = { isInitialized: false, isChatting: false, isSpeaking: false, isMuted: false, jobInfo: null, currentInteractionMode: InteractionMode.AUDIO, keepAliveActive: false }; const elements = { serverAddress: document.getElementById('serverAddress'), robotId: document.getElementById('robotId'), onceCode: document.getElementById('onceCode'), initBtn: document.getElementById('initBtn'), destroyBtn: document.getElementById('destroyBtn'), getOnceCodeBtn: document.getElementById('getOnceCodeBtn'), clearLogBtn: document.getElementById('clearLogBtn'), fullscreenBtn: document.getElementById('fullscreenBtn'), toggleConfigBtn: document.getElementById('toggleConfigBtn'), toggleCaptionBtn: document.getElementById('toggleCaptionBtn'), toggleLanguageBtn: document.getElementById('toggleLanguageBtn'), toggleLogBtn: document.getElementById('toggleLogBtn'), applyCaptionStyleBtn: document.getElementById('applyCaptionStyleBtn'), languageSelect: document.getElementById('languageSelect'), containerBgColor: document.getElementById('containerBgColor'), containerBgOpacity: document.getElementById('containerBgOpacity'), containerBorderColor: document.getElementById('containerBorderColor'), containerBorderRadius: document.getElementById('containerBorderRadius'), containerPadding: document.getElementById('containerPadding'), tipFontSize: document.getElementById('tipFontSize'), tipFontWeight: document.getElementById('tipFontWeight'), tipColor: document.getElementById('tipColor'), tipLineHeight: document.getElementById('tipLineHeight'), tipBgColor: document.getElementById('tipBgColor'), tipBgOpacity: document.getElementById('tipBgOpacity'), tipBorderRadius: document.getElementById('tipBorderRadius'), tipPadding: document.getElementById('tipPadding'), messageFontSize: document.getElementById('messageFontSize'), messageFontWeight: document.getElementById('messageFontWeight'), messageColor: document.getElementById('messageColor'), messageLineHeight: document.getElementById('messageLineHeight'), messageBgColor: document.getElementById('messageBgColor'), messageBgOpacity: document.getElementById('messageBgOpacity'), messageBorderRadius: document.getElementById('messageBorderRadius'), messagePadding: document.getElementById('messagePadding'), hotTitleFontSize: document.getElementById('hotTitleFontSize'), hotTitleFontWeight: document.getElementById('hotTitleFontWeight'), hotTitleColor: document.getElementById('hotTitleColor'), hotTitleLineHeight: document.getElementById('hotTitleLineHeight'), hotTitleBgColor: document.getElementById('hotTitleBgColor'), hotTitleBgOpacity: document.getElementById('hotTitleBgOpacity'), hotTitleBorderRadius: document.getElementById('hotTitleBorderRadius'), hotTitlePadding: document.getElementById('hotTitlePadding'), hotIssuesFontSize: document.getElementById('hotIssuesFontSize'), hotIssuesFontWeight: document.getElementById('hotIssuesFontWeight'), hotIssuesColor: document.getElementById('hotIssuesColor'), hotIssuesLineHeight: document.getElementById('hotIssuesLineHeight'), hotIssuesBgColor: document.getElementById('hotIssuesBgColor'), hotIssuesBgOpacity: document.getElementById('hotIssuesBgOpacity'), hotIssuesBorderRadius: document.getElementById('hotIssuesBorderRadius'), hotIssuesPadding: document.getElementById('hotIssuesPadding'), logLevel: document.getElementById('logLevel'), digitalHumanBox: document.getElementById('digitalHumanBox'), logContainer: document.getElementById('logContainer'), connectionStatus: document.getElementById('connectionStatus'), sdkVersion: document.getElementById('sdkVersion') }; const fullscreenState = { isFullscreen: false, sdkInitialized: false }; const panelState = { configPanel: false, captionPanel: false, languagePanel: false, logPanel: false }; let panelDragState = { isDragging: false, currentPanel: null, offsetX: 0, offsetY: 0, originalHeight: null }; function initPanelDrag() { document.querySelectorAll('.floating-panel').forEach(panel => { const header = panel.querySelector('.panel-header'); if (!header) return; header.style.cursor = 'move'; header.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON') return; panelDragState.isDragging = true; panelDragState.currentPanel = panel; const rect = panel.getBoundingClientRect(); panelDragState.offsetX = e.clientX - rect.left; panelDragState.offsetY = e.clientY - rect.top; panelDragState.originalHeight = panel.offsetHeight; panel.style.transform = 'none'; panel.style.height = panelDragState.originalHeight + 'px'; }); document.addEventListener('mousemove', (e) => { if (!panelDragState.isDragging || !panelDragState.currentPanel) return; const x = e.clientX - panelDragState.offsetX; const y = e.clientY - panelDragState.offsetY; const maxX = window.innerWidth - panelDragState.currentPanel.offsetWidth; const maxY = window.innerHeight - panelDragState.currentPanel.offsetHeight; panelDragState.currentPanel.style.left = Math.max(0, Math.min(x, maxX)) + 'px'; panelDragState.currentPanel.style.top = Math.max(0, Math.min(y, maxY)) + 'px'; }); document.addEventListener('mouseup', () => { if (panelDragState.isDragging) { panelDragState.isDragging = false; if (panelDragState.currentPanel) { panelDragState.currentPanel.style.height = ''; } panelDragState.currentPanel = null; } }); }); } 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 = ` [${timestamp}] ${level} ${displayMessage} `; 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); } }; 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); updateLanguageSelect(langInfo); }, [ICSEventKey.JOB_END_MENTION]: (data) => { logger.warn('任务即将结束', data); }, [ICSEventKey.JOB_END]: (data) => { logger.info('任务已结束,正在自动重新激活...', data); state.isChatting = false; state.jobInfo = null; if (state.keepAliveActive) { setTimeout(async () => { try { logger.info('正在调用 activeInteractionMode...'); await HwICSUiSdk.activeInteractionMode(); logger.info('自动激活成功'); state.isChatting = true; updateUIState(); } catch (error) { logger.error('自动激活失败', error); } }, 1000); } updateUIState(); } }; let keepAliveTimer = null; const KEEP_ALIVE_INTERVAL = 30000; function startKeepAlive() { if (keepAliveTimer) return; state.keepAliveActive = true; logger.info('启动对话保活机制'); keepAliveTimer = setInterval(async () => { if (state.isInitialized && state.keepAliveActive) { try { await HwICSUiSdk.activeInteractionMode(); logger.debug('保活 ping'); } catch (error) { logger.warn('保活失败', error); } } }, KEEP_ALIVE_INTERVAL); } function stopKeepAlive() { if (keepAliveTimer) { clearInterval(keepAliveTimer); keepAliveTimer = null; } state.keepAliveActive = false; logger.info('停止对话保活机制'); } function updateUIState() { const { isInitialized } = state; elements.initBtn.disabled = isInitialized; elements.destroyBtn.disabled = !isInitialized; } function updateConnectionStatus(connected) { const status = elements.connectionStatus; if (connected) { status.textContent = '已连接'; status.className = 'status connected'; } else { status.textContent = '未连接'; status.className = 'status disconnected'; } } function togglePanel(panelId) { const panel = document.getElementById(panelId); console.log('切换面板:', panelId, '找到元素:', !!panel); if (!panel) return; const isVisible = panel.classList.contains('visible'); console.log('当前可见:', isVisible); Object.keys(panelState).forEach(key => { const el = document.getElementById(key); if (el) el.classList.remove('visible'); panelState[key] = false; }); document.querySelectorAll('.toolbar-btn').forEach(btn => btn.classList.remove('active')); if (!isVisible) { panel.classList.add('visible'); panelState[panelId] = true; console.log('面板已显示:', panelId); const btnMap = { configPanel: 'toggleConfigBtn', captionPanel: 'toggleCaptionBtn', languagePanel: 'toggleLanguageBtn', logPanel: 'toggleLogBtn' }; const btnId = btnMap[panelId]; if (btnId && elements[btnId]) { elements[btnId].classList.add('active'); } } } async function loadLanguageOptions() { if (!state.isInitialized) { elements.languageSelect.innerHTML = ''; return; } try { const langInfo = await HwICSUiSdk.getLanguageInfo(); logger.info('获取语言信息成功', langInfo); if (langInfo.languageList && langInfo.languageList.length > 0) { elements.languageSelect.innerHTML = ''; langInfo.languageList.forEach(item => { const option = document.createElement('option'); option.value = item.language; option.textContent = item.language_desc; elements.languageSelect.appendChild(option); }); if (langInfo.currentLanguage) { elements.languageSelect.value = langInfo.currentLanguage; } } else { elements.languageSelect.innerHTML = ''; } } catch (error) { logger.error('获取语言信息失败', error); elements.languageSelect.innerHTML = ''; } } function updateLanguageSelect(langInfo) { if (!langInfo) return; if (langInfo.languageList && langInfo.languageList.length > 0) { elements.languageSelect.innerHTML = ''; langInfo.languageList.forEach(item => { const option = document.createElement('option'); option.value = item.language; option.textContent = item.language_desc; elements.languageSelect.appendChild(option); }); } if (langInfo.currentLanguage) { elements.languageSelect.value = langInfo.currentLanguage; } } async function changeLanguage(langCode) { if (!state.isInitialized) { logger.error('SDK 未初始化,无法切换语言'); return; } try { logger.info('正在切换语言...', langCode); await HwICSUiSdk.changeLanguage({ langCode }); logger.info('语言切换成功', langCode); } catch (error) { logger.error('语言切换失败', error); } } async function initializeSDK() { try { const serverAddress = elements.serverAddress.value.trim(); const robotId = elements.robotId.value.trim(); let onceCode = elements.onceCode.value.trim(); if (!serverAddress) { logger.error('请输入服务器地址'); return; } if (!onceCode) { logger.info('授权码为空,正在自动获取...'); try { onceCode = await fetchOnceCode(); elements.onceCode.value = onceCode; logger.info('授权码自动获取成功'); } catch (err) { logger.error('授权码自动获取失败', err.message); 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]: false, [ConfigKey.SHOW_LANGUAGE_UI]: false, [ConfigKey.ENABLE_DEVICE_SETTING]: false, [ConfigKey.ENABLE_NETWORK_CHECK]: false, [ConfigKey.AUTO_PLAY_REMOTE_VIDEO]: true, [ConfigKey.DEFAULT_MUTE_REMOTE_AUDIO]: false, [ConfigKey.ENABLE_LOCAL_WAKEUP]: true, [ConfigKey.USE_WORKER_WAKEUP]: false }, logLevel }; const resourcePath = { wasmPath: './hwICSUiSdk/wasmData.js', dataPath: './hwICSUiSdk/modelData.js', initModel: true, useWorker: false }; try { await HwICSUiSdk.initResourcePath(resourcePath); logger.info('唤醒资源初始化成功'); } catch (error) { logger.warn('唤醒资源初始化失败,将继续尝试', error); } await HwICSUiSdk.create(createParam); state.isInitialized = true; fullscreenState.sdkInitialized = true; updateConnectionStatus(true); updateUIState(); startKeepAlive(); try { await HwICSUiSdk.startLocalWakeupRecord(); logger.info('本地唤醒监听已启动'); } catch (error) { logger.warn('启动本地唤醒监听失败', error); } elements.sdkVersion.textContent = `v${HwICSUiSdk.version}`; logger.info('SDK 初始化成功', { version: HwICSUiSdk.version }); rebindDynamicButtons(); setTimeout(calculateScale, 500); } catch (error) { logger.error('SDK 初始化失败', error); state.isInitialized = false; updateUIState(); } } async function destroySDK() { try { logger.info('正在销毁 SDK...'); try { await HwICSUiSdk.stopLocalWakeupRecord(); logger.info('本地唤醒监听已停止'); } catch (e) { logger.warn('停止本地唤醒监听失败', e); } await HwICSUiSdk.destroy(); state.isInitialized = false; state.isChatting = false; state.isSpeaking = false; state.jobInfo = null; fullscreenState.sdkInitialized = false; stopKeepAlive(); updateConnectionStatus(false); updateUIState(); elements.digitalHumanBox.innerHTML = `
按 ESC 退出全屏
🤖

点击右下角 ⚙️ 按钮初始化 SDK

`; logger.info('SDK 已销毁'); } catch (error) { logger.error('销毁 SDK 失败', error); } } async function handleGetOnceCode() { try { elements.getOnceCodeBtn.disabled = true; elements.getOnceCodeBtn.textContent = '🔄 获取中...'; logger.info('正在获取授权码...'); const onceCode = await fetchOnceCode(); elements.onceCode.value = onceCode; logger.info('授权码获取成功'); } catch (error) { logger.error('获取授权码失败', error.message); } finally { elements.getOnceCodeBtn.disabled = false; elements.getOnceCodeBtn.textContent = '🔑 获取'; } } function clearLogs() { elements.logContainer.innerHTML = ''; logger.info('日志已清空'); } function applyCaptionStyle() { const captionContainer = document.querySelector('.caption-container-hub'); if (!captionContainer) { logger.warn('字幕容器未找到'); return; } const containerStyles = { backgroundColor: hexToRgba(elements.containerBgColor.value, elements.containerBgOpacity.value / 100), borderColor: elements.containerBorderColor.value, borderRadius: `${elements.containerBorderRadius.value}px`, padding: `${elements.containerPadding.value}px` }; Object.assign(captionContainer.style, containerStyles); const tipEl = captionContainer.querySelector('.layout-hub-tip'); if (tipEl) { tipEl.style.fontSize = `${elements.tipFontSize.value}px`; tipEl.style.fontWeight = elements.tipFontWeight.value; tipEl.style.color = elements.tipColor.value; tipEl.style.lineHeight = elements.tipLineHeight.value; tipEl.style.backgroundColor = hexToRgba(elements.tipBgColor.value, elements.tipBgOpacity.value / 100); tipEl.style.borderRadius = `${elements.tipBorderRadius.value}px`; tipEl.style.padding = `${elements.tipPadding.value}px`; } const messageItems = captionContainer.querySelectorAll('.message-item .item-content'); messageItems.forEach(item => { item.style.fontSize = `${elements.messageFontSize.value}px`; item.style.fontWeight = elements.messageFontWeight.value; item.style.color = elements.messageColor.value; item.style.lineHeight = elements.messageLineHeight.value; item.style.backgroundColor = hexToRgba(elements.messageBgColor.value, elements.messageBgOpacity.value / 100); item.style.borderRadius = `${elements.messageBorderRadius.value}px`; item.style.padding = `${elements.messagePadding.value}px`; }); const hotTitleEl = captionContainer.querySelector('.layout-hub-hot-title'); if (hotTitleEl) { hotTitleEl.style.fontSize = `${elements.hotTitleFontSize.value}px`; hotTitleEl.style.fontWeight = elements.hotTitleFontWeight.value; hotTitleEl.style.color = elements.hotTitleColor.value; hotTitleEl.style.lineHeight = elements.hotTitleLineHeight.value; hotTitleEl.style.backgroundColor = hexToRgba(elements.hotTitleBgColor.value, elements.hotTitleBgOpacity.value / 100); hotTitleEl.style.borderRadius = `${elements.hotTitleBorderRadius.value}px`; hotTitleEl.style.padding = `${elements.hotTitlePadding.value}px`; } const hotIssuesEl = captionContainer.querySelector('.layout-hub-hot-issues'); if (hotIssuesEl) { hotIssuesEl.style.fontSize = `${elements.hotIssuesFontSize.value}px`; hotIssuesEl.style.fontWeight = elements.hotIssuesFontWeight.value; hotIssuesEl.style.color = elements.hotIssuesColor.value; hotIssuesEl.style.lineHeight = elements.hotIssuesLineHeight.value; hotIssuesEl.style.backgroundColor = hexToRgba(elements.hotIssuesBgColor.value, elements.hotIssuesBgOpacity.value / 100); hotIssuesEl.style.borderRadius = `${elements.hotIssuesBorderRadius.value}px`; hotIssuesEl.style.padding = `${elements.hotIssuesPadding.value}px`; } logger.info('字幕样式已应用'); } function updateSingleStyle(inputId) { const captionContainer = document.querySelector('.caption-container-hub'); if (!captionContainer) return; const updateMap = { containerBgColor: () => { captionContainer.style.backgroundColor = hexToRgba(elements.containerBgColor.value, elements.containerBgOpacity.value / 100); }, containerBgOpacity: () => { captionContainer.style.backgroundColor = hexToRgba(elements.containerBgColor.value, elements.containerBgOpacity.value / 100); }, containerBorderColor: () => { captionContainer.style.borderColor = elements.containerBorderColor.value; }, containerBorderRadius: () => { captionContainer.style.borderRadius = `${elements.containerBorderRadius.value}px`; }, containerPadding: () => { captionContainer.style.padding = `${elements.containerPadding.value}px`; }, tipFontSize: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.fontSize = `${elements.tipFontSize.value}px`; }, tipFontWeight: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.fontWeight = elements.tipFontWeight.value; }, tipColor: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.color = elements.tipColor.value; }, tipLineHeight: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.lineHeight = elements.tipLineHeight.value; }, tipBgColor: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.backgroundColor = hexToRgba(elements.tipBgColor.value, elements.tipBgOpacity.value / 100); }, tipBgOpacity: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.backgroundColor = hexToRgba(elements.tipBgColor.value, elements.tipBgOpacity.value / 100); }, tipBorderRadius: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.borderRadius = `${elements.tipBorderRadius.value}px`; }, tipPadding: () => { const el = captionContainer.querySelector('.layout-hub-tip'); if (el) el.style.padding = `${elements.tipPadding.value}px`; }, messageFontSize: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.fontSize = `${elements.messageFontSize.value}px`); }, messageFontWeight: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.fontWeight = elements.messageFontWeight.value); }, messageColor: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.color = elements.messageColor.value); }, messageLineHeight: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.lineHeight = elements.messageLineHeight.value); }, messageBgColor: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.backgroundColor = hexToRgba(elements.messageBgColor.value, elements.messageBgOpacity.value / 100)); }, messageBgOpacity: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.backgroundColor = hexToRgba(elements.messageBgColor.value, elements.messageBgOpacity.value / 100)); }, messageBorderRadius: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.borderRadius = `${elements.messageBorderRadius.value}px`); }, messagePadding: () => { captionContainer.querySelectorAll('.message-item .item-content').forEach(el => el.style.padding = `${elements.messagePadding.value}px`); }, hotTitleFontSize: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.fontSize = `${elements.hotTitleFontSize.value}px`; }, hotTitleFontWeight: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.fontWeight = elements.hotTitleFontWeight.value; }, hotTitleColor: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.color = elements.hotTitleColor.value; }, hotTitleLineHeight: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.lineHeight = elements.hotTitleLineHeight.value; }, hotTitleBgColor: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.backgroundColor = hexToRgba(elements.hotTitleBgColor.value, elements.hotTitleBgOpacity.value / 100); }, hotTitleBgOpacity: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.backgroundColor = hexToRgba(elements.hotTitleBgColor.value, elements.hotTitleBgOpacity.value / 100); }, hotTitleBorderRadius: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.borderRadius = `${elements.hotTitleBorderRadius.value}px`; }, hotTitlePadding: () => { const el = captionContainer.querySelector('.layout-hub-hot-title'); if (el) el.style.padding = `${elements.hotTitlePadding.value}px`; }, hotIssuesFontSize: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.fontSize = `${elements.hotIssuesFontSize.value}px`; }, hotIssuesFontWeight: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.fontWeight = elements.hotIssuesFontWeight.value; }, hotIssuesColor: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.color = elements.hotIssuesColor.value; }, hotIssuesLineHeight: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.lineHeight = elements.hotIssuesLineHeight.value; }, hotIssuesBgColor: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.backgroundColor = hexToRgba(elements.hotIssuesBgColor.value, elements.hotIssuesBgOpacity.value / 100); }, hotIssuesBgOpacity: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.backgroundColor = hexToRgba(elements.hotIssuesBgColor.value, elements.hotIssuesBgOpacity.value / 100); }, hotIssuesBorderRadius: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.borderRadius = `${elements.hotIssuesBorderRadius.value}px`; }, hotIssuesPadding: () => { const el = captionContainer.querySelector('.layout-hub-hot-issues'); if (el) el.style.padding = `${elements.hotIssuesPadding.value}px`; } }; if (updateMap[inputId]) { updateMap[inputId](); } } function hexToRgba(hex, alpha) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } const CAPTION_STYLE_KEY = 'captionStyleSettings'; const CAPTION_POSITION_KEY = 'captionPositionSize'; function saveCaptionPositionSize() { const captionContainer = document.querySelector('.caption-container-hub'); if (!captionContainer) return; const positionData = { left: captionContainer.style.left || 'auto', top: captionContainer.style.top || 'auto', width: captionContainer.style.width || '', height: captionContainer.style.height || '', maxHeight: captionContainer.style.maxHeight || '' }; localStorage.setItem(CAPTION_POSITION_KEY, JSON.stringify(positionData)); logger.info('字幕容器位置尺寸已保存'); } function loadCaptionPositionSize() { const savedData = localStorage.getItem(CAPTION_POSITION_KEY); if (!savedData) return null; try { return JSON.parse(savedData); } catch (e) { logger.error('加载字幕容器位置尺寸失败', e.message); return null; } } function applyCaptionPositionSize(positionData) { const captionContainer = document.querySelector('.caption-container-hub'); if (!captionContainer || !positionData) return; if (positionData.left && positionData.left !== 'auto') { captionContainer.style.left = positionData.left; captionContainer.style.right = 'auto'; } if (positionData.top && positionData.top !== 'auto') { captionContainer.style.top = positionData.top; captionContainer.style.bottom = 'auto'; } if (positionData.width) { captionContainer.style.width = positionData.width; } if (positionData.height) { captionContainer.style.height = positionData.height; } if (positionData.maxHeight) { captionContainer.style.maxHeight = positionData.maxHeight; } logger.info('字幕容器位置尺寸已应用'); } function saveCaptionStyleToStorage() { const styleData = { containerBgColor: elements.containerBgColor.value, containerBgOpacity: elements.containerBgOpacity.value, containerBorderColor: elements.containerBorderColor.value, containerBorderRadius: elements.containerBorderRadius.value, containerPadding: elements.containerPadding.value, tipFontSize: elements.tipFontSize.value, tipFontWeight: elements.tipFontWeight.value, tipColor: elements.tipColor.value, tipLineHeight: elements.tipLineHeight.value, tipBgColor: elements.tipBgColor.value, tipBgOpacity: elements.tipBgOpacity.value, tipBorderRadius: elements.tipBorderRadius.value, tipPadding: elements.tipPadding.value, messageFontSize: elements.messageFontSize.value, messageFontWeight: elements.messageFontWeight.value, messageColor: elements.messageColor.value, messageLineHeight: elements.messageLineHeight.value, messageBgColor: elements.messageBgColor.value, messageBgOpacity: elements.messageBgOpacity.value, messageBorderRadius: elements.messageBorderRadius.value, messagePadding: elements.messagePadding.value, hotTitleFontSize: elements.hotTitleFontSize.value, hotTitleFontWeight: elements.hotTitleFontWeight.value, hotTitleColor: elements.hotTitleColor.value, hotTitleLineHeight: elements.hotTitleLineHeight.value, hotTitleBgColor: elements.hotTitleBgColor.value, hotTitleBgOpacity: elements.hotTitleBgOpacity.value, hotTitleBorderRadius: elements.hotTitleBorderRadius.value, hotTitlePadding: elements.hotTitlePadding.value, hotIssuesFontSize: elements.hotIssuesFontSize.value, hotIssuesFontWeight: elements.hotIssuesFontWeight.value, hotIssuesColor: elements.hotIssuesColor.value, hotIssuesLineHeight: elements.hotIssuesLineHeight.value, hotIssuesBgColor: elements.hotIssuesBgColor.value, hotIssuesBgOpacity: elements.hotIssuesBgOpacity.value, hotIssuesBorderRadius: elements.hotIssuesBorderRadius.value, hotIssuesPadding: elements.hotIssuesPadding.value }; localStorage.setItem(CAPTION_STYLE_KEY, JSON.stringify(styleData)); logger.info('字幕样式已保存'); } function loadCaptionStyleFromStorage() { const savedData = localStorage.getItem(CAPTION_STYLE_KEY); if (!savedData) return null; try { return JSON.parse(savedData); } catch (e) { logger.error('加载保存的字幕样式失败', e.message); return null; } } function hasSavedCaptionStyle() { return localStorage.getItem(CAPTION_STYLE_KEY) !== null; } function applySavedCaptionStyle(savedData) { if (savedData.containerBgColor) elements.containerBgColor.value = savedData.containerBgColor; if (savedData.containerBgOpacity) elements.containerBgOpacity.value = savedData.containerBgOpacity; if (savedData.containerBorderColor) elements.containerBorderColor.value = savedData.containerBorderColor; if (savedData.containerBorderRadius) elements.containerBorderRadius.value = savedData.containerBorderRadius; if (savedData.containerPadding) elements.containerPadding.value = savedData.containerPadding; if (savedData.tipFontSize) elements.tipFontSize.value = savedData.tipFontSize; if (savedData.tipFontWeight) elements.tipFontWeight.value = savedData.tipFontWeight; if (savedData.tipColor) elements.tipColor.value = savedData.tipColor; if (savedData.tipLineHeight) elements.tipLineHeight.value = savedData.tipLineHeight; if (savedData.tipBgColor) elements.tipBgColor.value = savedData.tipBgColor; if (savedData.tipBgOpacity) elements.tipBgOpacity.value = savedData.tipBgOpacity; if (savedData.tipBorderRadius) elements.tipBorderRadius.value = savedData.tipBorderRadius; if (savedData.tipPadding) elements.tipPadding.value = savedData.tipPadding; if (savedData.messageFontSize) elements.messageFontSize.value = savedData.messageFontSize; if (savedData.messageFontWeight) elements.messageFontWeight.value = savedData.messageFontWeight; if (savedData.messageColor) elements.messageColor.value = savedData.messageColor; if (savedData.messageLineHeight) elements.messageLineHeight.value = savedData.messageLineHeight; if (savedData.messageBgColor) elements.messageBgColor.value = savedData.messageBgColor; if (savedData.messageBgOpacity) elements.messageBgOpacity.value = savedData.messageBgOpacity; if (savedData.messageBorderRadius) elements.messageBorderRadius.value = savedData.messageBorderRadius; if (savedData.messagePadding) elements.messagePadding.value = savedData.messagePadding; if (savedData.hotTitleFontSize) elements.hotTitleFontSize.value = savedData.hotTitleFontSize; if (savedData.hotTitleFontWeight) elements.hotTitleFontWeight.value = savedData.hotTitleFontWeight; if (savedData.hotTitleColor) elements.hotTitleColor.value = savedData.hotTitleColor; if (savedData.hotTitleLineHeight) elements.hotTitleLineHeight.value = savedData.hotTitleLineHeight; if (savedData.hotTitleBgColor) elements.hotTitleBgColor.value = savedData.hotTitleBgColor; if (savedData.hotTitleBgOpacity) elements.hotTitleBgOpacity.value = savedData.hotTitleBgOpacity; if (savedData.hotTitleBorderRadius) elements.hotTitleBorderRadius.value = savedData.hotTitleBorderRadius; if (savedData.hotTitlePadding) elements.hotTitlePadding.value = savedData.hotTitlePadding; if (savedData.hotIssuesFontSize) elements.hotIssuesFontSize.value = savedData.hotIssuesFontSize; if (savedData.hotIssuesFontWeight) elements.hotIssuesFontWeight.value = savedData.hotIssuesFontWeight; if (savedData.hotIssuesColor) elements.hotIssuesColor.value = savedData.hotIssuesColor; if (savedData.hotIssuesLineHeight) elements.hotIssuesLineHeight.value = savedData.hotIssuesLineHeight; if (savedData.hotIssuesBgColor) elements.hotIssuesBgColor.value = savedData.hotIssuesBgColor; if (savedData.hotIssuesBgOpacity) elements.hotIssuesBgOpacity.value = savedData.hotIssuesBgOpacity; if (savedData.hotIssuesBorderRadius) elements.hotIssuesBorderRadius.value = savedData.hotIssuesBorderRadius; if (savedData.hotIssuesPadding) elements.hotIssuesPadding.value = savedData.hotIssuesPadding; document.querySelectorAll('#captionPanel input[type="range"]').forEach(slider => { const valueSpan = slider.nextElementSibling; if (valueSpan && valueSpan.classList.contains('range-value')) { valueSpan.textContent = `${slider.value}%`; } }); } function showCaptionStylePrompt(callback) { const savedData = loadCaptionStyleFromStorage(); if (!savedData) { callback(false); return; } const confirmed = confirm('检测到已保存的字幕样式设置。\n\n点击"确定"应用保存的样式,\n点击"取消"使用全新默认设置。'); callback(confirmed); } function loadCaptionStyleFromContainer() { const captionContainer = document.querySelector('.caption-container-hub'); if (!captionContainer) return; const containerStyles = window.getComputedStyle(captionContainer); const containerBg = containerStyles.backgroundColor; const containerBorder = containerStyles.borderColor; const containerRadius = containerStyles.borderRadius; const containerPad = containerStyles.padding; if (containerBg) { const rgbaMatch = containerBg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/); if (rgbaMatch) { elements.containerBgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3])); if (rgbaMatch[4]) { elements.containerBgOpacity.value = Math.round(parseFloat(rgbaMatch[4]) * 100); } } else if (containerBg === 'transparent' || containerBg === 'rgba(0, 0, 0, 0)') { elements.containerBgColor.value = '#000000'; elements.containerBgOpacity.value = 0; } } if (containerBorder) { const rgbaMatch = containerBorder.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/); if (rgbaMatch) { elements.containerBorderColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3])); } } if (containerRadius) { elements.containerBorderRadius.value = parseInt(containerRadius) || 8; } if (containerPad) { elements.containerPadding.value = parseInt(containerPad) || 10; } updateRangeValue(elements.containerBgOpacity); const sections = [ { selector: '.layout-hub-tip', elements: { fontSize: elements.tipFontSize, fontWeight: elements.tipFontWeight, color: elements.tipColor, lineHeight: elements.tipLineHeight, bgColor: elements.tipBgColor, bgOpacity: elements.tipBgOpacity, borderRadius: elements.tipBorderRadius, padding: elements.tipPadding } }, { selector: '.message-item .item-content', elements: { fontSize: elements.messageFontSize, fontWeight: elements.messageFontWeight, color: elements.messageColor, lineHeight: elements.messageLineHeight, bgColor: elements.messageBgColor, bgOpacity: elements.messageBgOpacity, borderRadius: elements.messageBorderRadius, padding: elements.messagePadding } }, { selector: '.layout-hub-hot-title', elements: { fontSize: elements.hotTitleFontSize, fontWeight: elements.hotTitleFontWeight, color: elements.hotTitleColor, lineHeight: elements.hotTitleLineHeight, bgColor: elements.hotTitleBgColor, bgOpacity: elements.hotTitleBgOpacity, borderRadius: elements.hotTitleBorderRadius, padding: elements.hotTitlePadding } }, { selector: '.layout-hub-hot-issues', elements: { fontSize: elements.hotIssuesFontSize, fontWeight: elements.hotIssuesFontWeight, color: elements.hotIssuesColor, lineHeight: elements.hotIssuesLineHeight, bgColor: elements.hotIssuesBgColor, bgOpacity: elements.hotIssuesBgOpacity, borderRadius: elements.hotIssuesBorderRadius, padding: elements.hotIssuesPadding } } ]; sections.forEach(section => { const el = captionContainer.querySelector(section.selector); if (!el) { logger.warn(`loadCaptionStyleFromContainer: 元素 ${section.selector} 未找到`); return; } const styles = window.getComputedStyle(el); if (styles.fontSize) { section.elements.fontSize.value = parseInt(styles.fontSize) || 14; } if (styles.fontWeight) { section.elements.fontWeight.value = styles.fontWeight === 'bold' || styles.fontWeight === '700' ? 'bold' : 'normal'; } if (styles.color) { const parsedColor = parseColor(styles.color); if (parsedColor) { section.elements.color.value = parsedColor; } } if (styles.backgroundColor) { const rgbaMatch = styles.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/); if (rgbaMatch) { section.elements.bgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3])); if (rgbaMatch[4]) { section.elements.bgOpacity.value = Math.round(parseFloat(rgbaMatch[4]) * 100); } else { section.elements.bgOpacity.value = 0; } } else if (styles.backgroundColor === 'transparent' || styles.backgroundColor === 'rgba(0, 0, 0, 0)') { section.elements.bgColor.value = '#000000'; section.elements.bgOpacity.value = 0; } } if (styles.borderRadius) { section.elements.borderRadius.value = parseInt(styles.borderRadius) || 4; } if (styles.padding) { section.elements.padding.value = parseInt(styles.padding) || 6; } if (section.elements.bgOpacity) { updateRangeValue(section.elements.bgOpacity); } }); logger.info('已从字幕容器加载样式'); } function parseColor(colorStr) { if (!colorStr) return null; if (colorStr.startsWith('#')) return colorStr; const rgbMatch = colorStr.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); if (rgbMatch) { return rgbToHex(parseInt(rgbMatch[1]), parseInt(rgbMatch[2]), parseInt(rgbMatch[3])); } const rgbaMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/); if (rgbaMatch) { return rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3])); } return null; } function rgbToHex(r, g, b) { return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); } function updateRangeValue(rangeInput) { const valueSpan = rangeInput.nextElementSibling; if (valueSpan && valueSpan.classList.contains('range-value')) { valueSpan.textContent = `${rangeInput.value}%`; } } function initCaptionDragAndResize(container) { container.dataset.dragInitialized = 'true'; let isDragging = false; let isResizing = false; let startX, startY, startLeft, startTop, startWidth, startHeight; let resizeHandle = container.querySelector('.caption-resize-handle'); if (!resizeHandle) { resizeHandle = document.createElement('div'); resizeHandle.className = 'caption-resize-handle'; resizeHandle.style.position = 'absolute'; resizeHandle.style.right = '0'; resizeHandle.style.bottom = '0'; resizeHandle.style.width = '24px'; resizeHandle.style.height = '24px'; resizeHandle.style.cursor = 'nwse-resize'; resizeHandle.style.background = 'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.95) 50%, rgba(255,255,255,0.8) 100%)'; resizeHandle.style.borderRadius = '0 0 8px 0'; resizeHandle.style.zIndex = '10000'; resizeHandle.style.borderRight = '3px solid rgba(0, 123, 255, 0.7)'; resizeHandle.style.borderBottom = '3px solid rgba(0, 123, 255, 0.7)'; resizeHandle.style.boxShadow = 'inset -2px -2px 4px rgba(0,0,0,0.1), 0 0 8px rgba(0, 123, 255, 0.2)'; resizeHandle.style.opacity = '0'; resizeHandle.style.transition = 'opacity 0.2s ease'; resizeHandle.title = '拖拽调整大小'; container.appendChild(resizeHandle); } container.addEventListener('mouseenter', () => { resizeHandle.style.opacity = '1'; }); container.addEventListener('mouseleave', () => { if (!isResizing) { resizeHandle.style.opacity = '0'; } }); const onContainerMouseDown = (e) => { if (e.target === resizeHandle || resizeHandle.contains(e.target)) return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = container.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; e.preventDefault(); e.stopPropagation(); container.style.borderColor = 'rgba(0, 123, 255, 0.6)'; container.style.boxShadow = '0 4px 12px rgba(0, 123, 255, 0.3)'; }; const onResizeHandleMouseDown = (e) => { isResizing = true; startX = e.clientX; startY = e.clientY; startWidth = container.offsetWidth; startHeight = container.offsetHeight; e.preventDefault(); e.stopPropagation(); container.style.borderColor = 'rgba(0, 123, 255, 0.8)'; container.style.boxShadow = '0 4px 12px rgba(0, 123, 255, 0.4)'; }; const onMouseMove = (e) => { if (!isDragging && !isResizing) return; e.preventDefault(); if (isDragging) { const dx = e.clientX - startX; const dy = e.clientY - startY; container.style.left = `${startLeft + dx}px`; container.style.top = `${startTop + dy}px`; } if (isResizing) { const dx = e.clientX - startX; const dy = e.clientY - startY; const newWidth = Math.max(200, startWidth + dx); const newHeight = Math.max(100, startHeight + dy); container.style.width = `${newWidth}px`; container.style.height = `${newHeight}px`; container.style.maxHeight = `${newHeight}px`; } }; const onMouseUp = () => { if (isDragging || isResizing) { container.style.borderColor = 'transparent'; container.style.boxShadow = 'none'; resizeHandle.style.opacity = '0'; saveCaptionPositionSize(); } isDragging = false; isResizing = false; }; container.addEventListener('mousedown', onContainerMouseDown); resizeHandle.addEventListener('mousedown', onResizeHandleMouseDown); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); logger.info('字幕容器拖拽和缩放已启用'); } let captionStyleObserver = null; let captionStyleApplied = false; let captionStylePromptShown = false; function watchCaptionContainer() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList') { const captionContainer = document.querySelector('.caption-container-hub'); if (captionContainer && !captionContainer.dataset.dragInitialized) { logger.info('检测到字幕容器,自动启用拖拽功能'); captionContainer.style.position = 'fixed'; captionContainer.style.cursor = 'move'; captionContainer.style.userSelect = 'none'; captionContainer.style.zIndex = '9999'; initCaptionDragAndResize(captionContainer); loadCaptionStyleFromContainer(); applyCaptionPositionSize(loadCaptionPositionSize()); if (captionStyleApplied) { setTimeout(applyCaptionStyle, 100); } } else if (captionContainer && captionStyleApplied) { setTimeout(applyCaptionStyle, 100); } } } }); observer.observe(document.body, { childList: true, subtree: true }); if (document.querySelector('.caption-container-hub')) { setTimeout(loadCaptionStyleFromContainer, 100); } logger.info('已启动字幕容器监听'); } function watchCaptionContentChanges() { const captionContainer = document.querySelector('.caption-container-hub'); if (!captionContainer) return; if (captionStyleObserver) { captionStyleObserver.disconnect(); } captionStyleObserver = new MutationObserver(() => { if (captionStyleApplied) { setTimeout(applyCaptionStyle, 50); } }); captionStyleObserver.observe(captionContainer, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); logger.info('已启动字幕内容变化监听'); } function calculateScale() { const container = elements.digitalHumanBox; const sdkContainer = container.querySelector('.digital-intelligent-interaction'); if (!sdkContainer) return; sdkContainer.style.transform = 'none'; } function toggleFullscreen() { if (!fullscreenState.sdkInitialized) { logger.warn('请先初始化 SDK'); return; } fullscreenState.isFullscreen = !fullscreenState.isFullscreen; if (fullscreenState.isFullscreen) { elements.digitalHumanBox.classList.add('fullscreen'); elements.fullscreenBtn.innerHTML = '⛶'; elements.fullscreenBtn.title = '退出全屏'; logger.info('进入全屏模式'); } else { elements.digitalHumanBox.classList.remove('fullscreen'); elements.fullscreenBtn.innerHTML = '⛶'; elements.fullscreenBtn.title = '全屏显示'; logger.info('退出全屏模式'); } setTimeout(calculateScale, 100); } function handleFullscreenKeydown(e) { if (e.key === 'Escape' && fullscreenState.isFullscreen) { toggleFullscreen(); } } function initScaling() { const resizeObserver = new ResizeObserver(() => { if (fullscreenState.sdkInitialized) { calculateScale(); } }); resizeObserver.observe(elements.digitalHumanBox); window.addEventListener('keydown', handleFullscreenKeydown); } function bindEvents() { elements.initBtn.addEventListener('click', initializeSDK); elements.destroyBtn.addEventListener('click', destroySDK); elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode); elements.applyCaptionStyleBtn.addEventListener('click', () => { applyCaptionStyle(); saveCaptionStyleToStorage(); saveCaptionPositionSize(); captionStyleApplied = true; watchCaptionContentChanges(); }); document.querySelectorAll('input[type="range"]').forEach(slider => { slider.addEventListener('input', (e) => { const valueSpan = e.target.nextElementSibling; if (valueSpan && valueSpan.classList.contains('range-value')) { valueSpan.textContent = `${e.target.value}%`; } updateSingleStyle(e.target.id); saveCaptionStyleToStorage(); captionStyleApplied = true; watchCaptionContentChanges(); }); }); document.querySelectorAll('#captionPanel input[type="number"], #captionPanel input[type="color"], #captionPanel select').forEach(input => { input.addEventListener('input', (e) => { updateSingleStyle(e.target.id); saveCaptionStyleToStorage(); captionStyleApplied = true; watchCaptionContentChanges(); }); input.addEventListener('change', (e) => { updateSingleStyle(e.target.id); saveCaptionStyleToStorage(); captionStyleApplied = true; watchCaptionContentChanges(); }); }); elements.clearLogBtn.addEventListener('click', clearLogs); elements.logLevel.addEventListener('change', () => { if (state.isInitialized) { HwICSUiSdk.setLogLevel(elements.logLevel.value); logger.info(`日志级别已切换为: ${elements.logLevel.value}`); } }); elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel')); elements.toggleCaptionBtn.addEventListener('click', () => { togglePanel('captionPanel'); if (!captionStylePromptShown && hasSavedCaptionStyle()) { captionStylePromptShown = true; showCaptionStylePrompt((applySaved) => { if (applySaved) { const savedData = loadCaptionStyleFromStorage(); if (savedData) { applySavedCaptionStyle(savedData); applyCaptionStyle(); applyCaptionPositionSize(loadCaptionPositionSize()); captionStyleApplied = true; watchCaptionContentChanges(); logger.info('已应用保存的字幕样式'); } } else { loadCaptionStyleFromContainer(); localStorage.removeItem(CAPTION_STYLE_KEY); localStorage.removeItem(CAPTION_POSITION_KEY); captionStyleApplied = false; logger.info('已清除保存的字幕样式,使用全新默认设置'); } }); } else { loadCaptionStyleFromContainer(); } }); elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel')); elements.toggleLanguageBtn.addEventListener('click', () => { togglePanel('languagePanel'); loadLanguageOptions(); }); elements.languageSelect.addEventListener('change', (e) => { changeLanguage(e.target.value); }); elements.fullscreenBtn.addEventListener('click', toggleFullscreen); document.querySelectorAll('.panel-close').forEach(btn => { btn.addEventListener('click', () => { const panelId = btn.dataset.panel; if (panelId) togglePanel(panelId); }); }); window.addEventListener('beforeunload', () => { if (state.isInitialized) { stopKeepAlive(); HwICSUiSdk.destroy(); } }); } function rebindDynamicButtons() { const fullscreenBtn = document.getElementById('fullscreenBtn'); if (fullscreenBtn) { fullscreenBtn.removeEventListener('click', toggleFullscreen); fullscreenBtn.addEventListener('click', toggleFullscreen); } } function init() { logger.info('应用初始化完成'); logger.info('HwICSUiSdk 版本', { version: HwICSUiSdk.version, buildTime: HwICSUiSdk.buildTime }); bindEvents(); updateUIState(); initScaling(); initPanelDrag(); watchCaptionContainer(); } init();