2 Commits
3 changed files with 155 additions and 9 deletions
Binary file not shown.
BIN
View File
Binary file not shown.
+153 -7
View File
@@ -124,6 +124,96 @@
color: #999;
}
/* TTS 控制选项 */
.tts-controls {
display: flex;
gap: 15px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #eee;
align-items: center;
}
.auto-play-switch {
display: flex;
align-items: center;
gap: 8px;
}
.switch {
position: relative;
width: 44px;
height: 22px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .3s;
border-radius: 22px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .3s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #667eea;
}
input:checked + .slider:before {
transform: translateX(22px);
}
.volume-control {
display: flex;
align-items: center;
gap: 8px;
}
.volume-control input[type="range"] {
width: 80px;
height: 6px;
border-radius: 3px;
background: #ddd;
outline: none;
-webkit-appearance: none;
}
.volume-control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
}
.volume-value {
font-size: 12px;
color: #666;
min-width: 35px;
}
.voice-select {
margin-top: 10px;
}
@@ -372,9 +462,9 @@
<div class="name">🌐 Edge TTS</div>
<div class="status" id="edgeStatus">检测中...</div>
</div>
<div class="tts-option disabled" data-provider="chattts">
<div class="tts-option" data-provider="chattts">
<div class="name">🤖 ChatTTS</div>
<div class="status">暂未部署</div>
<div class="status" id="chatttsStatus">检测中...</div>
</div>
</div>
<div class="voice-select" id="voiceSelect" style="display: none;">
@@ -388,6 +478,20 @@
<option value="zh-CN-XiaoyouNeural">晓悠(女)</option>
</select>
</div>
<div class="tts-controls" id="ttsControls" style="display: none;">
<div class="auto-play-switch">
<label class="switch">
<input type="checkbox" id="autoPlaySwitch" checked>
<span class="slider"></span>
</label>
<span>自动播放</span>
</div>
<div class="volume-control">
<span>🔊</span>
<input type="range" id="volumeSlider" min="0.5" max="2" step="0.1" value="1.5">
<span class="volume-value" id="volumeValue">150%</span>
</div>
</div>
</div>
<!-- 录音 -->
@@ -439,6 +543,8 @@
let conversationId = null;
let currentTTSProvider = 'none';
let currentVoice = 'zh-CN-XiaoxiaoNeural';
let autoPlay = true; // 自动播放开关
let volumeLevel = 1.5; // 音量倍率
// 元素
const statusDot = document.getElementById('statusDot');
@@ -453,6 +559,10 @@
const ttsOptions = document.getElementById('ttsOptions');
const voiceSelect = document.getElementById('voiceSelect');
const voiceDropdown = document.getElementById('voiceDropdown');
const ttsControls = document.getElementById('ttsControls');
const autoPlaySwitch = document.getElementById('autoPlaySwitch');
const volumeSlider = document.getElementById('volumeSlider');
const volumeValue = document.getElementById('volumeValue');
// 初始化
async function init() {
@@ -490,13 +600,20 @@
// 更新状态
data.providers.forEach(p => {
if (p.name === 'edge') {
const statusEl = document.getElementById('edgeStatus');
const statusElId = p.name === 'edge' ? 'edgeStatus' :
p.name === 'chattts' ? 'chatttsStatus' : null;
if (statusElId) {
const statusEl = document.getElementById(statusElId);
statusEl.textContent = p.available ? '可用 ✓' : '不可用';
const optionEl = ttsOptions.querySelector('[data-provider="edge"]');
const optionEl = ttsOptions.querySelector(`[data-provider="${p.name}"]`);
if (optionEl) {
if (p.available) {
optionEl.classList.remove('disabled');
} else {
optionEl.classList.add('disabled');
}
}
}
});
@@ -524,6 +641,9 @@
// 显示/隐藏音色选择
voiceSelect.style.display = provider === 'edge' ? 'block' : 'none';
// 显示/隐藏控制选项(有TTS才显示)
ttsControls.style.display = provider !== 'none' ? 'flex' : 'none';
// 保存设置
saveTTSSettings();
}
@@ -772,8 +892,10 @@
`;
} else if (role === 'assistant') {
let audioHtml = '';
let audioBtnId = '';
if (audioData) {
audioHtml = `<button class="play-btn tts-play-btn" onclick="playAudio('${audioData}', this)">
audioBtnId = `audioBtn_${Date.now()}`;
audioHtml = `<button class="play-btn tts-play-btn" id="${audioBtnId}" onclick="playAudio('${audioData}', this)">
<span class="play-icon">🔊</span>
<span>播放回复</span>
</button>`;
@@ -782,6 +904,16 @@
<div class="role">AI</div>
<div class="content">${content}${audioHtml}</div>
`;
// 自动播放(如果开启)
if (audioData && autoPlay) {
setTimeout(() => {
const btn = document.getElementById(audioBtnId);
if (btn) {
playAudio(audioData, btn);
}
}, 100);
}
} else {
msg.innerHTML = `<div class="role">我</div><div class="content">${content}</div>`;
}
@@ -795,13 +927,16 @@
const audio = new Audio(url);
const icon = btn.querySelector('.play-icon');
// 应用音量倍率
audio.volume = Math.min(volumeLevel, 2); // 最大不超过2
audio.onplay = () => {
icon.textContent = '🔊';
btn.classList.add('playing');
};
audio.onended = () => {
icon.textContent = url.startsWith('/audio') ? '🔊' : '▶️';
icon.textContent = url.startsWith('/audio') || url.startsWith('http') ? '🔊' : '▶️';
btn.classList.remove('playing');
};
@@ -855,6 +990,17 @@
saveTTSSettings();
});
// 自动播放开关
autoPlaySwitch.addEventListener('change', () => {
autoPlay = autoPlaySwitch.checked;
});
// 音量控制
volumeSlider.addEventListener('input', () => {
volumeLevel = parseFloat(volumeSlider.value);
volumeValue.textContent = `${Math.round(volumeLevel * 100)}%`;
});
recordBtn.addEventListener('click', () => {
isRecording ? stopRecording() : startRecording();
});