XiaoBai1221's picture
Done
6c78660
let currentState = 'idle';
function setState(newState, options = {}) {
if (currentState === newState) {
return;
}
const oldState = currentState;
currentState = newState;
micContainer.classList.remove('recording', 'thinking', 'speaking', 'disconnected');
switch(newState) {
case 'idle':
hideAgentOutput();
if (typeof stopSpeaking === 'function') {
stopSpeaking();
}
if (options.clearCards !== false) {
clearAllCards();
}
break;
case 'recording':
micContainer.classList.add('recording');
if (!options.keepOutput) {
hideAgentOutput();
}
if (!options.keepCards) {
clearAllCards();
}
transcript.textContent = '聆聽中...';
transcript.className = 'voice-transcript provisional';
break;
case 'thinking':
micContainer.classList.add('thinking');
hideAgentOutput();
if (typeof stopSpeaking === 'function') {
stopSpeaking();
}
break;
case 'speaking':
micContainer.classList.add('speaking');
if (options.outputText) {
typewriterEffect(options.outputText, 40, options.enableTTS);
}
break;
case 'disconnected':
micContainer.classList.add('disconnected');
hideAgentOutput();
if (typeof stopSpeaking === 'function') {
stopSpeaking();
}
clearAllCards();
break;
default:
console.error(`❌ 未知狀態: ${newState}`);
}
}
function applyEmotion(emotion) {
const validEmotions = ['neutral', 'happy', 'sad', 'angry', 'fear', 'surprise'];
if (!validEmotions.includes(emotion)) {
emotion = 'neutral';
}
background.className = `voice-immersive-background emotion-${emotion} active`;
emotionIndicator.textContent = `當前情緒: ${emotionEmojis[emotion]}`;
}
function showErrorNotification(message) {
console.error('🚨 錯誤:', message);
setState('speaking', {
outputText: `抱歉,發生錯誤:${message}`,
enableTTS: false
});
setTimeout(() => setState('idle'), 3000);
}
let isThinking = false;
let isDisconnected = false;
let isRecording = false;
let isSpeaking = false;
function initAgentControls() {
micContainer.addEventListener('click', async () => {
if (currentState === 'recording') {
isRecording = false;
if (typeof stopRealAudioAnalysis === 'function') {
stopRealAudioAnalysis();
}
if (wsManager && typeof wsManager.stopRecording === 'function') {
wsManager.stopRecording();
}
setState('thinking');
return;
}
if (currentState === 'idle' || currentState === 'disconnected' || currentState === 'speaking') {
if (currentState === 'speaking' && typeof stopSpeaking === 'function') {
stopSpeaking();
}
isRecording = true;
setState('recording', {
keepOutput: true, // 保留前次 Agent 回應
keepCards: true // 保留前次工具卡片
});
if (typeof startRealAudioAnalysis === 'function') {
await startRealAudioAnalysis();
}
if (wsManager && typeof wsManager.startRecording === 'function') {
const success = await wsManager.startRecording();
if (!success) {
console.error('❌ 錄音啟動失敗');
setState('idle');
isRecording = false;
if (typeof stopRealAudioAnalysis === 'function') {
stopRealAudioAnalysis();
}
}
} else {
console.error('❌ WebSocket 管理器未初始化');
setState('idle');
isRecording = false;
if (typeof stopRealAudioAnalysis === 'function') {
stopRealAudioAnalysis();
}
}
}
});
document.getElementById('toggle-recording').addEventListener('click', async () => {
isRecording = !isRecording;
if (isRecording) {
setState('recording');
await startRealAudioAnalysis();
} else {
setState('idle');
stopRealAudioAnalysis();
}
});
document.getElementById('toggle-thinking').addEventListener('click', () => {
isThinking = !isThinking;
if (isThinking) {
setState('thinking');
} else {
setState('idle', {clearCards: false}); // 保留工具卡片
}
});
document.getElementById('toggle-speaking').addEventListener('click', () => {
isSpeaking = !isSpeaking;
if (isSpeaking) {
clearAllCards();
setTimeout(() => addToolCard('weather'), 300);
const responseText = '根據目前的天氣資料,台北今天氣溫約 23°C,天氣晴朗,濕度 65%。建議您外出時可以穿著輕便舒適的衣物,並記得攜帶太陽眼鏡。';
setState('speaking', {outputText: responseText});
} else {
setState('idle', {clearCards: false}); // 保留工具卡片
}
});
document.getElementById('toggle-disconnected').addEventListener('click', () => {
isDisconnected = !isDisconnected;
if (isDisconnected) {
setState('disconnected');
} else {
setState('idle');
}
});
}