Files
RGKT/rg-09112127/js/aiStorytelling.js

1172 lines
41 KiB
JavaScript
Raw Normal View History

2025-10-10 19:35:04 +08:00
/**
* AI讲故事主要功能类
* 提供故事库管理阅读体验AI语音交互等功能
*/
class AIStorytelling {
constructor() {
this.stories = [];
this.currentStory = null;
this.currentSection = 0;
this.isSpeaking = false;
this.isVoiceEnabled = true;
this.init();
}
async init() {
await this.loadStories();
this.initEventListeners();
this.renderStoryGrid();
this.initAdvancedFeatures();
if (window.virtualTeacher) {
await window.virtualTeacher.init();
}
console.log('🎭 AI讲故事系统初始化完成');
this.showWelcomeMessage();
this.updateStatsDisplay();
}
initAdvancedFeatures() {
// 初始化搜索功能
this.searchDelay = null;
// 初始化排序功能
this.currentSort = 'default';
// 初始化收藏功能
this.favorites = JSON.parse(localStorage.getItem('storyFavorites') || '[]');
// 初始化语速控制
this.desiredRate = 0.8;
// 初始化自动播放
this.autoPlay = false;
// 初始化进度追踪
this.readingProgress = {};
// 初始化剧情选择系统
this.currentChoices = [];
this.choicesHistory = [];
this.branchingActive = false;
this.waitingForChoice = false;
}
async loadStories() {
try {
const response = await fetch('../data/storyBank.json');
const data = await response.json();
this.stories = data.stories;
console.log('📚 故事数据加载成功:', this.stories.length, '个故事');
// 调试:检查第一个故事的选择项
if (this.stories.length > 0) {
const firstStory = this.stories[0];
console.log('🔍 第一个故事:', firstStory.title);
console.log('🔍 第一个故事的选择项:', firstStory.content?.beginning?.choices);
}
} catch (error) {
console.error('❌ 加载故事数据失败:', error);
this.stories = this.getDefaultStories();
}
}
getDefaultStories() {
return [{
id: "story_default",
title: "欢迎来到故事世界",
category: "欢迎故事",
difficulty: 1,
cover: "asset/logo.png",
description: "这是一个欢迎故事帮助您熟悉AI讲故事功能",
timeEstimate: "2-3分钟",
ageRange: "3-8岁",
content: {
beginning: {
text: "欢迎来到AI讲故事的神奇世界在这里每一页都是一个新奇的冒险每一个故事都充满智慧与乐趣。",
image: "asset/logo.png"
},
middle: {
text: "AI老师会用温柔的声音为您朗读故事就像真正的老师在身边一样。让我们一起来享受这个美好的故事时光吧",
image: "asset/maoz.png"
},
ending: {
text: "希望您喜欢这个AI讲故事世界现在可以选择您感兴趣的故事开始精彩的阅读之旅吧",
image: "asset/sping.png"
}
}
}];
}
initEventListeners() {
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
this.filterStories(e.target.dataset.category);
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
});
});
document.getElementById('closeReader').addEventListener('click', () => {
this.closeReader();
});
document.getElementById('prevSection').addEventListener('click', () => {
this.previousSection();
});
document.getElementById('nextSection').addEventListener('click', () => {
this.nextSection();
});
document.getElementById('readCurrentSection').addEventListener('click', () => {
this.readCurrentSection();
});
document.getElementById('playPauseBtn').addEventListener('click', () => {
this.togglePlayPause();
});
document.getElementById('stopBtn').addEventListener('click', () => {
this.stopSpeaking();
});
// 语音控制面板切换按钮
document.getElementById('voiceToggleBtn').addEventListener('click', () => {
this.toggleVoicePanel();
});
// 语音控制面板关闭按钮
document.getElementById('voiceCloseBtn').addEventListener('click', () => {
this.hideVoicePanel();
});
// 语音开关功能(在语音控制面板内)
document.getElementById('voiceOnOffBtn').addEventListener('click', () => {
this.toggleVoice();
});
// 新增事件监听器
document.getElementById('speedControl')?.addEventListener('click', () => {
this.openSpeedPanel();
});
document.getElementById('autoPlayBtn')?.addEventListener('click', () => {
this.toggleAutoPlay();
});
document.getElementById('bookmarkBtn')?.addEventListener('click', () => {
this.toggleFavorite(this.currentStory?.id);
});
document.getElementById('shareBtn')?.addEventListener('click', () => {
this.shareStory();
});
document.getElementById('sortBy')?.addEventListener('change', (e) => {
this.sortStories(e.target.value);
});
document.addEventListener('keydown', (e) => {
if (document.getElementById('storyReader').classList.contains('active')) {
switch (e.key) {
case 'Escape':
this.closeReader();
break;
case 'ArrowLeft':
this.previousSection();
break;
case 'ArrowRight':
this.nextSection();
break;
case ' ':
e.preventDefault();
this.readCurrentSection();
break;
case 'a':
case 'A':
e.preventDefault();
this.toggleAutoPlay();
break;
case 'b':
case 'B':
e.preventDefault();
this.toggleFavorite(this.currentStory?.id);
break;
}
}
});
document.getElementById('storyReader').addEventListener('click', (e) => {
if (e.target.id === 'storyReader') {
this.closeReader();
}
});
// 语速面板事件监听
const speedSlider = document.getElementById('speedSlider');
const speedCloseBtn = document.getElementById('closeSpeedPanel');
const speedPresets = document.querySelectorAll('.speed-preset');
speedSlider?.addEventListener('input', (e) => {
this.desiredRate = parseFloat(e.target.value);
document.getElementById('speedValue').textContent = `${this.desiredRate.toFixed(1)}x`;
this.updateVoiceSettings();
});
speedCloseBtn?.addEventListener('click', () => {
this.closeSpeedPanel();
});
speedPresets.forEach(preset => {
preset.addEventListener('click', (e) => {
const speed = e.target.dataset.speed;
this.desiredRate = parseFloat(speed);
speedSlider.value = speed;
document.getElementById('speedValue').textContent = `${speed}x`;
this.updateVoiceSettings();
});
});
// 继续从分支剧情按钮
document.getElementById('continueFromBranch')?.addEventListener('click', () => {
this.continueFromBranch();
});
}
showWelcomeMessage() {
setTimeout(() => {
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak("欢迎来到AI故事世界这里有精彩的故事等着你选择一个你喜欢的故事开始吧", {
rate: 0.8,
pitch: 1.0
});
}
}, 2000);
}
filterStories(category) {
const filteredStories = category === 'all'
? this.stories
: this.stories.filter(story => story.category === category);
this.renderStoryGrid(filteredStories);
if (window.virtualTeacher && this.isVoiceEnabled) {
const categoryText = category === 'all' ? '所有故事' : category;
window.virtualTeacher.speak(`现在显示${categoryText},共找到${filteredStories.length}个故事`);
}
}
renderStoryGrid(storiesToRender = this.stories) {
const grid = document.getElementById('storyGrid');
if (storiesToRender.length === 0) {
grid.innerHTML = `
<div style="grid-column: 1 / -1; text-align: center; padding: 50px; color: rgba(255,255,255,0.7);">
<i class="fa fa-book-open" style="font-size: 3rem; margin-bottom: 20px;"></i>
<h3>暂无故事</h3>
<p>该分类下暂时没有故事请选择其他分类</p>
</div>
`;
return;
}
grid.innerHTML = '<div class="loading-spinner"></div> 正在加载故事...';
setTimeout(() => {
grid.innerHTML = storiesToRender.map(story => `
<div class="story-card animate__animated animate__fadeInUp" data-story-id="${story.id}">
<div class="story-cover">
<img src="../${story.cover}" alt="${story.title}" onerror="this.src='../asset/logo.png'">
</div>
<div class="story-info">
<h3 class="story-title">${story.title}</h3>
<p class="story-description">${story.description}</p>
<div class="story-meta">
<span class="story-category">${story.category}</span>
<span class="story-difficulty">
${this.generateDifficultyStars(story.difficulty)}
<span style="margin-left: 5px;">${story.ageRange}</span>
</span>
</div>
<div class="story-meta">
<span><img src="../asset/icon-时间.png" alt="时间" class="meta-icon"> ${story.timeEstimate}</span>
</div>
<div class="story-actions">
<button class="btn-story btn-primary start-reading" onclick="aiStorytelling.openReader('${story.id}')">
<img src="../asset/icon-开始阅读 .png" alt="开始阅读" class="btn-icon"> 开始阅读
</button>
<button class="btn-story btn-secondary story-info" onclick="aiStorytelling.showStoryInfo('${story.id}')">
<img src="../asset/icon-详情.png" alt="详情" class="btn-icon"> 详情
</button>
</div>
</div>
</div>
`).join('');
}, 500);
}
generateDifficultyStars(difficulty) {
return new Array(difficulty).fill('').map(() => '<span class="difficulty-star">⭐</span>').join('');
}
openReader(storyId) {
console.log('🔍 openReader被调用storyId:', storyId);
console.log('📚 当前故事数组:', this.stories?.length || '未定义');
this.currentStory = this.stories.find(story => story.id === storyId);
if (!this.currentStory) {
console.error('❌ 找不到指定故事:', storyId);
console.log('💾 可用故事列表:', this.stories?.map(s => ({ id: s.id, title: s.title })) || 'stories为undefined');
return;
}
console.log('✅ 找到故事:', this.currentStory.title);
this.currentSection = 0;
document.getElementById('readerTitle').textContent = this.currentStory.title;
// 初始化进度到第1段
this.updateReadingProgress(this.currentStory.id, this.currentSection);
this.renderStoryContent();
const reader = document.getElementById('storyReader');
reader.classList.add('active');
document.body.style.overflow = 'hidden';
if (window.virtualTeacher && this.isVoiceEnabled) {
setTimeout(() => {
window.virtualTeacher.speak(`欢迎来到《${this.currentStory.title}》的故事世界!`, {
rate: 0.8,
pitch: 1.1
});
}, 300);
}
console.log('📖 打开故事:', this.currentStory.title);
}
closeReader() {
const reader = document.getElementById('storyReader');
reader.classList.remove('active');
document.body.style.overflow = '';
if (this.isSpeaking) {
this.stopSpeaking();
}
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak('故事时间结束了,期待下次再见!', {
rate: 0.8,
pitch: 1.0
});
}
console.log('📚 故事阅读器已关闭');
}
renderStoryContent() {
const content = document.getElementById('storyContent');
const sections = ['beginning', 'middle', 'ending'];
const currentSectionData = this.currentStory.content[sections[this.currentSection]];
console.log('🔍 当前段落数据:', currentSectionData);
console.log('🔍 是否有选择项:', currentSectionData.choices);
console.log('🔍 选择项数量:', currentSectionData.choices?.length || 0);
// 清除之前的定时器
if (this.branchingTimeout) {
clearTimeout(this.branchingTimeout);
this.branchingTimeout = null;
}
// 清除之前的选择和分支内容
const storyChoices = document.getElementById('storyChoices');
const branchingContent = document.getElementById('branchingContent');
const branchNarrative = document.getElementById('branchNarrative');
const choicesContainer = document.getElementById('choicesContainer');
const choiceFeedback = document.getElementById('choiceFeedback');
console.log('🔍 DOM元素检查:', {
storyChoices: !!storyChoices,
branchingContent: !!branchingContent,
branchNarrative: !!branchNarrative,
choicesContainer: !!choicesContainer,
choiceFeedback: !!choiceFeedback
});
if (storyChoices) storyChoices.style.display = 'none';
if (branchingContent) branchingContent.style.display = 'none';
if (branchNarrative) branchNarrative.innerHTML = '';
if (choicesContainer) choicesContainer.innerHTML = '';
if (choiceFeedback) choiceFeedback.innerHTML = '';
// 只更新故事内容部分,不覆盖选择界面
const storySection = document.createElement('div');
storySection.className = 'story-section';
storySection.innerHTML = `
<h3 style="margin-bottom: 20px; color: #1e40af;">📖 故事 ${this.getCurrentSectionTitle()}</h3>
${currentSectionData.image ? `
<img src="../${currentSectionData.image}" alt="故事插图" class="section-image">
` : ''}
<p class="section-text">${currentSectionData.text}</p>
<div style="text-align: center; margin-top: 20px; color: #64748b; font-size: 0.9rem;">
${this.currentSection + 1} / ${sections.length}
</div>
`;
// 清除之前的故事内容,但保留选择界面
const existingStorySection = content.querySelector('.story-section');
if (existingStorySection) {
content.removeChild(existingStorySection);
}
content.insertBefore(storySection, content.firstChild);
// 渲染内容后刷新进度显示
this.updateReadingProgress(this.currentStory.id, this.currentSection);
// 检查是否有选择项 - 始终显示选择界面,不管是否朗读
if (currentSectionData.choices && currentSectionData.choices.length > 0) {
console.log('✅ 发现选择项,准备显示选择界面');
// 立即显示选择,让用户可以随时选择
setTimeout(() => {
this.showStoryChoices(currentSectionData.choices);
}, 500);
}
// 无论是否有选择项,都开始朗读(如果有语音功能)
if (this.isVoiceEnabled) {
setTimeout(() => {
this.readCurrentSection();
}, 800);
}
}
getCurrentSectionTitle() {
const titles = ['开始', '中间', '结尾'];
return titles[this.currentSection] || '段落';
}
previousSection() {
if (this.currentSection > 0) {
this.currentSection--;
// 更新进度
if (this.currentStory) {
this.updateReadingProgress(this.currentStory.id, this.currentSection);
}
this.renderStoryContent();
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak(`现在来到了故事的${this.getCurrentSectionTitle()}部分`);
}
}
}
nextSection() {
const sections = ['beginning', 'middle', 'ending'];
if (this.currentSection < sections.length - 1) {
this.currentSection++;
// 更新进度
if (this.currentStory) {
this.updateReadingProgress(this.currentStory.id, this.currentSection);
}
this.renderStoryContent();
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak(`现在来到了故事的${this.getCurrentSectionTitle()}部分`);
}
} else {
this.showStoryEnding();
}
}
readCurrentSection() {
if (!this.currentStory) return;
const sections = ['beginning', 'middle', 'ending'];
const currentSectionData = this.currentStory.content[sections[this.currentSection]];
if (window.virtualTeacher) {
this.isSpeaking = true;
const voiceBtn = document.getElementById('readCurrentSection');
voiceBtn.classList.add('speaking');
window.virtualTeacher.speak(currentSectionData.text, {
rate: 0.8,
pitch: 1.1,
onend: () => {
this.isSpeaking = false;
voiceBtn.classList.remove('speaking');
}
});
}
}
showStoryEnding() {
const content = document.getElementById('storyContent');
content.innerHTML = `
<div class="story-section" style="text-align: center;">
<h3 style="color: #667eea; margin-bottom: 20px;">🎉 故事结束了</h3>
<img src="../asset/maoz.png" alt="故事结束" style="width: 150px; height: 150px; border-radius: 50%; margin-bottom: 20px;">
<p style="font-size: 1.2rem; margin-bottom: 20px;">
恭喜你完成了${this.currentStory.title}的阅读
</p>
${this.currentStory.description ? `
<p style="color: #666;">
${this.currentStory.description}
</p>
` : ''}
<div style="margin-top: 30px;">
<button class="btn-story btn-large btn-primary" onclick="aiStorytelling.closeReader()">
结束阅读 <i class="fa fa-bookmark"></i>
</button>
<button class="btn-story btn-large btn-secondary" onclick="aiStorytelling.restartStory()">
重新阅读 <i class="fa fa-refresh"></i>
</button>
</div>
</div>
`;
// 结束时设置进度为 3/3
const sections = ['beginning', 'middle', 'ending'];
this.updateReadingProgress(this.currentStory?.id || '', sections.length - 1);
if (window.virtualTeacher && this.isVoiceEnabled) {
setTimeout(() => {
window.virtualTeacher.speak(`恭喜你完成了《${this.currentStory.title}》的阅读!希望你喜欢这个故事!`);
}, 1000);
}
}
restartStory() {
this.currentSection = 0;
this.renderStoryContent();
// 重启时重置进度为 1/3
if (this.currentStory) {
this.updateReadingProgress(this.currentStory.id, this.currentSection);
}
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak('让我们重新开始这个故事吧!');
}
}
showStoryInfo(storyId) {
const story = this.stories.find(s => s.id === storyId);
if (!story) return;
// 填充对话框内容
const modal = document.getElementById('storyDetailModal');
const detailTitle = document.getElementById('detailTitle');
const detailImage = document.getElementById('detailImage');
const detailCategory = document.getElementById('detailCategory');
const detailCategoryIcon = document.getElementById('detailCategoryIcon');
const detailDifficulty = document.getElementById('detailDifficulty');
const detailDuration = document.getElementById('detailDuration');
const detailAge = document.getElementById('detailAge');
const detailDescription = document.getElementById('detailDescription');
const detailStart = document.getElementById('detailStart');
const detailFavorite = document.getElementById('detailFavorite');
const detailShare = document.getElementById('detailShare');
if (!modal) return;
detailTitle.textContent = story.title;
detailImage.src = `../${story.cover}`;
detailCategory.textContent = story.category;
// 设置类别图标
const categoryIconMap = {
'生活故事': '../asset/icon-shenghuo.png',
'经典故事': '../asset/icon-经典.png',
'数学故事': '../asset/icon-xyz.png',
'梦想故事': '../asset/icon-mengxiang1.png',
'成长故事': '../asset/icon-chengzhang2.png'
};
detailCategoryIcon.src = categoryIconMap[story.category] || '../asset/icon-shenghuo.png';
detailCategoryIcon.alt = story.category;
detailDifficulty.textContent = '⭐'.repeat(story.difficulty || 1);
detailDuration.textContent = story.timeEstimate || '约3分钟';
detailAge.textContent = story.ageRange || '3-8岁';
detailDescription.textContent = story.description || '';
// 绑定按钮动作
detailStart.onclick = () => {
modal.style.display = 'none';
this.openReader(story.id);
};
detailFavorite.onclick = () => this.toggleFavorite(story.id);
detailShare.onclick = () => this.shareStory();
// 打开对话框
modal.style.display = 'block';
// 关闭事件(遮罩与关闭按钮)
const closeBtn = document.getElementById('closeDetail');
const backdrop = document.getElementById('detailBackdrop');
const close = () => { modal.style.display = 'none'; };
closeBtn && (closeBtn.onclick = close);
backdrop && (backdrop.onclick = close);
}
togglePlayPause() {
const btn = document.getElementById('playPauseBtn');
const icon = btn.querySelector('i');
if (this.isSpeaking) {
this.stopSpeaking();
icon.className = 'fa fa-play';
} else {
this.readCurrentSection();
icon.className = 'fa fa-pause';
}
}
stopSpeaking() {
if (window.virtualTeacher) {
window.virtualTeacher.stopAllSpeech();
}
this.isSpeaking = false;
document.querySelectorAll('.speaking').forEach(el => el.classList.remove('speaking'));
const playBtn = document.getElementById('playPauseBtn');
const icon = playBtn.querySelector('i');
icon.className = 'fa fa-play';
}
toggleVoice() {
this.isVoiceEnabled = !this.isVoiceEnabled;
const btn = document.getElementById('voiceOnOffBtn');
const icon = btn.querySelector('i');
if (this.isVoiceEnabled) {
icon.className = 'fa fa-volume-up';
btn.title = '关闭语音';
voiceBroadcast('语音功能已开启');
document.getElementById('voiceStatus').textContent = '语音已开启';
document.getElementById('voiceStatus').style.color = '#3b82f6';
} else {
icon.className = 'fa fa-volume-off';
btn.title = '开启语音';
voiceBroadcast('语音功能已关闭');
this.stopSpeaking();
document.getElementById('voiceStatus').textContent = '语音已关闭';
document.getElementById('voiceStatus').style.color = '#94a3b8';
}
}
// 切换语音控制面板显示/隐藏
toggleVoicePanel() {
const voiceControls = document.getElementById('voiceControls');
const toggleBtn = document.getElementById('voiceToggleBtn');
if (voiceControls.style.display === 'none' || voiceControls.style.display === '') {
this.showVoicePanel();
} else {
this.hideVoicePanel();
}
}
// 显示语音控制面板
showVoicePanel() {
const voiceControls = document.getElementById('voiceControls');
const toggleBtn = document.getElementById('voiceToggleBtn');
voiceControls.style.display = 'block';
toggleBtn.style.display = 'none';
// 添加显示动画
setTimeout(() => {
voiceControls.style.opacity = '1';
voiceControls.style.transform = 'translateY(0)';
}, 10);
}
// 隐藏语音控制面板
hideVoicePanel() {
const voiceControls = document.getElementById('voiceControls');
const toggleBtn = document.getElementById('voiceToggleBtn');
voiceControls.style.opacity = '0';
voiceControls.style.transform = 'translateY(20px)';
setTimeout(() => {
voiceControls.style.display = 'none';
toggleBtn.style.display = 'block';
}, 300);
}
// 新增功能方法
updateStatsDisplay() {
const totalStories = this.stories.length;
const totalDuration = this.stories.reduce((sum, story) => {
const duration = parseInt(story.timeEstimate?.match(/\d+/)?.[0] || '3');
return sum + duration;
}, 0);
document.getElementById('totalStories').textContent = totalStories;
document.getElementById('totalDuration').textContent = `${totalDuration}分钟`;
}
openSpeedPanel() {
const panel = document.getElementById('speedPanel');
if (panel) {
panel.classList.add('active');
document.getElementById('speedSlider').value = this.desiredRate;
document.getElementById('speedValue').textContent = `${this.desiredRate.toFixed(1)}x`;
}
}
closeSpeedPanel() {
const panel = document.getElementById('speedPanel');
if (panel) {
panel.classList.remove('active');
}
}
updateVoiceSettings() {
// 更新语音设置,将应用到后续的朗读中
console.log('语速已更新为:', this.desiredRate);
}
toggleAutoPlay() {
this.autoPlay = !this.autoPlay;
const btn = document.getElementById('autoPlayBtn');
const icon = btn.querySelector('i');
if (this.autoPlay) {
icon.className = 'fa fa-pause-circle';
btn.querySelector('.btn-text').textContent = '关闭自动';
btn.style.background = 'linear-gradient(135deg, #ef4444, #dc2626)';
if (this.isVoiceEnabled && window.virtualTeacher) {
window.virtualTeacher.speak('自动播放已开启,我将自动为您朗读每个段落');
}
} else {
icon.className = 'fa fa-play-circle';
btn.querySelector('.btn-text').textContent = '自动播放';
btn.style.background = 'linear-gradient(135deg, #10b981, #059669)';
if (window.virtualTeacher) {
window.virtualTeacher.stopAllSpeech();
}
}
}
toggleFavorite(storyId) {
if (!storyId) return;
const index = this.favorites.indexOf(storyId);
const btn = document.getElementById('bookmarkBtn');
const icon = btn.querySelector('i');
if (index > -1) {
this.favorites.splice(index, 1);
icon.className = 'fa fa-bookmark-o';
btn.style.color = '#ffffff';
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak('已从收藏夹中移除');
}
} else {
this.favorites.push(storyId);
icon.className = 'fa fa-bookmark';
btn.style.color = '#fbbf24';
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak('已添加到收藏夹');
}
}
localStorage.setItem('storyFavorites', JSON.stringify(this.favorites));
}
shareStory() {
if (!this.currentStory) return;
const storyInfo = {
title: this.currentStory.title,
description: this.currentStory.description,
category: this.currentStory.category,
url: window.location.href
};
if (navigator.share) {
navigator.share({
title: `${storyInfo.title} - AI讲故事`,
text: storyInfo.description,
url: storyInfo.url
});
} else {
// 复制到剪贴板
const shareText = `🌟 推荐一个AI故事${storyInfo.title}\n\n${storyInfo.description}\n\n🔗 ${storyInfo.url}`;
navigator.clipboard.writeText(shareText).then(() => {
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak('故事链接已复制到剪贴板');
}
});
}
}
sortStories(sortType) {
this.currentSort = sortType;
let sortedStories = [...this.stories];
switch (sortType) {
case 'difficulty':
sortedStories.sort((a, b) => a.difficulty - b.difficulty);
break;
case 'duration':
sortedStories.sort((a, b) => {
const durationA = parseInt(a.timeEstimate?.match(/\d+/)?.[0] || '3');
const durationB = parseInt(b.timeEstimate?.match(/\d+/)?.[0] || '3');
return durationA - durationB;
});
break;
case 'popularity':
// 基于收藏数量或人气排序(这里简化为随机)
sortedStories.sort(() => Math.random() - 0.5);
break;
default:
// 默认排序
break;
}
this.renderStoryGrid(sortedStories);
if (window.virtualTeacher && this.isVoiceEnabled) {
const sortText = {
'default': '默认顺序',
'difficulty': '按难度排序',
'duration': '按时长排序',
'popularity': '按受欢迎度排序'
};
window.virtualTeacher.speak(`故事已按${sortText[sortType]}排列`);
}
}
searchStories(query) {
if (!query.trim()) {
this.renderStoryGrid();
return;
}
const filteredStories = this.stories.filter(story => {
const searchText = `${story.title} ${story.description} ${story.category}`.toLowerCase();
return searchText.includes(query.toLowerCase());
});
this.renderStoryGrid(filteredStories);
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak(`搜索到${filteredStories.length}个相关故事`);
}
}
updateReadingProgress(storyId, sectionIndex) {
this.readingProgress[storyId] = sectionIndex;
localStorage.setItem('readingProgress', JSON.stringify(this.readingProgress));
// 更新进度条
const sections = ['beginning', 'middle', 'ending'];
const progress = ((sectionIndex + 1) / sections.length) * 100;
document.getElementById('progressFill').style.width = `${progress}%`;
document.getElementById('progressText').textContent = `${sectionIndex + 1}/${sections.length}`;
}
// 剧情选择系统方法
showStoryChoices(choices) {
console.log('🎭 showStoryChoices被调用选择项:', choices);
this.waitingForChoice = true;
this.currentChoices = choices;
// 显示选择界面
const choicesContainer = document.getElementById('choicesContainer');
const choicesElement = document.getElementById('storyChoices');
console.log('🔍 选择界面元素检查:', {
choicesContainer: !!choicesContainer,
choicesElement: !!choicesElement
});
if (choicesContainer) {
const choicesHTML = choices.map((choice, index) => `
<button class="choice-btn" data-choice-index="${index}" onclick="aiStorytelling.makeChoice(${index})">
${choice.choiceText}
</button>
`).join('');
console.log('🔍 生成的选择按钮HTML:', choicesHTML);
choicesContainer.innerHTML = choicesHTML;
}
if (choicesElement) {
choicesElement.style.display = 'block';
console.log('✅ 选择界面已显示');
}
// 不播报选择提示,让用户直接看到选择界面
console.log('🎭 选择界面已显示,用户可以随时选择');
// 添加提示说明用户可以随时选择
const choicesHeader = document.querySelector('.choices-header h3');
if (choicesHeader) {
choicesHeader.textContent = '🎭 在开始朗读前,请选择故事的走向!';
}
const choicesSubtitle = document.querySelector('.choices-header p');
if (choicesSubtitle) {
choicesSubtitle.textContent = '请选择一个选项,这将决定故事的发展方向。您可以在语音朗读过程中随时选择:';
}
}
makeChoice(choiceIndex) {
if (!this.waitingForChoice || choiceIndex < 0 || choiceIndex >= this.currentChoices.length) {
return;
}
const choice = this.currentChoices[choiceIndex];
// 不设置waitingForChoice = false让用户可以继续选择
// 记录选择历史(只记录最后一次选择)
const currentSectionKey = ['beginning', 'middle', 'ending'][this.currentSection];
// 移除当前段落之前的选择记录
this.choicesHistory = this.choicesHistory.filter(record =>
!(record.storyId === this.currentStory.id && record.section === currentSectionKey)
);
// 添加新的选择记录
this.choicesHistory.push({
storyId: this.currentStory.id,
section: currentSectionKey,
choice: choice.choiceText,
consequence: choice.consequence,
timestamp: new Date().toISOString()
});
// 更新UI显示选择结果只能选择一个选项可以更换选择
const choiceButtons = document.querySelectorAll('.choice-btn');
choiceButtons.forEach((btn, index) => {
// 清除所有按钮的选择状态
btn.classList.remove('clicked');
btn.removeAttribute('data-selected');
// 只标记当前选择的按钮
if (index === choiceIndex) {
btn.classList.add('clicked');
btn.setAttribute('data-selected', 'true');
}
});
// 显示选择反馈
const feedbackPosition = document.getElementById('choiceFeedback');
if (feedbackPosition) {
feedbackPosition.innerHTML = `
<p class="choice-feedback-text">
🎉 ${choice.effect}
</p>
`;
console.log('📝 选择反馈已更新:', choice.effect);
}
// 停止之前的语音播报
if (window.virtualTeacher) {
window.virtualTeacher.stopSpeaking();
}
// 先显示分支剧情,然后按顺序播报语音
console.log('🎭 立即显示分支剧情:', choice.consequence);
this.showBranchingNarrative(choice.consequence);
// 语音播报选择的选项文字,读完后再读分支剧情
if (window.virtualTeacher && this.isVoiceEnabled) {
const cleanChoiceText = choice.choiceText.replace(/[🌟🐾🧺🌅🌞🌆🎣🎈👥🚪🌲🧱💨🤝🏡🌧️🎣🏃🏠🌾🤝🏃]/g, '');
console.log('🔊 开始朗读选项文字:', cleanChoiceText);
// 朗读选项的文字内容,读完后再读分支剧情
window.virtualTeacher.speak(cleanChoiceText, {
rate: this.desiredRate,
pitch: 1.0,
onend: () => {
console.log('✅ 选项文字朗读完成,停顿后读分支剧情');
// 停顿2秒后读分支剧情
setTimeout(() => {
const branchingNarrative = this.currentStory.branchingNarratives?.[choice.consequence];
if (branchingNarrative) {
console.log('🔊 开始朗读分支剧情:', branchingNarrative.text);
window.virtualTeacher.speak(branchingNarrative.text, {
rate: this.desiredRate,
pitch: 1.0,
onend: () => {
console.log('✅ 分支剧情朗读完成');
// 停顿2秒后提示继续
setTimeout(() => {
window.virtualTeacher.speak("点击继续故事按钮来继续阅读");
}, 2000);
},
onerror: () => {
console.log('⚠️ 分支剧情语音播报出错,直接提示继续');
setTimeout(() => {
window.virtualTeacher.speak("点击继续故事按钮来继续阅读");
}, 1000);
}
});
}
}, 2000);
},
onerror: () => {
console.log('⚠️ 选项文字语音播报出错,直接读分支剧情');
// 如果选项文字播报出错,直接读分支剧情
setTimeout(() => {
const branchingNarrative = this.currentStory.branchingNarratives?.[choice.consequence];
if (branchingNarrative) {
console.log('🔊 开始朗读分支剧情:', branchingNarrative.text);
window.virtualTeacher.speak(branchingNarrative.text, {
rate: this.desiredRate,
pitch: 1.0,
onend: () => {
console.log('✅ 分支剧情朗读完成');
setTimeout(() => {
window.virtualTeacher.speak("点击继续故事按钮来继续阅读");
}, 2000);
},
onerror: () => {
console.log('⚠️ 分支剧情语音播报也出错,直接提示继续');
setTimeout(() => {
window.virtualTeacher.speak("点击继续故事按钮来继续阅读");
}, 1000);
}
});
}
}, 1000);
}
});
}
// 备用方案如果3秒后还没有开始读分支剧情强制开始
this.branchingTimeout = setTimeout(() => {
console.log('⚠️ 备用方案:强制开始读分支剧情');
const branchingNarrative = this.currentStory.branchingNarratives?.[choice.consequence];
if (branchingNarrative) {
console.log('🔊 备用方案开始朗读分支剧情:', branchingNarrative.text);
window.virtualTeacher.speak(branchingNarrative.text, {
rate: this.desiredRate,
pitch: 1.0,
onend: () => {
console.log('✅ 备用方案分支剧情朗读完成');
setTimeout(() => {
window.virtualTeacher.speak("点击继续故事按钮来继续阅读");
}, 2000);
},
onerror: () => {
console.log('⚠️ 备用方案分支剧情语音播报也出错,直接提示继续');
setTimeout(() => {
window.virtualTeacher.speak("点击继续故事按钮来继续阅读");
}, 1000);
}
});
}
}, 3000);
}
showBranchingNarrative(consequenceKey) {
console.log('🔍 查找分支剧情:', consequenceKey);
const branchingNarrative = this.currentStory.branchingNarratives?.[consequenceKey];
if (!branchingNarrative) {
console.error('❌ 找不到分支剧情:', consequenceKey);
console.log('📚 当前故事的分支剧情:', this.currentStory.branchingNarratives);
return;
}
console.log('✅ 找到分支剧情:', branchingNarrative);
// 停止之前的语音播报
if (window.virtualTeacher) {
window.virtualTeacher.stopSpeaking();
}
const branchingContent = document.getElementById('branchingContent');
const branchNarrative = document.getElementById('branchNarrative');
if (branchNarrative) {
branchNarrative.innerHTML = `
<div style="font-weight: 600; color: #1e40af; margin-bottom: 15px; font-size: 1.1rem;">🎭 你的选择带来了新的故事发展</div>
<div style="line-height: 1.7; color: #374151;">${branchingNarrative.text}</div>
`;
console.log('📖 分支剧情内容已更新:', branchingNarrative.text);
}
if (branchingContent) {
branchingContent.style.display = 'block';
}
// 语音播报现在在makeChoice方法中处理这里不再播报
}
continueFromBranch() {
// 清除之前的定时器
if (this.branchingTimeout) {
clearTimeout(this.branchingTimeout);
this.branchingTimeout = null;
}
// 隐藏并清除分支内容
const branchingContent = document.getElementById('branchingContent');
const branchNarrative = document.getElementById('branchNarrative');
const storyChoices = document.getElementById('storyChoices');
const choicesContainer = document.getElementById('choicesContainer');
const choiceFeedback = document.getElementById('choiceFeedback');
if (branchingContent) branchingContent.style.display = 'none';
if (storyChoices) storyChoices.style.display = 'none';
// 清除所有内容
if (branchNarrative) branchNarrative.innerHTML = '';
if (choicesContainer) choicesContainer.innerHTML = '';
if (choiceFeedback) choiceFeedback.innerHTML = '';
this.waitingForChoice = false;
// 点击"继续故事"后,总是进入下一段故事
setTimeout(() => {
this.nextSection();
}, 1000);
// 语音反馈
if (window.virtualTeacher && this.isVoiceEnabled) {
window.virtualTeacher.speak("很好!故事继续发展...");
}
}
updateControlButtons() {
const prevBtn = document.getElementById('prevSection');
const nextBtn = document.getElementById('nextSection');
const readBtn = document.getElementById('readCurrentSection');
// 更新按钮状态
if (prevBtn) prevBtn.disabled = this.currentSection === 0;
const sections = ['beginning', 'middle', 'ending'];
if (nextBtn) nextBtn.disabled = this.currentSection >= sections.length - 1;
if (readBtn) readBtn.disabled = this.waitingForChoice;
// 更新按钮文字
if (readBtn) {
const readText = readBtn.querySelector('.btn-text');
if (readText) {
readText.textContent = this.waitingForChoice ? '请先做选择' : '朗读本段';
}
}
}
}
// 创建全局实例
window.aiStorytelling = new AIStorytelling();
// 防止页面刷新时状态丢失
window.addEventListener('beforeunload', () => {
if (window.virtualTeacher) {
window.virtualTeacher.stopAllSpeech();
}
});