469 lines
14 KiB
HTML
469 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CardioAI - AI 语音助手</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
color: white;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 10px;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.header p {
|
|
font-size: 1.1rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.card {
|
|
background: white;
|
|
border-radius: 20px;
|
|
padding: 30px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.chat-container {
|
|
min-height: 400px;
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 15px;
|
|
padding: 20px;
|
|
background: #f8f9fa;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.message {
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.message.user {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.message.assistant {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.message-bubble {
|
|
max-width: 75%;
|
|
padding: 15px 20px;
|
|
border-radius: 18px;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.message.user .message-bubble {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-bottom-right-radius: 5px;
|
|
}
|
|
|
|
.message.assistant .message-bubble {
|
|
background: white;
|
|
color: #333;
|
|
border: 1px solid #e0e0e0;
|
|
border-bottom-left-radius: 5px;
|
|
}
|
|
|
|
.message-avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.2rem;
|
|
margin: 0 10px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.message.user .message-avatar {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
order: 2;
|
|
}
|
|
|
|
.message.assistant .message-avatar {
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
color: white;
|
|
}
|
|
|
|
.audio-player {
|
|
margin-top: 10px;
|
|
width: 100%;
|
|
}
|
|
|
|
.input-container {
|
|
display: flex;
|
|
gap: 15px;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.input-wrapper {
|
|
flex: 1;
|
|
position: relative;
|
|
}
|
|
|
|
#userInput {
|
|
width: 100%;
|
|
padding: 15px 20px;
|
|
border: 2px solid #e0e0e0;
|
|
border-radius: 25px;
|
|
font-size: 1rem;
|
|
resize: none;
|
|
min-height: 50px;
|
|
max-height: 150px;
|
|
font-family: inherit;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
#userInput:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
}
|
|
|
|
.btn-send {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 15px 30px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
border-radius: 25px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.btn-send:hover:not(:disabled) {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
|
}
|
|
|
|
.btn-send:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.loading {
|
|
display: none;
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.loading.active {
|
|
display: block;
|
|
}
|
|
|
|
.spinner {
|
|
border: 4px solid #f3f3f3;
|
|
border-top: 4px solid #667eea;
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 10px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.loading-text {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.error-message {
|
|
background: #ff7675;
|
|
color: white;
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.error-message.active {
|
|
display: block;
|
|
}
|
|
|
|
.welcome-message {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: #666;
|
|
}
|
|
|
|
.welcome-message .icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.welcome-message h3 {
|
|
color: #333;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.suggested-questions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.suggested-question {
|
|
background: #f0f0f0;
|
|
color: #555;
|
|
padding: 8px 15px;
|
|
border-radius: 20px;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.suggested-question:hover {
|
|
background: #e0e0e0;
|
|
color: #333;
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.header h1 {
|
|
font-size: 1.8rem;
|
|
}
|
|
.input-container {
|
|
flex-direction: column;
|
|
}
|
|
.message-bubble {
|
|
max-width: 85%;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🤖 CardioAI 语音助手</h1>
|
|
<p>专业心血管健康顾问 - DeepSeek + CosyVoice</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<!-- 错误消息 -->
|
|
<div class="error-message" id="errorMessage"></div>
|
|
|
|
<!-- 聊天区域 -->
|
|
<div class="chat-container" id="chatContainer">
|
|
<div class="welcome-message" id="welcomeMessage">
|
|
<div class="icon">❤️</div>
|
|
<h3>您好!我是 CardioAI 健康助手</h3>
|
|
<p>我可以回答您关于心血管健康的任何问题</p>
|
|
<div class="suggested-questions">
|
|
<span class="suggested-question" onclick="askQuestion('什么是高血压?')">什么是高血压?</span>
|
|
<span class="suggested-question" onclick="askQuestion('如何预防心血管疾病?')">如何预防心血管疾病?</span>
|
|
<span class="suggested-question" onclick="askQuestion('正常的心率是多少?')">正常的心率是多少?</span>
|
|
<span class="suggested-question" onclick="askQuestion('胆固醇高怎么办?')">胆固醇高怎么办?</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 加载动画 -->
|
|
<div class="loading" id="loading">
|
|
<div class="spinner"></div>
|
|
<p class="loading-text">正在思考并生成语音回答...</p>
|
|
</div>
|
|
|
|
<!-- 输入区域 -->
|
|
<div class="input-container">
|
|
<div class="input-wrapper">
|
|
<textarea id="userInput" placeholder="输入您关于心血管健康的问题..." rows="1"></textarea>
|
|
</div>
|
|
<button class="btn-send" id="sendBtn" onclick="sendMessage()">
|
|
发送
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="text-align: center; color: white; opacity: 0.8; font-size: 0.9rem;">
|
|
<p>⚠️ 本助手提供的健康建议仅供参考,不能替代专业医疗诊断</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const chatContainer = document.getElementById('chatContainer');
|
|
const userInput = document.getElementById('userInput');
|
|
const sendBtn = document.getElementById('sendBtn');
|
|
const loading = document.getElementById('loading');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
const welcomeMessage = document.getElementById('welcomeMessage');
|
|
|
|
// 自动调整文本框高度
|
|
userInput.addEventListener('input', function() {
|
|
this.style.height = 'auto';
|
|
this.style.height = Math.min(this.scrollHeight, 150) + 'px';
|
|
});
|
|
|
|
// 回车发送消息
|
|
userInput.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
sendMessage();
|
|
}
|
|
});
|
|
|
|
function askQuestion(question) {
|
|
userInput.value = question;
|
|
sendMessage();
|
|
}
|
|
|
|
function sendMessage() {
|
|
const question = userInput.value.trim();
|
|
if (!question) return;
|
|
|
|
// 隐藏欢迎消息
|
|
if (welcomeMessage) {
|
|
welcomeMessage.style.display = 'none';
|
|
}
|
|
|
|
// 添加用户消息
|
|
addMessage('user', question);
|
|
userInput.value = '';
|
|
userInput.style.height = '50px';
|
|
|
|
// 显示加载动画
|
|
loading.classList.add('active');
|
|
sendBtn.disabled = true;
|
|
errorMessage.classList.remove('active');
|
|
|
|
// 调用 API
|
|
fetch('/voice_assistant', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ question: question })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
loading.classList.remove('active');
|
|
sendBtn.disabled = false;
|
|
|
|
if (data.error) {
|
|
showError(data.error);
|
|
} else {
|
|
addAssistantMessage(data.text_answer, data.audio_base64);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
loading.classList.remove('active');
|
|
sendBtn.disabled = false;
|
|
showError('网络错误,请确保服务已启动');
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
function addMessage(type, text) {
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.className = `message ${type}`;
|
|
|
|
const avatar = document.createElement('div');
|
|
avatar.className = 'message-avatar';
|
|
avatar.textContent = type === 'user' ? '👤' : '🤖';
|
|
|
|
const bubble = document.createElement('div');
|
|
bubble.className = 'message-bubble';
|
|
bubble.textContent = text;
|
|
|
|
if (type === 'user') {
|
|
messageDiv.appendChild(bubble);
|
|
messageDiv.appendChild(avatar);
|
|
} else {
|
|
messageDiv.appendChild(avatar);
|
|
messageDiv.appendChild(bubble);
|
|
}
|
|
|
|
chatContainer.appendChild(messageDiv);
|
|
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
}
|
|
|
|
function addAssistantMessage(text, audioBase64) {
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.className = 'message assistant';
|
|
|
|
const avatar = document.createElement('div');
|
|
avatar.className = 'message-avatar';
|
|
avatar.textContent = '🤖';
|
|
|
|
const bubble = document.createElement('div');
|
|
bubble.className = 'message-bubble';
|
|
|
|
const textContent = document.createElement('div');
|
|
textContent.textContent = text;
|
|
bubble.appendChild(textContent);
|
|
|
|
// 创建音频播放器
|
|
if (audioBase64) {
|
|
const audio = document.createElement('audio');
|
|
audio.className = 'audio-player';
|
|
audio.controls = true;
|
|
audio.src = `data:audio/mp3;base64,${audioBase64}`;
|
|
audio.autoplay = true;
|
|
bubble.appendChild(audio);
|
|
}
|
|
|
|
messageDiv.appendChild(avatar);
|
|
messageDiv.appendChild(bubble);
|
|
|
|
chatContainer.appendChild(messageDiv);
|
|
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
}
|
|
|
|
function showError(message) {
|
|
errorMessage.textContent = message;
|
|
errorMessage.classList.add('active');
|
|
setTimeout(() => {
|
|
errorMessage.classList.remove('active');
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|