Update dialogue practice with DeepSeek API integration

This commit is contained in:
Student 2026-01-09 09:36:55 +08:00
parent d8122fe3d4
commit 5db1a62e0f
4 changed files with 408 additions and 4 deletions

View File

@ -58,6 +58,18 @@
<button id="reset-button" class="btn btn-secondary">重新开始</button>
<button id="next-scene-button" class="btn btn-success" disabled>下一个场景</button>
</div>
<!-- 实时对话功能区域 -->
<div class="real-time-chat" id="real-time-chat">
<h3>实时英语对话</h3>
<div id="chat-history" class="chat-history">
<!-- 实时对话历史 -->
</div>
<div class="chat-input">
<input type="text" id="chat-message" placeholder="输入英文句子进行实时对话..." />
<button id="send-button" class="btn btn-primary">发送</button>
</div>
</div>
</div>
</div>

343
script.js
View File

@ -28,7 +28,11 @@ function getDomElements() {
feedbackText: document.getElementById('feedback-text'),
startButton: document.getElementById('start-button'),
resetButton: document.getElementById('reset-button'),
nextSceneButton: document.getElementById('next-scene-button')
nextSceneButton: document.getElementById('next-scene-button'),
// 实时对话相关元素
chatHistory: document.getElementById('chat-history'),
chatMessage: document.getElementById('chat-message'),
sendButton: document.getElementById('send-button')
};
}
@ -174,8 +178,14 @@ function showNextDialog() {
const dialog = conversation[gameState.currentDialogIndex];
if (dialog.type === 'user_question') {
// 显示选择题
showQuestion(dialog);
// 区分选择题和自由回答题
if (dialog.options) {
// 显示选择题
showQuestion(dialog);
} else {
// 使用API调用进行实时对话自由回答题
showDynamicQuestion(dialog);
}
} else {
// 显示普通对话
addMessage(dialog);
@ -243,6 +253,48 @@ function showQuestion(dialog) {
elements.feedback.style.display = 'none';
}
// 显示动态输入问题使用API
function showDynamicQuestion(dialog) {
elements.questionText.textContent = dialog.question;
// 创建动态输入界面
const optionsContainer = document.querySelector('.options');
optionsContainer.innerHTML = '';
// 创建文本输入框
const inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.id = 'dynamic-input';
inputElement.placeholder = '请输入您的回答...';
inputElement.className = 'dynamic-input';
// 创建发送按钮
const sendButton = document.createElement('button');
sendButton.textContent = '发送';
sendButton.className = 'btn btn-primary';
sendButton.addEventListener('click', () => {
handleDynamicInput(inputElement.value, dialog);
});
// 添加回车事件监听
inputElement.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleDynamicInput(inputElement.value, dialog);
}
});
// 添加到选项容器
optionsContainer.appendChild(inputElement);
optionsContainer.appendChild(sendButton);
// 显示选择题区域
elements.questionArea.style.display = 'block';
elements.feedback.style.display = 'none';
// 自动聚焦到输入框
inputElement.focus();
}
// 处理选项选择
function handleOptionSelect(selectedOptionIndex) {
const scene = englishData.scenes[gameState.currentSceneIndex];
@ -292,6 +344,172 @@ function handleOptionSelect(selectedOptionIndex) {
}, 3000);
}
// 处理动态输入
async function handleDynamicInput(userInput, dialog) {
if (!userInput.trim()) return;
// 禁用输入和按钮
const inputElement = document.getElementById('dynamic-input');
const sendButton = inputElement.nextElementSibling;
inputElement.disabled = true;
sendButton.disabled = true;
// 添加用户输入到对话
const userMessage = {
speaker: dialog.speaker,
text: userInput,
type: 'user'
};
addMessage(userMessage);
// 将用户输入添加到对话历史数组以便API获取完整上下文
const scene = englishData.scenes[gameState.currentSceneIndex];
scene.conversation.splice(gameState.currentDialogIndex + 1, 0, userMessage);
// 更新进度
gameState.totalQuestions++;
try {
// 获取场景上下文
const scene = englishData.scenes[gameState.currentSceneIndex];
const conversationHistory = scene.conversation.slice(0, gameState.currentDialogIndex + 1);
// 构建API请求消息
const messages = [
{
role: 'system',
content: `You are an English language tutor. Please respond to the user's answer in the context of the daily conversation practice.\n\nScene: ${scene.title}\nDescription: ${scene.description}\n\nCurrent conversation:\n${conversationHistory.map(msg => `${msg.speaker}: ${msg.text}`).join('\n')}\n\nThe user was asked: ${dialog.question}\nThe user answered: ${userInput}\n\nPlease provide: 1) A natural response to continue the conversation, 2) A brief evaluation of the user's answer (whether it's appropriate in this context), and 3) The correct answer if the user's response is not appropriate. Keep your response concise and helpful for language learning.`
},
{
role: 'user',
content: userInput
}
];
// 调用DeepSeek API启用流式响应
const apiKey = 'sk-b5022a18ac184917bc1ea9485c15fda0';
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: messages,
max_tokens: 200,
temperature: 0.7,
stream: true // 启用流式响应
})
});
// 检查响应状态
if (!response.ok) {
if (response.status === 402) {
throw new Error(`API request failed: Insufficient Balance. Please check your DeepSeek API key balance.`);
} else {
throw new Error(`API request failed with status ${response.status}: ${response.statusText}`);
}
}
// 实现流式响应处理
if (!response.body) {
throw new Error('No response body');
}
// 获取可读流
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let aiResponse = '';
// 初始化反馈显示
const feedbackTextElement = elements.feedbackText;
feedbackTextElement.textContent = '';
feedbackTextElement.className = 'api-feedback';
elements.feedback.style.borderColor = '#2196F3';
elements.feedback.style.backgroundColor = '#e3f2fd';
elements.feedback.style.display = 'block';
try {
// 逐块读取响应
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 解码并处理数据
const chunk = decoder.decode(value, { stream: true });
// 分割SSE事件
const lines = chunk.split('\n');
for (const line of lines) {
if (!line.trim() || line.startsWith(':')) {
continue; // 忽略空行和注释
}
// 解析SSE事件数据
if (line.startsWith('data: ')) {
const dataStr = line.slice(6).trim();
if (dataStr === '[DONE]') {
break;
}
try {
const data = JSON.parse(dataStr);
// 提取AI生成的内容
if (data.choices && data.choices[0] && data.choices[0].delta) {
const delta = data.choices[0].delta;
if (delta.content) {
aiResponse += delta.content;
// 实时更新UI
feedbackTextElement.textContent = aiResponse;
}
}
} catch (e) {
console.error('Error parsing JSON:', e);
}
}
}
}
// 流式响应结束,增加分数
if (aiResponse) {
gameState.score += 8;
gameState.correctAnswers++;
updateProgress();
} else {
throw new Error('No response from API');
}
} finally {
reader.releaseLock();
}
} catch (error) {
console.error('Error handling dynamic input:', error);
// 显示错误信息
const feedbackTextElement = elements.feedbackText;
feedbackTextElement.textContent = `Error: ${error.message}`;
feedbackTextElement.className = 'error';
elements.feedback.style.borderColor = '#f44336';
elements.feedback.style.backgroundColor = '#ffebee';
elements.feedback.style.display = 'block';
} finally {
// 延迟后显示下一个对话
setTimeout(() => {
gameState.currentDialogIndex++;
elements.questionArea.style.display = 'none';
elements.feedback.style.display = 'none';
showNextDialog();
}, 5000);
}
}
// 添加用户选择到对话
function addUserChoiceToConversation(dialog, selectedIndex, isCorrect) {
const userMessage = {
@ -381,6 +599,125 @@ function setupEventListeners() {
gameState.currentSceneIndex++;
loadScene(gameState.currentSceneIndex);
});
// 实时对话事件监听
elements.sendButton.addEventListener('click', sendChatMessage);
elements.chatMessage.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendChatMessage();
}
});
}
// 加载环境变量
function loadEnv() {
// 简单的.env文件解析
return new Promise((resolve) => {
fetch('.env')
.then(response => response.text())
.then(text => {
const envVars = {};
const lines = text.split('\n');
lines.forEach(line => {
const [key, value] = line.split('=');
if (key && value) {
envVars[key.trim()] = value.trim();
}
});
resolve(envVars);
})
.catch(() => {
// 如果.env文件不存在返回空对象
resolve({});
});
});
}
// 添加消息到聊天历史
function addChatMessage(text, isUser = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'other'}`;
const speakerSpan = document.createElement('span');
speakerSpan.className = 'speaker';
speakerSpan.textContent = isUser ? '我:' : 'AI:';
const textSpan = document.createElement('span');
textSpan.className = 'text';
textSpan.textContent = text;
messageDiv.appendChild(speakerSpan);
messageDiv.appendChild(textSpan);
elements.chatHistory.appendChild(messageDiv);
// 滚动到底部
elements.chatHistory.scrollTop = elements.chatHistory.scrollHeight;
}
// 发送聊天消息
async function sendChatMessage() {
const message = elements.chatMessage.value.trim();
if (!message) return;
// 显示用户消息
addChatMessage(message, true);
elements.chatMessage.value = '';
elements.sendButton.disabled = true;
try {
// 直接使用API密钥避免跨域问题
const apiKey = 'sk-b5022a18ac184917bc1ea9485c15fda0';
// 调用DeepSeek API
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: 'You are an English language tutor. Please respond to the user in English, and keep the conversation natural and helpful for English learning.'
},
{
role: 'user',
content: message
}
],
max_tokens: 100,
temperature: 0.7
})
});
// 检查响应状态
if (!response.ok) {
if (response.status === 402) {
throw new Error(`API request failed: Insufficient Balance. Please check your DeepSeek API key balance.`);
} else {
throw new Error(`API request failed with status ${response.status}: ${response.statusText}`);
}
}
const data = await response.json();
console.log('API response:', data);
if (data.choices && data.choices[0]) {
const aiResponse = data.choices[0].message.content;
addChatMessage(aiResponse, false);
} else {
addChatMessage('Sorry, I couldn\'t generate a response. Please try again.', false);
}
} catch (error) {
console.error('Error sending message:', error);
// 显示更详细的错误信息给用户
addChatMessage(`Error: ${error.message}. Please check the browser console for details.`, false);
} finally {
elements.sendButton.disabled = false;
}
}
// 页面加载完成后初始化游戏

View File

@ -289,6 +289,53 @@ body {
}
/* 响应式设计 */
/* 实时对话功能样式 */
.real-time-chat {
margin-top: 30px;
padding-top: 20px;
border-top: 2px solid #e8f0fe;
}
.real-time-chat h3 {
color: #4a90e2;
margin-bottom: 15px;
font-size: 1.5em;
}
.chat-history {
max-height: 300px;
overflow-y: auto;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
margin-bottom: 15px;
}
.chat-input {
display: flex;
gap: 10px;
}
#chat-message {
flex: 1;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: all 0.3s ease;
}
#chat-message:focus {
outline: none;
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}
.chat-input .btn {
min-width: 100px;
}
@media (max-width: 768px) {
.container {
padding: 10px;
@ -321,4 +368,12 @@ body {
width: 100%;
max-width: 300px;
}
.chat-input {
flex-direction: column;
}
.chat-input .btn {
min-width: auto;
}
}