ad5d2fe204
refactor(script): improve UI feedback with styled thinking and response elements docs: add project planning and feature list documents style(script): add visual styling for better user feedback chore: update PRD and README to reflect current project status
190 lines
7.8 KiB
JavaScript
190 lines
7.8 KiB
JavaScript
import BellaAI from './core.js';
|
|
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
// --- Get all necessary DOM elements first ---
|
|
const transcriptDiv = document.getElementById('transcript');
|
|
const loadingScreen = document.getElementById('loading-screen');
|
|
const video1 = document.getElementById('video1');
|
|
const video2 = document.getElementById('video2');
|
|
const micButton = document.getElementById('mic-button');
|
|
|
|
|
|
// --- AI Core Initialization ---
|
|
let bellaAI;
|
|
micButton.disabled = true;
|
|
transcriptDiv.textContent = '正在唤醒贝拉的核心...';
|
|
try {
|
|
bellaAI = await BellaAI.getInstance();
|
|
micButton.disabled = false;
|
|
transcriptDiv.textContent = '贝拉已准备好,请点击麦克风开始对话。';
|
|
} catch (error) {
|
|
console.error('Failed to initialize Bella AI:', error);
|
|
transcriptDiv.textContent = '唤醒贝拉时遇到问题,请检查控制台信息。';
|
|
// Keep the app running with visual features even if AI fails
|
|
}
|
|
|
|
// --- Loading screen handling ---
|
|
setTimeout(() => {
|
|
loadingScreen.style.opacity = '0';
|
|
// Hide it after the animation to prevent it from blocking interactions
|
|
setTimeout(() => {
|
|
loadingScreen.style.display = 'none';
|
|
}, 500); // This time should match the transition time in CSS
|
|
}, 1500); // Start fading out after 1.5 seconds
|
|
|
|
let activeVideo = video1;
|
|
let inactiveVideo = video2;
|
|
|
|
// 视频列表
|
|
const videoList = [
|
|
'视频资源/3D 建模图片制作.mp4',
|
|
'视频资源/jimeng-2025-07-16-1043-笑着优雅的左右摇晃,过一会儿手扶着下巴,保持微笑.mp4',
|
|
'视频资源/jimeng-2025-07-16-4437-比耶,然后微笑着优雅的左右摇晃.mp4',
|
|
'视频资源/生成加油视频.mp4',
|
|
'视频资源/生成跳舞视频.mp4',
|
|
'视频资源/负面/jimeng-2025-07-16-9418-双手叉腰,嘴巴一直在嘟囔,表情微微生气.mp4'
|
|
];
|
|
|
|
// --- 视频交叉淡入淡出播放功能 ---
|
|
function switchVideo() {
|
|
// 1. 选择下一个视频
|
|
const currentVideoSrc = activeVideo.querySelector('source').getAttribute('src');
|
|
let nextVideoSrc = currentVideoSrc;
|
|
while (nextVideoSrc === currentVideoSrc) {
|
|
const randomIndex = Math.floor(Math.random() * videoList.length);
|
|
nextVideoSrc = videoList[randomIndex];
|
|
}
|
|
|
|
// 2. 设置不活动的 video 元素的 source
|
|
inactiveVideo.querySelector('source').setAttribute('src', nextVideoSrc);
|
|
inactiveVideo.load();
|
|
|
|
// 3. 当不活动的视频可以播放时,执行切换
|
|
inactiveVideo.addEventListener('canplaythrough', function onCanPlayThrough() {
|
|
// 确保事件只触发一次
|
|
inactiveVideo.removeEventListener('canplaythrough', onCanPlayThrough);
|
|
|
|
// 4. 播放新视频
|
|
inactiveVideo.play().catch(error => {
|
|
console.error("Video play failed:", error);
|
|
});
|
|
|
|
// 5. 切换 active class 来触发 CSS 过渡
|
|
activeVideo.classList.remove('active');
|
|
inactiveVideo.classList.add('active');
|
|
|
|
// 6. 更新角色
|
|
[activeVideo, inactiveVideo] = [inactiveVideo, activeVideo];
|
|
|
|
// 为新的 activeVideo 绑定 ended 事件
|
|
activeVideo.addEventListener('ended', switchVideo, { once: true });
|
|
}, { once: true }); // 使用 { once: true } 确保事件只被处理一次
|
|
}
|
|
|
|
// 初始启动
|
|
activeVideo.addEventListener('ended', switchVideo, { once: true });
|
|
|
|
|
|
// --- 语音识别核心 ---
|
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
let recognition;
|
|
|
|
// 检查浏览器是否支持语音识别
|
|
if (SpeechRecognition) {
|
|
recognition = new SpeechRecognition();
|
|
recognition.continuous = true; // 持续识别
|
|
recognition.lang = 'zh-CN'; // 设置语言为中文
|
|
recognition.interimResults = true; // 获取临时结果
|
|
|
|
recognition.onresult = async (event) => {
|
|
const transcriptContainer = document.getElementById('transcript');
|
|
let final_transcript = '';
|
|
let interim_transcript = '';
|
|
|
|
for (let i = event.resultIndex; i < event.results.length; ++i) {
|
|
if (event.results[i].isFinal) {
|
|
final_transcript += event.results[i][0].transcript;
|
|
} else {
|
|
interim_transcript += event.results[i][0].transcript;
|
|
}
|
|
}
|
|
|
|
// Update interim results
|
|
transcriptContainer.textContent = `你: ${final_transcript || interim_transcript}`;
|
|
|
|
// Once we have a final result, process it with the AI
|
|
if (final_transcript && bellaAI) {
|
|
const userText = final_transcript.trim();
|
|
transcriptContainer.textContent = `你: ${userText}`;
|
|
|
|
try {
|
|
// Let Bella think
|
|
const thinkingText = document.createElement('p');
|
|
thinkingText.textContent = '贝拉正在思考...';
|
|
thinkingText.style.color = '#888';
|
|
thinkingText.style.fontStyle = 'italic';
|
|
transcriptContainer.appendChild(thinkingText);
|
|
|
|
const response = await bellaAI.think(userText);
|
|
|
|
transcriptContainer.removeChild(thinkingText);
|
|
const bellaText = document.createElement('p');
|
|
bellaText.textContent = `贝拉: ${response}`;
|
|
bellaText.style.color = '#ff6b9d';
|
|
bellaText.style.fontWeight = 'bold';
|
|
bellaText.style.marginTop = '10px';
|
|
transcriptContainer.appendChild(bellaText);
|
|
|
|
// TTS功能暂时禁用,将在下一阶段激活
|
|
// TODO: 激活语音合成功能
|
|
// const audioData = await bellaAI.speak(response);
|
|
// const blob = new Blob([audioData], { type: 'audio/wav' });
|
|
// const audioUrl = URL.createObjectURL(blob);
|
|
// const audio = new Audio(audioUrl);
|
|
// audio.play();
|
|
|
|
} catch (error) {
|
|
console.error('Bella AI processing error:', error);
|
|
const errorText = document.createElement('p');
|
|
errorText.textContent = '贝拉处理时遇到问题,但她还在努力学习中...';
|
|
errorText.style.color = '#ff9999';
|
|
transcriptContainer.appendChild(errorText);
|
|
}
|
|
}
|
|
};
|
|
|
|
recognition.onerror = (event) => {
|
|
console.error('语音识别错误:', event.error);
|
|
};
|
|
|
|
} else {
|
|
console.log('您的浏览器不支持语音识别功能。');
|
|
// 可以在界面上给用户提示
|
|
}
|
|
|
|
// --- 麦克风按钮交互 ---
|
|
let isListening = false;
|
|
|
|
micButton.addEventListener('click', function() {
|
|
if (!SpeechRecognition) return; // 如果不支持,则不执行任何操作
|
|
|
|
isListening = !isListening;
|
|
micButton.classList.toggle('is-listening', isListening);
|
|
const transcriptContainer = document.querySelector('.transcript-container');
|
|
const transcriptText = document.getElementById('transcript');
|
|
|
|
if (isListening) {
|
|
transcriptText.textContent = '聆听中...'; // 立刻显示提示
|
|
transcriptContainer.classList.add('visible');
|
|
recognition.start();
|
|
} else {
|
|
recognition.stop();
|
|
transcriptContainer.classList.remove('visible');
|
|
transcriptText.textContent = ''; // 清空文本
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
}); |