- 将重新激活逻辑从 activeInteractionMode 改为更可靠的 startChat - 增加对 startChat 失败情况的处理,包括错误码 999400004 的特殊处理 - 添加双重错误处理机制,在 startChat 失败后尝试 activeInteractionMode - 将保持活跃间隔从 20000 毫秒缩短为 10000 毫秒 - 移除重复的 updateUIState 调用
1470 lines
58 KiB
JavaScript
1470 lines
58 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 WakeupType = {
|
|
VOICE: 'VOICE',
|
|
RADAR: 'RADAR',
|
|
INFRARED: 'INFRARED',
|
|
CAMERA: 'CAMERA'
|
|
};
|
|
|
|
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 = `
|
|
<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);
|
|
updateLanguageSelect(langInfo);
|
|
},
|
|
|
|
[ICSEventKey.JOB_END_MENTION]: (data) => {
|
|
logger.warn('任务即将结束', data);
|
|
},
|
|
|
|
[ICSEventKey.JOB_END]: (data) => {
|
|
logger.info('任务已结束,正在自动重新激活...', data);
|
|
state.isChatting = false;
|
|
state.jobInfo = null;
|
|
updateUIState();
|
|
if (state.keepAliveActive) {
|
|
setTimeout(async () => {
|
|
try {
|
|
logger.info('正在调用 startChat 重新开始对话...');
|
|
const chatResult = await HwICSUiSdk.startChat({
|
|
interactionMode: InteractionMode.AUDIO,
|
|
wakeupType: WakeupType.VOICE,
|
|
preCheckPermissions: false
|
|
});
|
|
if (chatResult.result) {
|
|
logger.info('重新开始对话成功', chatResult);
|
|
state.isChatting = true;
|
|
updateUIState();
|
|
} else {
|
|
logger.warn('重新开始对话返回失败', chatResult);
|
|
if (chatResult.errorCode === '999400004') {
|
|
logger.info('对话已存在,尝试 activeInteractionMode');
|
|
await HwICSUiSdk.activeInteractionMode();
|
|
state.isChatting = true;
|
|
updateUIState();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error('重新激活失败', error);
|
|
try {
|
|
await HwICSUiSdk.activeInteractionMode();
|
|
state.isChatting = true;
|
|
updateUIState();
|
|
} catch (e) {
|
|
logger.error('activeInteractionMode 也失败', e);
|
|
}
|
|
}
|
|
}, 2000);
|
|
}
|
|
}
|
|
};
|
|
|
|
let keepAliveTimer = null;
|
|
const KEEP_ALIVE_INTERVAL = 10000;
|
|
|
|
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 = '<option value="">SDK 未初始化</option>';
|
|
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 = '<option value="">无可用语言</option>';
|
|
}
|
|
} catch (error) {
|
|
logger.error('获取语言信息失败', error);
|
|
elements.languageSelect.innerHTML = '<option value="">加载失败</option>';
|
|
}
|
|
}
|
|
|
|
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 = `
|
|
<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;
|
|
}
|
|
|
|
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();
|