Files
antiwear-web/frontend/scripts/main.js
ZF sun 319a2f883b refactor(前端): 更新消息气泡样式选择器及处理逻辑
将消息文字样式改为消息气泡样式,统一使用'.message-item .item-content'作为选择器
修改对应的样式应用逻辑,使用querySelectorAll遍历所有消息气泡元素
更新注释和标签文本以反映实际功能
2026-05-06 15:37:29 +08:00

804 lines
30 KiB
JavaScript

/**
* 华为交互数字人 - 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'
};
const state = {
isInitialized: false,
isChatting: false,
isSpeaking: false,
isMuted: false,
jobInfo: null,
currentInteractionMode: InteractionMode.AUDIO
};
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'),
toggleLogBtn: document.getElementById('toggleLogBtn'),
applyCaptionStyleBtn: document.getElementById('applyCaptionStyleBtn'),
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,
logPanel: false
};
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); }
};
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();
}
};
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',
logPanel: 'toggleLogBtn'
};
const btnId = btnMap[panelId];
if (btnId && elements[btnId]) {
elements[btnId].classList.add('active');
}
}
}
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
},
logLevel
};
await HwICSUiSdk.create(createParam);
state.isInitialized = true;
fullscreenState.sdkInitialized = true;
updateConnectionStatus(true);
updateUIState();
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...');
await HwICSUiSdk.destroy();
state.isInitialized = false;
state.isChatting = false;
state.isSpeaking = false;
state.jobInfo = null;
fullscreenState.sdkInitialized = false;
updateConnectionStatus(false);
updateUIState();
elements.digitalHumanBox.innerHTML = `
<div class="fullscreen-exit-hint">按 ESC 退出全屏</div>
<div class="placeholder">
<span class="placeholder-icon">🤖</span>
<p>点击右下角 ⚙️ 按钮初始化 SDK</p>
</div>
`;
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;
}
captionContainer.style.backgroundColor = hexToRgba(
elements.containerBgColor.value,
elements.containerBgOpacity.value / 100
);
captionContainer.style.borderColor = elements.containerBorderColor.value;
captionContainer.style.borderRadius = `${elements.containerBorderRadius.value}px`;
captionContainer.style.padding = `${elements.containerPadding.value}px`;
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 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})`;
}
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 && containerBg !== 'transparent') {
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]));
elements.containerBgOpacity.value = rgbaMatch[4] ? Math.round(parseFloat(rgbaMatch[4]) * 100) : 100;
}
}
if (containerBorder && containerBorder !== 'transparent' && containerBorder !== 'rgba(0, 0, 0, 0)') {
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]));
}
}
elements.containerBorderRadius.value = parseInt(containerRadius) || 8;
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) return;
const styles = window.getComputedStyle(el);
section.elements.fontSize.value = parseInt(styles.fontSize) || 14;
section.elements.fontWeight.value = styles.fontWeight === 'bold' ? 'bold' : 'normal';
section.elements.color.value = parseColor(styles.color);
section.elements.lineHeight.value = parseFloat(styles.lineHeight) || 1.5;
if (styles.backgroundColor && styles.backgroundColor !== 'transparent') {
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]));
section.elements.bgOpacity.value = rgbaMatch[4] ? Math.round(parseFloat(rgbaMatch[4]) * 100) : 100;
}
}
section.elements.borderRadius.value = parseInt(styles.borderRadius) || 4;
section.elements.padding.value = parseInt(styles.padding) || 6;
updateRangeValue(section.elements.bgOpacity);
});
logger.info('已从字幕容器加载样式');
}
function parseColor(colorStr) {
if (!colorStr) return '#ffffff';
if (colorStr.startsWith('#')) return colorStr;
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 '#ffffff';
}
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';
}
isDragging = false;
isResizing = false;
};
container.addEventListener('mousedown', onContainerMouseDown);
resizeHandle.addEventListener('mousedown', onResizeHandleMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
logger.info('字幕容器拖拽和缩放已启用');
}
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();
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
if (document.querySelector('.caption-container-hub')) {
setTimeout(loadCaptionStyleFromContainer, 100);
}
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);
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}%`;
}
});
});
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');
loadCaptionStyleFromContainer();
});
elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel'));
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) {
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();
watchCaptionContainer();
}
init();