Files
YG_FT_Platform/streaming-test.html

396 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>