feat(多语言): 添加提示文本的多语言支持

- 在状态管理中新增当前语言和可用语言列表字段
- 为提示文本添加多语言输入框生成功能
- 实现根据当前语言自动更新提示文本显示
- 重构重启对话逻辑为独立函数并添加重试机制
- 将提示文本多语言配置保存到本地存储
This commit is contained in:
2026-05-07 13:55:48 +08:00
parent 36873764a0
commit d1f09c6afb
2 changed files with 162 additions and 33 deletions

View File

@@ -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 = '<option value="">无可用语言</option>';
}
@@ -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 = '<div class="form-group"><label>提示文本</label><p style="color: var(--text-muted); font-size: 0.85rem;">暂无可用的语言信息</p></div>';
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);