Files
2025-01-12 05:13:22 +00:00

593 lines
22 KiB
JavaScript

// chat.js
// 在文件开头添加这个函数
function i18n(key) {
return lang[selectedLang][key] || key;
}
async function showChat() {
try {
const userInfo = await fetchUserInfo();
if (!userInfo) {
alert(i18n('pleaseLogin'));
window.location.href = 'login.html'; // 重定向到登录页面
return;
}
// 检查邮箱验证状态
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
console.log('Email Verified:', emailVerified);
if (!emailVerified) {
alert(i18n('pleaseVerifyEmail'));
showUserCenterSection('profile'); // 跳转到个人资料页面
return;
}
// 检查是否已经设置了 API 密钥
if (!window.chatAssistant.API_KEY) {
const apiKey = prompt(i18n('pleaseEnterApiKey'));
if (!apiKey) {
alert(i18n('apiKeyRequired'));
return;
}
window.chatAssistant.setApiKey(apiKey);
}
const chatContent = document.getElementById('chatContent');
const dashboardContent = document.getElementById('dashboardContent');
const usersContent = document.getElementById('usersContent');
const pageTitle = document.getElementById('pageTitle');
const featureContent = document.getElementById('featureContent');
const userCenterContent = document.getElementById('userCenterContent');
const showApiDocs = document.getElementById('showApiDocs');
const apiDocsContent = document.getElementById('apiDocsContent');
if (apiDocsContent) apiDocsContent.style.display = 'none';
if (featureContent) featureContent.style.display = 'none';
if (chatContent) chatContent.style.display = 'flex';
if (dashboardContent) dashboardContent.style.display = 'none';
if (usersContent) usersContent.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
if (pageTitle) pageTitle.innerText = i18n('chatAssistant');
if (showApiDocs) showApiDocs.style.display = 'none';
if (window.chatAssistant) {
// window.chatAssistant.setApiKey(apiKey);
window.chatAssistant.showWelcomeMessage();
}
} catch (error) {
console.error('Error in showChat:', error);
alert(i18n('errorGettingUserInfo'));
}
}
class ChatAssistant {
constructor() {
this.API_BASE_URL = 'https://dev.obscura.work/v1_chat';
this.isRecording = false;
this.API_KEY = null;
this.audioChunks = [];
this.chatHistory = [];
this.sessionId = this.generateUUID();
this.onStatusUpdate = null;
this.onMessageReceived = null;
this.currentVoice ='default';
document.addEventListener('DOMContentLoaded', () => {
this.initializeEventListeners();
});
}
setApiKey(apiKey) {
this.API_KEY = apiKey;
}
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
setVoice(voice) {
this.currentVoice = voice;
}
initializeEventListeners() {
const micButton = document.getElementById('micButton');
const sendButton = document.getElementById('sendButton');
const textInput = document.getElementById('textInput');
const voiceSelect = document.getElementById('voiceSelect');
if (voiceSelect) {
voiceSelect.addEventListener('change', (e) => {
this.setVoice(e.target.value);
});
}
if (micButton) micButton.onclick = () => this.toggleMic();
if (sendButton) sendButton.onclick = () => this.sendTextMessage();
if (textInput) {
textInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.sendTextMessage();
}
});
}
}
addMessage(sender, text) {
const conversationLog = document.getElementById('conversationLog');
if (conversationLog) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', sender === 'user' ? 'user-message' : 'assistant-message');
messageDiv.textContent = text;
conversationLog.appendChild(messageDiv);
conversationLog.scrollTop = conversationLog.scrollHeight;
return messageDiv;
} else {
console.error("conversationLog element not found");
return null;
}
}
async initializeChat() {
if (!this.API_KEY) {
throw new Error(i18n('apiKeyNotSet'));
}
try {
this.updateStatus(i18n('initializingChat'));
const response = await fetch(`${this.API_BASE_URL}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.API_KEY}`
},
body: JSON.stringify({
session_id: this.sessionId,
query: i18n('startConversation')
})
});
if (!response.ok) {
const errorData = await response.json();
console.error('Server error:', errorData);
throw new Error(`HTTP error! status: ${response.status}, message: ${JSON.stringify(errorData)}`);
}
const data = await response.json();
console.log("初始化响应:", data);
this.updateStatus(i18n('chatInitialized'));
} catch (error) {
console.error('Error initializing chat:', error);
this.updateStatus(i18n('errorInitializingChat') + error.message);
}
}
toggleMic() {
if (!this.isRecording) {
this.startRecording();
document.getElementById('micButton').classList.add('recording');
} else {
this.stopRecording();
document.getElementById('micButton').classList.remove('recording');
}
}
async startRecording() {
this.audioChunks = [];
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.ondataavailable = event => {
this.audioChunks.push(event.data);
};
this.mediaRecorder.start();
this.isRecording = true;
this.updateStatus(i18n('recordingInProgress'));
document.getElementById('micButton').classList.add('recording');
} catch (error) {
console.error('Error accessing microphone:', error);
this.updateStatus(i18n('errorAccessingMicrophone'));
}
}
stopRecording() {
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop();
this.isRecording = false;
this.updateStatus(i18n('recordingStopped'));
document.getElementById('micButton').classList.remove('recording');
this.mediaRecorder.onstop = () => {
const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' });
this.uploadRecording(audioBlob);
};
}
}
uploadRecording(audioBlob) {
const formData = new FormData();
formData.append('audio', audioBlob, 'recording.wav');
this.uploadAudio(formData);
}
async uploadAudio(formData) {
try {
const response = await fetch(`${this.API_BASE_URL}/asr`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.API_KEY}`
},
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('上传成功:', data);
this.pollAsrResult(data.task_id);
} catch (error) {
console.error('上传失败:', error);
this.updateStatus(i18n('uploadFailed'));
}
}
async pollAsrResult(taskId) {
let attempts = 0;
const maxAttempts = 30; // 最多轮询60秒(30 * 2秒)
const pollInterval = setInterval(async () => {
if (attempts >= maxAttempts) {
clearInterval(pollInterval);
this.updateStatus(i18n('asrProcessingTimeout'));
return;
}
attempts++;
try {
console.log(`Polling ASR result for task: ${taskId}, attempt: ${attempts}`);
const response = await fetch(`${this.API_BASE_URL}/asr_result/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Received ASR poll response:', data);
if (data.status === 'completed') {
clearInterval(pollInterval);
console.log('ASR completed, transcription:', data.transcription);
this.displayTranscription(data.transcription);
this.processTranscription(data.transcription);
} else if (data.status === 'failed') {
clearInterval(pollInterval);
this.updateStatus(i18n('asrProcessingFailed'));
} else if (data.status === 'not_found') {
clearInterval(pollInterval);
this.updateStatus(i18n('asrTaskNotFound'));
} else {
// 其他状态,例如 'queued' 或 'processing'
this.updateStatus(i18n('asrProcessing') + `(${data.status})`);
}
} catch (error) {
console.error('轮询ASR结果时出错:', error);
clearInterval(pollInterval);
this.updateStatus(i18n('getAsrResultError'));
}
}, 2000);
}
displayTranscription(text) {
console.log('Displaying transcription:', text);
this.addMessage('user', text);
this.chatHistory.push(['user', text]);
this.updateStatus(i18n('processingTranscription'));
}
async sendTextMessage() {
const textInput = document.getElementById('textInput');
const message = textInput.value.trim();
if (message) {
console.log("Sending text message:", message);
this.addMessage('user', message);
textInput.value = '';
await this.processTranscription(message, this.currentVoice);
}
}
async processTranscription(text, voice) {
try {
this.updateStatus(i18n('generatingAIResponse'));
const chatResponse = await fetch(`${this.API_BASE_URL}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.API_KEY}`
},
body: JSON.stringify({
session_id: this.sessionId,
query: text,
voice: voice || 'default'
})
});
if (!chatResponse.ok) {
const errorData = await chatResponse.json();
console.error('Server error:', errorData);
throw new Error(`HTTP error! status: ${chatResponse.status}, message: ${JSON.stringify(errorData)}`);
}
const chatData = await chatResponse.json();
this.pollChatResult(chatData.task_id);
} catch (error) {
console.error('Error processing response:', error);
this.updateStatus(i18n('errorProcessingResponse') + error.message);
}
}
async pollChatResult(taskId) {
const pollInterval = setInterval(async () => {
try {
const response = await fetch(`${this.API_BASE_URL}/chat_result/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('收到聊天轮询响应:', data);
if (data.status === 'completed') {
clearInterval(pollInterval);
if (data.result && data.result.response) {
const aiResponse = data.result.response;
const messageElement = this.addMessage('assistant', aiResponse);
this.chatHistory.push(['assistant', aiResponse]);
const sentences = this.splitTextIntoSentences(aiResponse);
this.synthesizeAndPlaySentences([aiResponse], messageElement);
this.updateStatus(i18n('processingComplete'));
} else {
console.error('响应中的结果无效或为空:', data);
this.updateStatus(i18n('invalidChatResponse'));
}
} else if (data.status === 'failed') {
clearInterval(pollInterval);
this.updateStatus(i18n('chatProcessingFailed'));
} else {
this.updateStatus(i18n('chatProcessing') + `(${data.status})`);
}
} catch (error) {
console.error('轮询聊天结果时出错:', error);
clearInterval(pollInterval);
this.updateStatus(i18n('getChatResultError') + error.message);
}
}, 1000); // 每1秒轮询一次
}
splitTextIntoSentences(text) {
return text.split(/(?<=[!?。!?;;])\s*/);
}
async synthesizeAndPlaySentences(sentences, messageElement) {
const statusElement = document.createElement('div');
statusElement.textContent = i18n('generatingSpeech');
statusElement.style.fontStyle = 'italic';
statusElement.style.color = '#666';
statusElement.style.marginTop = '10px';
statusElement.style.fontSize = '0.9em';
messageElement.appendChild(statusElement);
for (let sentence of sentences) {
try {
const requestBody = {
text: sentence,
voice: this.currentVoice
};
console.log('Sending TTS request:', requestBody); // 打印发送的请求体
const response = await fetch(`${this.API_BASE_URL}/tts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.API_KEY}`
},
body: JSON.stringify(requestBody)
});
console.log('TTS response status:', response.status); // 打印响应状态码
if (!response.ok) {
const errorText = await response.text();
console.error('TTS error response:', errorText); // 打印错误响应
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('TTS response data:', data); // 打印响应数据
if (data.status === "completed") {
// 如果已经存在音频文件,直接获取并播放
await this.fetchAndPlayAudio(data.task_id, statusElement);
} else {
// 否则,开始轮询结果
await this.pollTtsResult(data.task_id, statusElement);
}
} catch (error) {
console.error(`Error in speech synthesis:`, error);
statusElement.textContent = i18n('errorProcessingResponse') + error.message;
}
}
}
async pollTtsResult(taskId, statusElement) {
const pollInterval = setInterval(async () => {
try {
const response = await fetch(`${this.API_BASE_URL}/tts_result/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.status === 'completed') {
clearInterval(pollInterval);
await this.fetchAndPlayAudio(taskId, statusElement);
} else if (data.status === 'failed') {
clearInterval(pollInterval);
statusElement.textContent = i18n('ttsProcessingFailed');
} else {
statusElement.textContent = i18n('ttsProcessing') + `(${data.status})`;
}
} catch (error) {
console.error(i18n('ttsPollingError'), error);
clearInterval(pollInterval);
statusElement.textContent = i18n('ttsPollingError');
}
}, 1000); // 每1秒轮询一次
}
async fetchAndPlayAudio(taskId, statusElement) {
try {
const audioResponse = await fetch(`${this.API_BASE_URL}/tts_audio/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!audioResponse.ok) {
throw new Error(`HTTP error! status: ${audioResponse.status}`);
}
const audioBlob = await audioResponse.blob();
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
await audio.play();
statusElement.textContent = i18n('speechGenerationComplete');
console.log('语音生成完成,正在播放音频'); // 添加这行
} catch (error) {
console.error('获取或播放音频时出错:', error);errorFetchingOrPlayingAudio
statusElement.textContent = i18n('errorFetchingOrPlayingAudio') + error.message;
}
}
updateStatus(message) {
if (this.onStatusUpdate) {
this.onStatusUpdate(message);
}
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.textContent = message;
}
}
showWelcomeMessage() {
const welcomeMessage = i18n('welcomeMessage');
this.addMessage('assistant', welcomeMessage);
this.chatHistory.push(['assistant', welcomeMessage]);
}
}
// 创建全局实例
window.chatAssistant = new ChatAssistant();
window.initializeChat = async function() {
console.log("Generated session ID:", window.chatAssistant.sessionId);
try {
window.chatAssistant.onStatusUpdate = (status) => {
const statusElement = document.getElementById('status');
if (statusElement) statusElement.textContent = status;
};
window.chatAssistant.onMessageReceived = (sender, message) => {
console.log(`Received message from ${sender}: ${message}`);
};
await window.chatAssistant.initializeChat();
window.chatAssistant.showWelcomeMessage();
} catch (error) {
console.error("Error initializing ChatAssistant:", error);
}
};
window.activateAssistant = function() {
const dialog = document.getElementById('assistantDialog');
if (dialog) {
dialog.style.display = 'block';
setTimeout(() => {
dialog.style.opacity = '1';
dialog.style.transform = 'translate(0, 0)';
}, 10);
}
};
window.closeAssistantDialog = function() {
const dialog = document.getElementById('assistantDialog');
if (dialog) {
dialog.style.opacity = '0';
dialog.style.transform = 'translate(20px, 20px)';
setTimeout(() => dialog.style.display = 'none', 300);
}
};
window.sendMessage = async function() {
if (window.chatAssistant) {
await window.chatAssistant.sendTextMessage();
}
};
window.toggleMic = function() {
if (window.chatAssistant) {
window.chatAssistant.toggleMic();
}
};
// 获取用户信息的函数
async function fetchUserInfo() {
try {
const token = localStorage.getItem('access_token');
if (!token) {
console.error('No access token found');
return null;
}
const response = await fetch(`${BASE_URL}/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const userInfo = await response.json();
return userInfo;
} catch (error) {
console.error('Error fetching user info:', error);
// 可以在这里处理错误,比如清除 token 并重定向到登录页面
localStorage.removeItem('access_token');
window.location.href = 'login.html'; // 重定向到登录页面
return null;
}
}