diff --git a/frontend/index.html b/frontend/index.html
index 6db64c3..3114751 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -146,6 +146,9 @@
+
+
+
diff --git a/frontend/scripts/main.js b/frontend/scripts/main.js
index 19593ed..4fc3c47 100644
--- a/frontend/scripts/main.js
+++ b/frontend/scripts/main.js
@@ -52,7 +52,10 @@ const state = {
isMuted: false,
jobInfo: null,
currentInteractionMode: InteractionMode.AUDIO,
- keepAliveActive: false
+ keepAliveActive: false,
+ currentLanguage: 'zh_CN',
+ availableLanguages: [],
+ tipTextValues: {}
};
const elements = {
@@ -86,6 +89,7 @@ const elements = {
tipBgOpacity: document.getElementById('tipBgOpacity'),
tipBorderRadius: document.getElementById('tipBorderRadius'),
tipPadding: document.getElementById('tipPadding'),
+ tipTextFieldsContainer: document.getElementById('tipTextFieldsContainer'),
messageFontSize: document.getElementById('messageFontSize'),
messageFontWeight: document.getElementById('messageFontWeight'),
@@ -220,6 +224,15 @@ const eventListeners = {
[ICSEventKey.ERROR]: (error) => {
logger.error('SDK 错误', error);
updateConnectionStatus(false);
+ if (error?.error_code === '999400003' || error?.errorMsg?.includes('MUTE_TIMEOUT')) {
+ logger.warn('检测到静默超时,正在重新开始对话...');
+ state.isChatting = false;
+ state.jobInfo = null;
+ updateUIState();
+ if (state.keepAliveActive) {
+ setTimeout(() => restartChat(), 2000);
+ }
+ }
},
[ICSEventKey.JOB_INFO_CHANGE]: (jobInfo) => {
@@ -270,38 +283,7 @@ const eventListeners = {
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);
+ setTimeout(() => restartChat(), 2000);
}
}
};
@@ -309,6 +291,48 @@ const eventListeners = {
let keepAliveTimer = null;
const KEEP_ALIVE_INTERVAL = 10000;
+async function restartChat(retryCount = 0) {
+ const MAX_RETRIES = 3;
+ 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();
+ } else if (retryCount < MAX_RETRIES) {
+ logger.info(`将在 3 秒后重试 (${retryCount + 1}/${MAX_RETRIES})`);
+ setTimeout(() => restartChat(retryCount + 1), 3000);
+ }
+ }
+ } catch (error) {
+ logger.error('重新激活失败', error);
+ if (retryCount < MAX_RETRIES) {
+ logger.info(`将在 3 秒后重试 (${retryCount + 1}/${MAX_RETRIES})`);
+ setTimeout(() => restartChat(retryCount + 1), 3000);
+ } else {
+ try {
+ await HwICSUiSdk.activeInteractionMode();
+ state.isChatting = true;
+ updateUIState();
+ } catch (e) {
+ logger.error('activeInteractionMode 也失败', e);
+ }
+ }
+ }
+}
+
function startKeepAlive() {
if (keepAliveTimer) return;
state.keepAliveActive = true;
@@ -409,6 +433,8 @@ async function loadLanguageOptions() {
if (langInfo.currentLanguage) {
elements.languageSelect.value = langInfo.currentLanguage;
}
+
+ generateTipTextFields(langInfo.languageList);
} else {
elements.languageSelect.innerHTML = '';
}
@@ -429,10 +455,14 @@ function updateLanguageSelect(langInfo) {
option.textContent = item.language_desc;
elements.languageSelect.appendChild(option);
});
+
+ generateTipTextFields(langInfo.languageList);
}
if (langInfo.currentLanguage) {
elements.languageSelect.value = langInfo.currentLanguage;
+ state.currentLanguage = langInfo.currentLanguage;
+ updateTipTextFromState();
}
}
@@ -611,6 +641,94 @@ function clearLogs() {
logger.info('日志已清空');
}
+async function loadTipTextFields() {
+ if (state.availableLanguages.length > 0) {
+ generateTipTextFields(state.availableLanguages);
+ return;
+ }
+
+ if (!state.isInitialized) return;
+
+ try {
+ const langInfo = await HwICSUiSdk.getLanguageInfo();
+ if (langInfo.languageList && langInfo.languageList.length > 0) {
+ state.availableLanguages = langInfo.languageList;
+ generateTipTextFields(langInfo.languageList);
+ }
+ } catch (error) {
+ logger.warn('获取语言信息用于提示文本失败', error);
+ }
+}
+
+function generateTipTextFields(languageList) {
+ if (!elements.tipTextFieldsContainer) return;
+
+ elements.tipTextFieldsContainer.innerHTML = '';
+
+ if (!languageList || languageList.length === 0) {
+ elements.tipTextFieldsContainer.innerHTML = '';
+ return;
+ }
+
+ state.availableLanguages = languageList;
+
+ languageList.forEach(item => {
+ const langCode = item.language;
+ const langDesc = item.language_desc;
+
+ if (!state.tipTextValues[langCode]) {
+ const defaultTexts = {
+ 'zh_CN': '点击开始对话',
+ 'en_US': 'Click to start conversation'
+ };
+ state.tipTextValues[langCode] = defaultTexts[langCode] || '';
+ }
+
+ const group = document.createElement('div');
+ group.className = 'form-group';
+ group.dataset.langCode = langCode;
+
+ const label = document.createElement('label');
+ label.setAttribute('for', `tipText_${langCode}`);
+ label.textContent = `提示文本 (${langDesc})`;
+
+ const input = document.createElement('input');
+ input.type = 'text';
+ input.id = `tipText_${langCode}`;
+ input.value = state.tipTextValues[langCode];
+ input.placeholder = `请输入${langDesc}提示文本`;
+
+ input.addEventListener('input', () => {
+ state.tipTextValues[langCode] = input.value;
+ const tipEl = document.querySelector('.caption-container-hub .layout-hub-tip');
+ if (tipEl && state.currentLanguage === langCode) {
+ tipEl.textContent = input.value;
+ }
+ saveCaptionStyleToStorage();
+ });
+
+ group.appendChild(label);
+ group.appendChild(input);
+ elements.tipTextFieldsContainer.appendChild(group);
+ });
+}
+
+function updateTipText(tipEl) {
+ const currentLang = state.currentLanguage || 'zh_CN';
+ const text = state.tipTextValues[currentLang] || state.tipTextValues['zh_CN'] || '';
+ if (tipEl) {
+ tipEl.textContent = text;
+ }
+}
+
+function updateTipTextFromState() {
+ const tipEl = document.querySelector('.caption-container-hub .layout-hub-tip');
+ if (!tipEl) return;
+ const currentLang = state.currentLanguage || 'zh_CN';
+ const text = state.tipTextValues[currentLang] || state.tipTextValues['zh_CN'] || '';
+ tipEl.textContent = text;
+}
+
function applyCaptionStyle() {
const captionContainer = document.querySelector('.caption-container-hub');
if (!captionContainer) {
@@ -636,6 +754,7 @@ function applyCaptionStyle() {
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`;
+ updateTipText(tipEl);
}
const messageItems = captionContainer.querySelectorAll('.message-item .item-content');
@@ -896,6 +1015,7 @@ function saveCaptionStyleToStorage() {
tipBgOpacity: elements.tipBgOpacity.value,
tipBorderRadius: elements.tipBorderRadius.value,
tipPadding: elements.tipPadding.value,
+ tipTextValues: state.tipTextValues,
messageFontSize: elements.messageFontSize.value,
messageFontWeight: elements.messageFontWeight.value,
messageColor: elements.messageColor.value,
@@ -978,6 +1098,9 @@ function applySavedCaptionStyle(savedData) {
if (savedData.hotIssuesBgOpacity) elements.hotIssuesBgOpacity.value = savedData.hotIssuesBgOpacity;
if (savedData.hotIssuesBorderRadius) elements.hotIssuesBorderRadius.value = savedData.hotIssuesBorderRadius;
if (savedData.hotIssuesPadding) elements.hotIssuesPadding.value = savedData.hotIssuesPadding;
+ if (savedData.tipTextValues) {
+ state.tipTextValues = savedData.tipTextValues;
+ }
document.querySelectorAll('#captionPanel input[type="range"]').forEach(slider => {
const valueSpan = slider.nextElementSibling;
@@ -1397,6 +1520,7 @@ function bindEvents() {
elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel'));
elements.toggleCaptionBtn.addEventListener('click', () => {
togglePanel('captionPanel');
+ loadTipTextFields();
if (!captionStylePromptShown && hasSavedCaptionStyle()) {
captionStylePromptShown = true;
showCaptionStylePrompt((applySaved) => {
@@ -1428,6 +1552,8 @@ function bindEvents() {
loadLanguageOptions();
});
elements.languageSelect.addEventListener('change', (e) => {
+ state.currentLanguage = e.target.value;
+ updateTipTextFromState();
changeLanguage(e.target.value);
});
elements.fullscreenBtn.addEventListener('click', toggleFullscreen);