การประมวลผล AI ที่ใช้เวลานาน โดยเฉพาะงานที่ต้องประมวลผลข้อมูลจำนวนมาก เช่น การวิเคราะห์เอกสารยาว การสร้างรายงาน หรือการประมวลผล Batch ต้องมีการแสดง Progress ให้ผู้ใช้เห็นว่าระบบทำงานอยู่ ไม่ใช่ค้างไป ในบทความนี้ผมจะสอนวิธี implement Server-Sent Events (SSE) สำหรับงาน AI ที่ใช้เวลานาน พร้อมโค้ดตัวอย่างที่รันได้จริงและเอาไปใช้งานได้ทันที
ทำไมต้องใช้ SSE สำหรับ AI Tasks?
จากประสบการณ์ที่ผมพัฒนาระบบ AI แบบ Real-time มาหลายโปรเจกต์ พบว่า SSE มีข้อดีหลายอย่าง:
- Real-time update - ส่งข้อมูลความคืบหน้าให้ Frontend ทันทีที่มีการอัพเดท
- Low latency - HolySheep AI รองรับ <50ms latency ทำให้ Progress อัพเดทได้เร็วมาก
- Persistent connection - เชื่อมต่อค้างไว้ได้ตลอดการประมวลผล
- Auto-reconnect - Browser รองรับการเชื่อมต่อใหม่อัตโนมัติเมื่อ Connection หลุด
เปรียบเทียบต้นทุน AI APIs ปี 2026
ก่อนจะเริ่มเขียนโค้ด มาดูต้นทุนของแต่ละ Provider กันก่อน เพื่อให้เห็นภาพว่า SSE ช่วยให้เราประมวลผลได้คุ้มค่าขึ้นอย่างไร:
| Model | Output Price ($/MTok) | 10M Tokens/เดือน | HolySheep ประหยัด |
|---|---|---|---|
| GPT-4.1 | $8.00 | $80.00 | - |
| Claude Sonnet 4.5 | $15.00 | $150.00 | - |
| Gemini 2.5 Flash | $2.50 | $25.00 | - |
| DeepSeek V3.2 | $0.42 | $4.20 | 85%+ |
จะเห็นได้ว่า DeepSeek V3.2 บน HolySheheep AI มีราคาถูกที่สุดในตลาด ที่ $0.42/MTok รวมอัตราแลกเปลี่ยน ¥1=$1 ทำให้ประหยัดได้ถึง 85%+ เมื่อเทียบกับ OpenAI และ Anthropic
Architecture Overview
ระบบ SSE Progress ประกอบด้วย 3 ส่วนหลัก:
- Backend API - รับ Request และส่ง Progress กลับผ่าน SSE
- AI Service - เรียก HolySheep AI API เพื่อประมวลผล
- Frontend Client - แสดง Progress bar และรับ Event จาก Server
โค้ด Backend - Node.js/Express
// server.js
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Cache สำหรับเก็บ Progress ของแต่ละ Task
const taskProgress = new Map();
/**
* SSE Endpoint - ส่ง Progress ให้ Client
* Endpoint: GET /api/ai/progress/:taskId
*/
app.get('/api/ai/progress/:taskId', (req, res) => {
const { taskId } = req.params;
// Set Headers สำหรับ SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('Access-Control-Allow-Origin', '*');
// ส่ง Heartbeat ทุก 15 วินาที เพื่อรักษา Connection
const heartbeat = setInterval(() => {
res.write(event: heartbeat\ndata: ${Date.now()}\n\n);
}, 15000);
// ส่ง Progress ทุกครั้งที่มีการอัพเดท
const checkProgress = setInterval(() => {
const progress = taskProgress.get(taskId);
if (progress) {
res.write(event: progress\ndata: ${JSON.stringify(progress)}\n\n);
}
}, 500); // อัพเดททุก 500ms
// Cleanup เมื่อ Client ปิด Connection
req.on('close', () => {
clearInterval(heartbeat);
clearInterval(checkProgress);
taskProgress.delete(taskId);
});
});
/**
* AI Processing Endpoint
* Endpoint: POST /api/ai/process
*/
app.post('/api/ai/process', async (req, res) => {
const { prompt, model = 'deepseek-v3.2' } = req.body;
const taskId = task_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
// Initialize Progress
taskProgress.set(taskId, {
taskId,
status: 'processing',
progress: 0,
message: 'เริ่มประมวลผล...',
startTime: Date.now()
});
// ส่ง Response กลับทันที (ไม่รอให้ AI ประมวลผลเสร็จ)
res.json({ taskId, message: 'เริ่มประมวลผลแล้ว' });
// เริ่ม Process AI (ทำเป็น async ไม่บล็อก)
processAITask(taskId, prompt, model);
});
/**
* ฟังก์ชันประมวลผล AI Task พร้อม Progress Updates
*/
async function processAITask(taskId, prompt, model) {
try {
// Step 1: ส่ง Prompt ไปยัง HolySheep AI
taskProgress.set(taskId, {
...taskProgress.get(taskId),
progress: 10,
message: 'กำลังส่งคำถามไปยัง AI...'
});
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer YOUR_HOLYSHEEP_API_KEY
},
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: prompt }],
stream: false // ไม่ใช้ streaming เพราะเราจะส่ง SSE เอง
})
});
// Step 2: รับ Response เริ่มต้น
taskProgress.set(taskId, {
...taskProgress.get(taskId),
progress: 50,
message: 'AI กำลังประมวลผล...'
});
if (!response.ok) {
throw new Error(API Error: ${response.status});
}
const data = await response.json();
// Step 3: ประมวลผล Response
taskProgress.set(taskId, {
...taskProgress.get(taskId),
progress: 90,
message: 'กำลังจัดรูปแบบผลลัพธ์...'
});
// Step 4: เสร็จสิ้น
taskProgress.set(taskId, {
taskId,
status: 'completed',
progress: 100,
message: 'ประมวลผลเสร็จสิ้น',
result: data.choices[0].message.content,
tokens: data.usage?.total_tokens || 0,
duration: Date.now() - taskProgress.get(taskId).startTime
});
} catch (error) {
taskProgress.set(taskId, {
taskId,
status: 'error',
progress: 0,
message: เกิดข้อผิดพลาด: ${error.message}
});
}
}
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
โค้ด Frontend - React + SSE Client
// SSEProgressBar.jsx
import React, { useState, useEffect, useRef } from 'react';
export default function SSEProgressBar({ taskId }) {
const [progress, setProgress] = useState({ status: 'idle', progress: 0, message: '' });
const eventSourceRef = useRef(null);
useEffect(() => {
if (!taskId) return;
// เชื่อมต่อ SSE Server
const eventSource = new EventSource(http://localhost:3000/api/ai/progress/${taskId});
eventSourceRef.current = eventSource;
// รับ Progress Updates
eventSource.addEventListener('progress', (event) => {
try {
const data = JSON.parse(event.data);
setProgress(data);
} catch (e) {
console.error('Parse error:', e);
}
});
// Heartbeat handler (ตรวจสอบว่า Connection ยังอยู่)
eventSource.addEventListener('heartbeat', (event) => {
console.log('Heartbeat:', event.data);
});
// Error handler
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
eventSource.close();
// พยายามเชื่อมต่อใหม่หลัง 3 วินาที
setTimeout(() => {
if (progress.status !== 'completed') {
setProgress(prev => ({ ...prev, message: 'กำลังเชื่อมต่อใหม่...' }));
}
}, 3000);
};
// Cleanup เมื่อ Component unmount
return () => {
eventSource.close();
};
}, [taskId]);
// คำนวณเวลาที่ใช้
const elapsedTime = progress.startTime
? Math.round((Date.now() - progress.startTime) / 1000)
: 0;
return (
<div style={styles.container}>
<div style={styles.header}>
<span style={styles.status}>{progress.status === 'completed' ? '✓' : '⏳'}</span>
<span style={styles.message}>{progress.message}</span>
<span style={styles.timer}>{elapsedTime}s</span>
</div>
<div style={styles.progressBar}>
<div
style={{
...styles.progressFill,
width: ${progress.progress}%,
backgroundColor: getProgressColor(progress.progress)
}}
/>
</div>
<div style={styles.percentage}>{progress.progress}%</div>
{progress.tokens && (
<div style={styles.tokens}>Tokens used: {progress.tokens}</div>
)}
{progress.status === 'error' && (
<div style={styles.error}>❌ {progress.message}</div>
)}
</div>
);
}
function getProgressColor(percent) {
if (percent < 30) return '#ff6b6b';
if (percent < 70) return '#ffd93d';
return '#6bcb77';
}
const styles = {
container: {
padding: '20px',
borderRadius: '12px',
backgroundColor: '#f8f9fa',
maxWidth: '500px',
margin: '20px auto',
fontFamily: 'system-ui, sans-serif'
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '10px'
},
status: { fontSize: '20px' },
message: { fontSize: '16px', fontWeight: '500' },
timer: { fontSize: '14px', color: '#666' },
progressBar: {
height: '24px',
backgroundColor: '#e9ecef',
borderRadius: '12px',
overflow: 'hidden'
},
progressFill: {
height: '100%',
transition: 'width 0.3s ease, background-color 0.3s ease',
borderRadius: '12px'
},
percentage: {
textAlign: 'center',
marginTop: '8px',
fontSize: '14px',
color: '#666'
},
tokens: {
textAlign: 'center',
marginTop: '8px',
fontSize: '12px',
color: '#888'
},
error: {
marginTop: '10px',
padding: '10px',
backgroundColor: '#ffe6e6',
borderRadius: '8px',
color: '#d63031'
}
};
โค้ด Frontend - Vanilla JavaScript (ไม่ใช้ React)
<!-- index.html -->
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>AI Progress Demo</title>
<style>
body { font-family: system-ui, sans-serif; padding: 20px; max-width: 600px; margin: 0 auto; }
.progress-container { background: #f5f5f5; border-radius: 12px; padding: 20px; margin: 20px 0; }
.progress-bar { height: 24px; background: #e0e0e0; border-radius: 12px; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); transition: width 0.3s; }
.status { display: flex; justify-content: space-between; margin-bottom: 10px; }
.btn { background: #667eea; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 16px; }
.btn:hover { background: #5a6fd6; }
.btn:disabled { background: #ccc; cursor: not-allowed; }
textarea { width: 100%; height: 120px; padding: 12px; border-radius: 8px; border: 1px solid #ddd; margin: 10px 0; }
.result { background: #e8f5e9; padding: 15px; border-radius: 8px; margin-top: 20px; white-space: pre-wrap; }
.error { background: #ffebee; color: #c62828; padding: 10px; border-radius: 8px; margin-top: 10px; }
.cost-display { background: #fff3e0; padding: 10px; border-radius: 8px; margin: 10px 0; font-size: 14px; }
</style>
</head>
<body>
<h1>🤖 AI Processing with SSE Progress</h1>
<div class="cost-display">
💡 ต้นทุนโดยประมาณ: DeepSeek V3.2 @ $0.42/MTok (HolySheep AI - ประหยัด 85%+)
</div>
<textarea id="prompt" placeholder="พิมพ์คำถามของคุณที่นี่...">อธิบายเกี่ยวกับ Server-Sent Events และวิธีใช้งานกับ AI Processing</textarea>
<button class="btn" id="startBtn" onclick="startProcessing()">เริ่มประมวลผล AI</button>
<div class="progress-container" id="progressContainer" style="display: none;">
<div class="status">
<span id="statusText">กำลังเชื่อมต่อ...</span>
<span id="timer">0s</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%;"></div>
</div>
<p style="text-align: center; margin: 10