import { WORD_LIBRARIES } from '../constants.js'; import { COMMON_NAMES, STOP_WORDS } from '../data/stopwords.js'; import { state } from '../core/state.js'; import { saveJsonSetting, saveSettings, STORAGE_KEYS } from '../core/storage.js'; import { renderPage } from '../core/router.js'; import { showToast } from '../ui/toast.js'; import { clearCloudTtsCache, getTtsSettings, resetCloudTtsAuthBlock, sanitizeAzureSpeechKey, testCloudTtsConnection as testAzureTTSConnection } from '../services/tts.js'; import { callAI } from '../services/ai.js'; import { escapeHtml } from '../ui/dom.js'; export { toggleBatchExtract } from './extract.js'; export { showFilterWordsModal } from './filter-words.js'; export { manualLoadWords, switchWordLibrary, toggleAutoLoad } from '../services/library.js'; export { clearAllData, clearFavorites, doImportBackup, exportAllData, handleImportBackup, showImportAllModal } from './settings-data.js'; // ==================== Page: Settings ==================== export function renderSettings(el) { const s = state.settings; el.innerHTML = `

AI 模型配置

格式:https://your-api.com/v1

TTS 语音配置

优先使用云端 TTS 朗读,失败后自动回退到有道发音。

启用云端 TTS
开启后朗读会优先调用下面配置的 TTS 模型

例如 eastus,需与 API Key 所属区域一致。

预取 Azure TTS 音频
提前生成 Azure 音频,减少播放等待,但会消耗更多请求额度

数据管理

所有数据存储在浏览器本地,清除浏览器数据会丢失。建议定期导出备份。

${state.words.length}
单词
${state.records.length}
学习记录
${Object.keys(state.schedule).length}
复习计划
${state.favorites.length}
收藏单词
${state.mailEmails.length}
保存邮件

清空数据不可恢复,AI 配置和收藏单词将保留

词库与提词

当前单词库
切换后会使用所选词库,并为每个词库分别保留学习进度和收藏。
启动时自动加载内置词库
若词库为空,自动加载 data/ 目录下的单词文件
邮件批量提词
在提词页面显示批量提取功能,支持上传文件并统计词频
过滤词管理

管理提词时自动排除的停用词和常见人名

自动播放设置

配置单词库自动播放时的朗读次数和间隔时间

每个单词朗读次数
重复朗读间隔
同一单词多次朗读之间的间隔时间
毫秒(留空使用默认 1 秒)
单词切换间隔
自动播放时切换到下一个单词的间隔时间
毫秒(留空使用默认 2.5 秒)
播放时滚动方式
自动播放切换单词时,页面滚动行为

应用更新

当前版本:${escapeHtml(window.APP_VERSION || '未知')}

如果 iPhone Safari 仍显示旧版本,可重新获取最新页面和程序文件。单词、学习记录、收藏及设置不会被清除。

