Files
YG_FT_Platform/streaming-test.html

396 lines
12 KiB
HTML
Raw Normal View History

<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background: white;
border-radius: 16px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
max-width: 800px;
width: 100%;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5rem;
font-weight: 700;
}
.controls {
display: flex;
gap: 15px;
margin-bottom: 30px;
justify-content: center;
flex-wrap: wrap;
}
button {
background: #667eea;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
button:hover {
background: #5a67d8;
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
button:disabled {
background: #a0aec0;
cursor: not-allowed;
transform: none;
}
.stream-output {
background: #f7fafc;
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 24px;
min-height: 400px;
max-height: 600px;
overflow-y: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 16px;
line-height: 1.8;
color: #2d3748;
white-space: pre-wrap;
word-break: break-word;
}
.stream-output::-webkit-scrollbar {
width: 8px;
}
.stream-output::-webkit-scrollbar-track {
background: #edf2f7;
border-radius: 4px;
}
.stream-output::-webkit-scrollbar-thumb {
background: #cbd5e0;
border-radius: 4px;
}
.stream-output::-webkit-scrollbar-thumb:hover {
background: #a0aec0;
}
.stats {
margin-top: 20px;
padding: 15px;
background: #edf2f7;
border-radius: 8px;
font-size: 14px;
color: #4a5568;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.stats-item {
display: flex;
align-items: center;
gap: 5px;
}
.loading-dots {
display: inline-block;
}
.loading-dots::after {
content: '.';
animation: dots 1s steps(5, end) infinite;
}
@keyframes dots {
0%, 20% { content: '.'; }
40% { content: '..'; }
60% { content: '...'; }
80%, 100% { content: '.'; }
}
</style>
</head>
<body>
<div class="container">
<h1>✨ 流式输出测试</h1>
<div class="controls">
<button id="startBtn">开始流式输出</button>
<button id="stopBtn" disabled>停止输出</button>
<button id="clearBtn">清空内容</button>
</div>
<div id="output" class="stream-output">
<div style="color: #718096;">点击上方按钮开始测试流式输出...</div>
</div>
<div class="stats">
<div class="stats-item">
<span>状态:</span>
<span id="status" style="font-weight: 600;">就绪</span>
</div>
<div class="stats-item">
<span>字符数:</span>
<span id="charCount">0</span>
</div>
<div class="stats-item">
<span>耗时:</span>
<span id="timeElapsed">0.00s</span>
</div>
</div>
</div>
<script>
// 页面元素
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const clearBtn = document.getElementById('clearBtn');
const output = document.getElementById('output');
const statusEl = document.getElementById('status');
const charCountEl = document.getElementById('charCount');
const timeElapsedEl = document.getElementById('timeElapsed');
// 状态变量
let controller = null;
let startTime = 0;
let charCount = 0;
let timer = null;
// 模拟大模型输出的文本内容
const sampleText = `欢迎使用流式输出测试页面!
这是一个模拟大语言模型生成内容的演示。
流式输出是一种将生成的内容逐字逐句发送给用户的技术,
它可以显著提升用户体验,让用户感觉内容是实时生成的。
在实际应用中大语言模型会在生成每个token后立即发送给客户端
而不需要等待整个响应完成。
这种技术特别适合:
- 长文本生成
- 对话系统
- 代码生成
- 实时翻译
流式输出的优势包括:
1. 更快的感知响应速度
2. 更好的用户体验
3. 更低的内存占用
4. 支持更长的内容生成
在这个测试中我们将模拟每秒生成约50个字符的速度
您可以看到文本是如何逐字逐句显示的。
您可以随时点击"停止输出"按钮来中断流式传输,
也可以点击"清空内容"按钮重新开始。
让我们继续探索流式输出的更多应用场景...
在Web应用中流式输出通常使用以下技术实现
- Server-Sent Events (SSE)
- WebSockets
- HTTP/2 Server Push
每种技术都有其优缺点,选择合适的技术取决于具体的应用场景。
例如SSE适合单向的服务器到客户端通信
而WebSockets适合双向通信。
流式输出不仅提升了用户体验,
也为开发者提供了更多的灵活性和控制能力。
感谢您使用这个测试页面!
希望您对流式输出技术有了更深入的了解。
如果您有任何问题或建议,欢迎随时提出。
祝您使用愉快!`;
// 更新统计信息
function updateStats() {
charCountEl.textContent = charCount;
if (startTime > 0) {
const elapsed = (Date.now() - startTime) / 1000;
timeElapsedEl.textContent = elapsed.toFixed(2) + 's';
}
}
// 清空输出
function clearOutput() {
output.innerHTML = '<div style="color: #718096;">点击上方按钮开始测试流式输出...</div>';
charCount = 0;
updateStats();
statusEl.textContent = '就绪';
}
// 模拟流式输出
async function simulateStreaming() {
startBtn.disabled = true;
stopBtn.disabled = false;
clearOutput();
output.textContent = '';
statusEl.innerHTML = '生成中 <span class="loading-dots"></span>';
startTime = Date.now();
charCount = 0;
// 创建AbortController用于取消操作
controller = new AbortController();
const signal = controller.signal;
try {
// 使用ReadableStream模拟流式输出
const stream = new ReadableStream({
async start(controller) {
let index = 0;
while (index < sampleText.length && !signal.aborted) {
// 随机延迟,模拟真实生成速度
const delay = Math.random() * 80 + 40; // 40-120ms
await new Promise(resolve => setTimeout(resolve, delay));
// 每次发送1-5个字符
const chunkSize = Math.floor(Math.random() * 5) + 1;
const chunk = sampleText.slice(index, index + chunkSize);
controller.enqueue(new TextEncoder().encode(chunk));
index += chunkSize;
// 更新统计信息
charCount += chunk.length;
updateStats();
}
controller.close();
},
cancel() {
console.log('流式输出已取消');
}
});
// 读取并显示流内容
const reader = stream.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 追加内容到输出
output.textContent += decoder.decode(value, { stream: true });
// 自动滚动到底部
output.scrollTop = output.scrollHeight;
}
// 完成
statusEl.textContent = '完成';
output.style.borderColor = '#48bb78';
// 添加完成标记
const doneMarker = document.createElement('div');
doneMarker.style.cssText = `
margin-top: 20px;
padding: 10px;
background: #48bb78;
color: white;
border-radius: 6px;
text-align: center;
font-weight: 600;
`;
doneMarker.textContent = '✅ 流式输出完成!';
output.appendChild(doneMarker);
} catch (error) {
if (error.name === 'AbortError') {
statusEl.textContent = '已停止';
output.style.borderColor = '#ed8936';
const stopMarker = document.createElement('div');
stopMarker.style.cssText = `
margin-top: 20px;
padding: 10px;
background: #ed8936;
color: white;
border-radius: 6px;
text-align: center;
font-weight: 600;
`;
stopMarker.textContent = '⏸️ 流式输出已停止';
output.appendChild(stopMarker);
} else {
statusEl.textContent = '错误';
output.innerHTML += `\n\n❌ 发生错误: ${error.message}`;
output.style.borderColor = '#f56565';
}
} finally {
// 重置状态
startBtn.disabled = false;
stopBtn.disabled = true;
controller = null;
if (timer) {
clearInterval(timer);
timer = null;
}
}
}
// 停止流式输出
function stopStreaming() {
if (controller) {
controller.abort();
controller = null;
}
}
// 事件监听器
startBtn.addEventListener('click', simulateStreaming);
stopBtn.addEventListener('click', stopStreaming);
clearBtn.addEventListener('click', clearOutput);
// 页面加载完成
window.addEventListener('load', () => {
console.log('流式输出测试页面已加载');
});
</script>
</body>
</html>