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
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>
|