<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大模型对话 - 流式响应示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; }
body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; display: flex; justify-content: center; align-items: center; }
.chat-app { width: 100%; max-width: 900px; height: 90vh; background: rgba(255, 255, 255, 0.95); border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column; }
.header { background: linear-gradient(90deg, #4f46e5, #7c3aed); color: white; padding: 20px 30px; text-align: center; border-bottom: 1px solid rgba(255, 255, 255, 0.2); }
.header h1 { font-size: 24px; font-weight: 600; margin-bottom: 5px; display: flex; align-items: center; justify-content: center; gap: 10px; }
.header h1::before { content: "🤖"; font-size: 28px; }
.subtitle { font-size: 14px; opacity: 0.9; margin-top: 5px; }
.chat-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.messages-container { flex: 1; overflow-y: auto; padding: 25px; display: flex; flex-direction: column; gap: 20px; }
.message { display: flex; max-width: 80%; animation: fadeIn 0.3s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.message.user { align-self: flex-end; flex-direction: row-reverse; }
.avatar { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; flex-shrink: 0; margin: 0 12px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }
.user .avatar { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
.assistant .avatar { background: linear-gradient(135deg, #10b981 0%, #3b82f6 100%); color: white; }
.message-content { padding: 15px 20px; border-radius: 18px; line-height: 1.5; font-size: 15px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); position: relative; overflow-wrap: break-word; word-break: break-word; }
.user .message-content { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-bottom-right-radius: 5px; }
.assistant .message-content { background: #f8fafc; color: #1e293b; border-bottom-left-radius: 5px; border: 1px solid #e2e8f0; }
.streaming .message-content { min-height: 24px; }
.cursor { display: inline-block; width: 8px; height: 20px; background-color: #3b82f6; vertical-align: middle; margin-left: 2px; animation: blink 1s infinite; }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.input-area { padding: 20px 30px; border-top: 1px solid #e2e8f0; background: #f8fafc; display: flex; gap: 12px; }
.input-area input { flex: 1; padding: 15px 20px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 15px; outline: none; transition: all 0.3s; background: white; }
.input-area input:focus { border-color: #8b5cf6; box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1); }
.input-area button { padding: 15px 25px; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; transition: all 0.3s; font-size: 15px; display: flex; align-items: center; justify-content: center; gap: 8px; }
.send-btn { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); color: white; min-width: 100px; }
.send-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(124, 58, 237, 0.3); }
.send-btn:disabled { background: #cbd5e1; transform: none; box-shadow: none; cursor: not-allowed; }
.stop-btn { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; min-width: 120px; }
.stop-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(239, 68, 68, 0.3); }
.status-bar { padding: 12px 30px; background: #f1f5f9; border-top: 1px solid #e2e8f0; font-size: 14px; color: #64748b; display: flex; justify-content: space-between; }
.status-indicator { display: flex; align-items: center; gap: 8px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; background: #10b981; animation: pulse 2s infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.status-dot.inactive { background: #94a3b8; animation: none; }
.typing-indicator { display: flex; align-items: center; gap: 4px; margin-top: 8px; }
.typing-dot { width: 8px; height: 8px; border-radius: 50%; background: #94a3b8; animation: typing 1.4s infinite ease-in-out; }
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes typing { 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; } 40% { transform: scale(1); opacity: 1; } }
/* 滚动条样式 */
.messages-container::-webkit-scrollbar { width: 8px; }
.messages-container::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 4px; }
.messages-container::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
.messages-container::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
/* 响应式设计 */
@media(max-width: 768px) {
.chat-app { height: 95vh; border-radius: 15px; }
.header { padding: 15px 20px; }
.messages-container { padding: 15px; }
.message { max-width: 90%; }
.input-area { padding: 15px; flex-wrap: wrap; }
.input-area button { padding: 12px 15px; flex: 1; }
.status-bar { padding: 10px 15px; font-size: 13px; }
}
.info-box { background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 12px; padding: 15px; margin: 15px 30px; font-size: 14px; color: #0369a1; line-height: 1.5; }
.info-box strong { color: #075985; }
</style>
</head>
<body>
<div id="app" class="chat-app">
<div class="header">
<h1>AI 对话助手 - 流式响应演示</h1>
<div class="subtitle">体验大模型逐词生成的流式响应效果,模拟真实对话场景</div>
</div>
<div class="info-box">
<strong>✨ 演示说明:</strong> 这是一个模拟大模型流式响应的前端示例。AI 的回答会逐词显示,模拟真实的流式响应效果。点击'发送'开始对话,在 AI 回复过程中可以点击'停止生成'中断回复。
</div>
<div class="chat-container">
<div class="messages-container" ref="messagesContainer">
<div v-for="(message, index) in messages" :key="index" :class="['message', message.role]">
<div class="avatar"> {{ message.role === 'user' ? '您' : 'AI' }} </div>
<div class="message-content"> {{ message.content }} </div>
</div>
<div v-if="isStreaming" class="message assistant streaming">
<div class="avatar"> AI </div>
<div class="message-content"> {{ streamingText }}<span class="cursor"></span></div>
</div>
<div v-if="isWaiting" class="message assistant">
<div class="avatar"> AI </div>
<div class="message-content">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
</div>
</div>
<div class="input-area">
<input v-model="userInput" @keyup.enter="sendMessage" placeholder="请输入您的问题,例如:解释一下什么是流式响应?" :disabled="isStreaming || isWaiting"/>
<button class="send-btn" @click="sendMessage" :disabled="!userInput.trim() || isStreaming || isWaiting">
<span v-if="!isWaiting">发送</span><span v-else>等待中...</span>
</button>
<button v-if="isStreaming" class="stop-btn" @click="stopStreaming"> 停止生成 </button>
</div>
<div class="status-bar">
<div class="status-indicator">
<div :class="['status-dot', isStreaming ? '' : 'inactive']"></div>
<span v-if="isStreaming">AI 正在思考中...</span><span v-else>AI 就绪</span>
</div>
<div> 已发送 {{ messages.filter(m => m.role === 'user').length }} 条消息 </div>
</div>
</div>
</div>
<script>
const { createApp, ref, onMounted, onUpdated, watch } = Vue;
createApp({
setup() {
const messages = ref([
{ role: 'assistant', content: '您好!我是 AI 助手,支持流式响应对话。您可以问我任何问题,我会逐词生成回答,模拟真实的大模型响应过程。' },
{ role: 'user', content: '请解释一下什么是流式响应?' },
{ role: 'assistant', content: '流式响应是一种实时数据传输方式,在大模型对话中,服务器将生成的内容分成多个小块逐步发送到前端,而不是一次性返回完整响应。' }
]);
const userInput = ref('');
const isStreaming = ref(false);
const isWaiting = ref(false);
const streamingText = ref('');
const messagesContainer = ref(null);
const aiResponses = {
'解释一下什么是流式响应?': '流式响应是一种实时数据传输方式,在大模型对话中,服务器将生成的内容分成多个小块逐步发送到前端,而不是一次性返回完整响应。这种方式可以:\n\n1. 降低用户感知延迟\n2. 提供更自然的交互体验\n3. 允许用户在中途停止生成\n4. 减少服务器内存压力\n\n前端通过接收这些数据流,逐词或逐段展示给用户,模拟'打字机'效果。',
'流式响应有什么优势?': '流式响应具有以下主要优势:\n\n• 实时性:用户立即看到部分结果,无需等待完整响应\n• 交互性:提供更接近真人对话的体验\n• 可中断性:用户可以在生成过程中停止\n• 资源友好:逐步处理数据,减少前端和后端的内存压力\n• 错误恢复:部分失败不影响整体体验',
'前端如何实现流式响应?': '前端可以通过多种技术实现流式响应:\n\n1. Server-Sent Events (SSE):基于 HTTP 的单向通信,简单易用\n2. WebSockets:全双工通信,适合复杂交互\n3. Fetch API with Streaming:使用 ReadableStream 逐块读取数据\n4. GraphQL Subscriptions:适合 GraphQL 后端\n\n每种方案都有适用场景,SSE 是最常用的大模型对话方案。',
: ,
: 停止生成,
:
};
defaultResponses = [
,
,
,
,
];
= () => {
( [key, response] .(aiResponses)) {
(question.(key.(, ).(, ))) {
response;
}
}
randomIndex = .(.() * defaultResponses.);
defaultResponses[randomIndex];
};
= () => {
isStreaming. = ;
streamingText. = ;
chars = fullResponse.();
index = ;
streamInterval = ( {
(index < chars.) {
chunkSize = .(.() * ) + ;
chunk = chars.(index, index + chunkSize).();
streamingText. += chunk;
index += chunkSize;
();
} {
(streamInterval);
messages..({ : , : streamingText. });
isStreaming. = ;
streamingText. = ;
isWaiting. = ;
}
}, .(.() * ) + );
streamInterval;
};
currentStreamInterval = ;
= () => {
input = userInput..();
(!input || isStreaming. || isWaiting.) ;
messages..({ : , : input });
userInput. = ;
isWaiting. = ;
();
( {
isWaiting. = ;
aiResponse = (input);
currentStreamInterval = (aiResponse);
}, .(.() * ) + );
};
= () => {
(currentStreamInterval) {
(currentStreamInterval);
currentStreamInterval = ;
}
(streamingText..()) {
messages..({ : , : streamingText. + });
}
isStreaming. = ;
streamingText. = ;
isWaiting. = ;
};
= () => {
.( {
(messagesContainer.) {
messagesContainer.. = messagesContainer..;
}
});
};
( {
();
});
( {
();
});
(messages, {
();
}, { : });
exampleQuestions = [
,
,
,
,
,
];
= () => {
userInput. = question;
};
{
messages,
userInput,
isStreaming,
isWaiting,
streamingText,
messagesContainer,
sendMessage,
stopStreaming,
exampleQuestions,
useExampleQuestion
};
}
}).();
</script>
</body>
</html>