`; } export async function forceRefreshBrowserCache() { if (!confirm('确定获取最新版本并重新加载吗?本地学习数据和设置不会被删除。')) return; const button = document.getElementById('force-refresh-btn'); if (button) { button.disabled = true; button.innerHTML = ' 正在获取最新版本...'; } try { // 当前无 Service Worker,Cache Storage 通常为空;此处为将来引入 SW 时的前瞻兼容。 // 真正的缓存绕过依赖下方的时间戳 URL + 静态资源 ?v= 版本号。 if ('caches' in window) { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map(cacheName => caches.delete(cacheName))); } } catch (error) { console.warn('清理应用缓存失败,将继续重新加载:', error); } const url = new URL(window.location.href); url.searchParams.set('_app_update', Date.now().toString()); window.location.replace(url.toString()); } export function setAutoPlayRepeat(count) { const previousValue = state.autoPlayRepeat; state.autoPlayRepeat = count; if (!saveJsonSetting(STORAGE_KEYS.autoPlayRepeat, count)) { state.autoPlayRepeat = previousValue; return false; } const delayEl = document.getElementById('repeat-delay-setting'); if (delayEl) delayEl.style.display = count <= 1 ? 'none' : ''; if (state.currentPage === 'settings') renderPage('settings'); return true; } export function setAutoPlayScrollMode(mode) { const previousValue = state.autoPlayScrollMode; state.autoPlayScrollMode = mode === 'visible' ? 'visible' : 'center'; if (!saveJsonSetting(STORAGE_KEYS.autoPlayScrollMode, state.autoPlayScrollMode)) { state.autoPlayScrollMode = previousValue; return false; } if (state.currentPage === 'settings') renderPage('settings'); return true; } export function saveAutoPlaySettings() { const intervalVal = parseInt(document.getElementById('set-word-interval').value) || 0; const repeatDelayVal = parseInt(document.getElementById('set-repeat-delay').value) || 0; const previousInterval = state.autoPlayInterval; const previousRepeatDelay = state.autoPlayRepeatDelay; state.autoPlayInterval = intervalVal; state.autoPlayRepeatDelay = repeatDelayVal; const saved = saveJsonSetting(STORAGE_KEYS.autoPlayInterval, intervalVal) && saveJsonSetting(STORAGE_KEYS.autoPlayRepeatDelay, repeatDelayVal); if (!saved) { state.autoPlayInterval = previousInterval; state.autoPlayRepeatDelay = previousRepeatDelay; saveJsonSetting(STORAGE_KEYS.autoPlayInterval, previousInterval); saveJsonSetting(STORAGE_KEYS.autoPlayRepeatDelay, previousRepeatDelay); return false; } showToast('播放设置已保存', 'success'); return true; } function valueById(id, fallback = '') { const el = document.getElementById(id); return el ? el.value.trim() : fallback; } function syncSettingsFromForm() { state.settings = { ...state.settings, apiKey: valueById('set-api-key', state.settings.apiKey || ''), apiUrl: valueById('set-api-url', state.settings.apiUrl || '').replace(/\/$/, ''), model: valueById('set-model', state.settings.model || ''), ttsProvider: 'azure', ttsApiKey: sanitizeAzureSpeechKey(valueById('set-tts-api-key', state.settings.ttsApiKey || '')), ttsEndpoint: '', ttsRegion: valueById('set-tts-region', state.settings.ttsRegion || 'eastus').toLowerCase(), ttsVoice: valueById('set-tts-voice', state.settings.ttsVoice || 'en-US-JennyNeural'), ttsOutputFormat: 'audio-24khz-48kbitrate-mono-mp3', ttsPrefetchEnabled: state.settings.ttsPrefetchEnabled === true }; } export function toggleTtsEnabled() { const previousSettings = state.settings; syncSettingsFromForm(); state.settings.ttsEnabled = state.settings.ttsEnabled === false; resetCloudTtsAuthBlock(); clearCloudTtsCache(); if (!saveSettings()) { state.settings = previousSettings; return false; } renderPage('settings'); return true; } export function toggleTtsPrefetchEnabled() { const previousSettings = state.settings; syncSettingsFromForm(); state.settings.ttsPrefetchEnabled = state.settings.ttsPrefetchEnabled !== true; if (!saveSettings()) { state.settings = previousSettings; return false; } renderPage('settings'); return true; } function persistSettings() { const previousSettings = state.settings; syncSettingsFromForm(); resetCloudTtsAuthBlock(); clearCloudTtsCache(); if (saveSettings()) return true; state.settings = previousSettings; return false; } export function doSaveSettings() { if (!persistSettings()) return false; showToast('设置已保存', 'success'); return true; } export async function testTTSConnection() { if (!persistSettings()) return; const cfg = getTtsSettings(); if (!cfg.enabled) { showToast('请先启用云端 TTS', 'warning'); return; } if (!cfg.apiKey || !cfg.region || !cfg.voice) { showToast('请填写 TTS API Key、Region 和 Voice', 'warning'); return; } showToast('正在测试 TTS...', 'info'); try { const played = await testAzureTTSConnection(); showToast(played ? 'TTS 测试成功' : 'TTS 未播放:请检查配置或稍后重试', played ? 'success' : 'warning'); } catch (e) { showToast('TTS 测试失败:' + e.message, 'error'); } } export async function testAIConnection() { if (!doSaveSettings()) return; if (!state.settings.apiKey || !state.settings.apiUrl || !state.settings.model) { showToast('请填写所有配置项', 'warning'); return; } showToast('正在测试连接...', 'info'); try { const reply = await callAI([ { role: 'user', content: '请回复"连接成功"四个字' } ]); showToast('连接成功!AI 回复:' + reply.slice(0, 50), 'success'); } catch (e) { showToast('连接失败:' + e.message, 'error'); } }