feat(frontend): engine dropdown + emotion dual mode

- Replace engine buttons with <select> dropdown
- Add ENGINE_CONFIG for centralized engine management
- Add global/per-sentence emotion UI mode switching
- Add #engine-select dark theme styles
- Remove old .switch-btn and .engine-icon styles
- Reset emotion state on engine switch
This commit is contained in:
lanhao
2026-07-01 00:48:05 +08:00
parent 81460f06d7
commit a0346d05ba
3 changed files with 88 additions and 42 deletions
+47 -10
View File
@@ -3,10 +3,23 @@
*/
const CONFIG = {
API_BASE_URL: '', // 空字符串表示使用相对路径,通过Nginx代理
API_BASE_URL: '',
MAX_CHARS: 2000,
};
const ENGINE_CONFIG = {
minimax: {
name: 'MiniMax',
emotionMode: 'global',
color: '#00d4aa'
},
cosyvoice: {
name: 'CosyVoice',
emotionMode: 'global',
color: '#ff6b6b'
}
};
// 情绪按钮与 API emotion 值的映射
const EMOTION_MAP = {
'happy': { emoji: '😊', label: '开心', apiValue: 'happy' },
@@ -30,8 +43,10 @@ const elements = {
editor: document.getElementById('editor'),
editorPlaceholder: document.getElementById('editorPlaceholder'),
charCount: document.getElementById('charCount'),
switchBtns: document.querySelectorAll('.switch-btn'),
engineSelect: document.getElementById('engine-select'),
emotionBtns: document.querySelectorAll('.emotion-btn'),
emotionGlobal: document.getElementById('emotion-global'),
emotionPerSentence: document.getElementById('emotion-per-sentence'),
speedSlider: document.getElementById('speedSlider'),
speedValue: document.getElementById('speedValue'),
pitchSlider: document.getElementById('pitchSlider'),
@@ -143,12 +158,34 @@ function updateSliderDisplay() {
function switchEngine(engine) {
state.currentEngine = engine;
elements.switchBtns.forEach(btn => {
btn.classList.toggle('active', btn.dataset.engine === engine);
});
const engineName = engine === 'minimax' ? 'MiniMax' : 'CosyVoice';
document.getElementById('engineBadge').textContent = `蓝皓 · ${engineName}`;
showToast(`已切换到${engineName}引擎`);
const config = ENGINE_CONFIG[engine];
if (!config) return;
// Update engine badge
const badge = document.getElementById('engineBadge');
badge.textContent = `蓝皓 · ${config.name}`;
badge.style.background = config.color;
// Update engine select value
elements.engineSelect.value = engine;
// Switch emotion UI mode
updateEmotionMode(config.emotionMode);
// Reset emotion state
clearEmotionSelection();
showToast(`已切换到${config.name}引擎`);
}
function updateEmotionMode(mode) {
if (mode === 'global') {
elements.emotionGlobal.style.display = 'flex';
elements.emotionPerSentence.style.display = 'none';
} else if (mode === 'perSentence') {
elements.emotionGlobal.style.display = 'none';
elements.emotionPerSentence.style.display = 'block';
}
}
async function generateAudio() {
@@ -290,8 +327,8 @@ function downloadAudio() {
elements.editor.addEventListener('input', updateCharCount);
elements.editor.addEventListener('paste', () => { setTimeout(updateCharCount, 0); });
elements.switchBtns.forEach(btn => {
btn.addEventListener('click', () => switchEngine(btn.dataset.engine));
elements.engineSelect.addEventListener('change', (e) => {
switchEngine(e.target.value);
});
elements.emotionBtns.forEach(btn => {
btn.addEventListener('click', () => {
+9 -8
View File
@@ -37,17 +37,14 @@
<div class="toolbar">
<div class="toolbar-group">
<span class="toolbar-label">引擎:</span>
<button class="switch-btn active" data-engine="minimax">
<span class="engine-icon minimax-icon">M</span>
MiniMax
</button>
<button class="switch-btn" data-engine="cosyvoice">
<span class="engine-icon cosyvoice-icon">C</span>
CosyVoice
</button>
<select id="engine-select">
<option value="minimax">MiniMax</option>
<option value="cosyvoice">CosyVoice</option>
</select>
</div>
<div class="toolbar-group emotion-group">
<span class="toolbar-label">情绪:</span>
<div id="emotion-global">
<button class="emotion-btn" data-emotion="happy" title="开心愉快">😊</button>
<button class="emotion-btn" data-emotion="sad" title="悲伤">😢</button>
<button class="emotion-btn" data-emotion="angry" title="激昂">🔥</button>
@@ -56,6 +53,10 @@
<button class="emotion-btn" data-emotion="disgusted" title="厌恶">😒</button>
<span class="toolbar-hint">← 选择整段语音的情绪风格</span>
</div>
<div id="emotion-per-sentence" style="display:none">
逐句标注模式:选中句子后点击情绪标签(即将支持)
</div>
</div>
</div>
<!-- Editor -->
+25 -17
View File
@@ -121,30 +121,38 @@ body {
.toolbar-label { font-size: 0.85rem; color: var(--text-secondary); }
.toolbar-hint { font-size: 0.7rem; color: var(--text-muted); margin-left: 8px; }
.switch-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 12px;
background: transparent;
border: 1px solid var(--border-color);
#engine-select {
padding: 6px 12px;
background: #1a1a25;
border: 1px solid #2a2a3a;
border-radius: var(--radius-sm);
color: var(--text-secondary);
color: #ffffff;
font-size: 0.85rem;
cursor: pointer;
transition: var(--transition-fast);
outline: none;
-webkit-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2371717a' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
padding-right: 28px;
}
.switch-btn:hover { border-color: var(--border-hover); color: var(--text-primary); }
.switch-btn.active { background: var(--bg-card); border-color: var(--accent-primary); color: var(--text-primary); }
#engine-select:hover { border-color: var(--border-hover); }
#engine-select:focus { border-color: var(--accent-primary); }
#engine-select option { background: #1a1a25; color: #ffffff; }
.engine-icon {
width: 18px; height: 18px;
border-radius: var(--radius-sm);
display: flex; align-items: center; justify-content: center;
font-size: 0.65rem; font-weight: 700;
#emotion-global {
display: flex;
align-items: center;
gap: 4px;
}
#emotion-per-sentence {
color: #71717a;
padding: 8px;
font-size: 0.85rem;
}
.minimax-icon { background: var(--minimax-color); color: #0a0a0f; }
.cosyvoice-icon { background: var(--cosyvoice-color); color: white; }
.emotion-btn {
width: 32px; height: 32px;