You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
9.2 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能客服 - 文本 + 语音</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 30px auto;
padding: 20px;
}
.container {
border: 1px solid #ddd;
border-radius: 8px;
padding: 30px;
}
h1 {
color: #333;
margin-bottom: 30px;
}
.input-group {
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 100px;
padding: 12px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
box-sizing: border-box;
}
button {
padding: 12px 30px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.output-section {
margin-top: 30px;
border-top: 1px solid #eee;
padding-top: 20px;
}
.output-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.output-text {
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
min-height: 100px;
font-size: 16px;
line-height: 1.8;
color: #333;
white-space: pre-wrap;
}
.status {
margin-top: 20px;
padding: 15px;
background-color: #d1ecf1;
border-radius: 4px;
color: #0c5460;
font-size: 16px;
}
audio {
margin-top: 20px;
width: 100%;
}
.example-btn {
margin-left: 10px;
background-color: #28a745;
}
.example-btn:hover {
background-color: #218838;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 智能客服 - 文本 + 语音</h1>
<div class="input-group">
<label style="display: block; margin-bottom: 10px; font-size: 16px;">请输入您的问题:</label>
<textarea id="questionInput" placeholder="请输入您的问题,例如:什么是半仓?"></textarea>
</div>
<div class="input-group">
<button id="generateBtn" onclick="askQuestion()">提问</button>
<button class="example-btn" onclick="fillExample()">示例问题</button>
</div>
<div class="status" id="status">
状态:等待提问
</div>
<div class="output-section">
<div class="output-title">📝 回答内容</div>
<div class="output-text" id="outputText">
(回答将在这里显示)
</div>
</div>
<audio id="audioPlayer" controls></audio>
</div>
<script>
const exampleQuestion = "什么是半仓?";
function fillExample() {
document.getElementById('questionInput').value = exampleQuestion;
}
async function askQuestion() {
const question = document.getElementById('questionInput').value.trim();
const statusDiv = document.getElementById('status');
const outputText = document.getElementById('outputText');
const audioPlayer = document.getElementById('audioPlayer');
const generateBtn = document.getElementById('generateBtn');
if (!question) {
alert('请输入您的问题!');
return;
}
generateBtn.disabled = true;
outputText.textContent = '';
statusDiv.textContent = '状态:正在获取回答...';
console.log('开始提问:', question);
let fullText = '';
let eventSource = null;
try {
eventSource = new EventSource(`/customer/service2?question=${encodeURIComponent(question)}`);
eventSource.onmessage = (event) => {
console.log('收到原始数据:', event.data);
const dataStr = event.data;
if (!dataStr || dataStr.trim() === '') {
console.log('跳过空数据');
return;
}
try {
const data = JSON.parse(dataStr);
const text = data.text;
if (text) {
fullText += text;
outputText.textContent += text;
outputText.scrollTop = outputText.scrollHeight;
statusDiv.textContent = `状态:正在获取回答... (已收到 ${fullText.length} 字)`;
console.log('解析到文本:', text);
}
} catch (parseError) {
console.error('JSON解析失败:', parseError, '原始数据:', dataStr);
}
};
eventSource.onerror = (error) => {
console.log('EventSource 连接关闭:', error);
eventSource.close();
if (fullText.length > 0) {
statusDiv.textContent = '状态:回答获取完成,正在生成语音...';
console.log('完整回答:', fullText);
generateAndPlayAudio(fullText);
} else {
statusDiv.textContent = '状态:未获取到回答';
generateBtn.disabled = false;
}
};
} catch (error) {
console.error('获取回答错误:', error);
statusDiv.textContent = `状态:获取回答失败 - ${error.message}`;
generateBtn.disabled = false;
}
}
async function generateAndPlayAudio(text) {
const statusDiv = document.getElementById('status');
const audioPlayer = document.getElementById('audioPlayer');
const generateBtn = document.getElementById('generateBtn');
statusDiv.textContent = '状态:正在生成语音...';
console.log('开始语音合成,文本长度:', text.length);
try {
const url = `/customer/synthesize?text=${encodeURIComponent(text)}`;
audioPlayer.src = url;
audioPlayer.onloadstart = () => {
statusDiv.textContent = '状态:开始接收音频流...';
console.log('开始接收音频流');
};
audioPlayer.onloadeddata = () => {
statusDiv.textContent = '状态:数据加载中,准备播放...';
console.log('音频数据加载完成,可以开始播放');
};
audioPlayer.onplaying = () => {
statusDiv.textContent = '状态:正在播放(流式)';
console.log('开始播放音频');
};
audioPlayer.onwaiting = () => {
statusDiv.textContent = '状态:缓冲中...';
};
audioPlayer.oncanplay = () => {
statusDiv.textContent = '状态:正在播放(流畅)';
};
audioPlayer.onended = () => {
statusDiv.textContent = '状态:播放完成';
generateBtn.disabled = false;
console.log('播放完成');
};
audioPlayer.onerror = (e) => {
console.error('音频播放错误:', e);
statusDiv.textContent = `状态:音频播放失败 - ${audioPlayer.error ? audioPlayer.error.message : '未知错误'}`;
generateBtn.disabled = false;
};
audioPlayer.play();
console.log('已调用 audio.play()');
} catch (error) {
console.error('语音合成错误:', error);
statusDiv.textContent = `状态:出错了 - ${error.message}`;
generateBtn.disabled = false;
}
}
window.onload = () => {
console.log('页面加载完成');
};
</script>
</body>
</html>