การประมวลผล AI ที่ใช้เวลานาน โดยเฉพาะงานที่ต้องประมวลผลข้อมูลจำนวนมาก เช่น การวิเคราะห์เอกสารยาว การสร้างรายงาน หรือการประมวลผล Batch ต้องมีการแสดง Progress ให้ผู้ใช้เห็นว่าระบบทำงานอยู่ ไม่ใช่ค้างไป ในบทความนี้ผมจะสอนวิธี implement Server-Sent Events (SSE) สำหรับงาน AI ที่ใช้เวลานาน พร้อมโค้ดตัวอย่างที่รันได้จริงและเอาไปใช้งานได้ทันที

ทำไมต้องใช้ SSE สำหรับ AI Tasks?

จากประสบการณ์ที่ผมพัฒนาระบบ AI แบบ Real-time มาหลายโปรเจกต์ พบว่า SSE มีข้อดีหลายอย่าง:

เปรียบเทียบต้นทุน AI APIs ปี 2026

ก่อนจะเริ่มเขียนโค้ด มาดูต้นทุนของแต่ละ Provider กันก่อน เพื่อให้เห็นภาพว่า SSE ช่วยให้เราประมวลผลได้คุ้มค่าขึ้นอย่างไร:

ModelOutput 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.2085%+

จะเห็นได้ว่า DeepSeek V3.2 บน HolySheheep AI มีราคาถูกที่สุดในตลาด ที่ $0.42/MTok รวมอัตราแลกเปลี่ยน ¥1=$1 ทำให้ประหยัดได้ถึง 85%+ เมื่อเทียบกับ OpenAI และ Anthropic

Architecture Overview

ระบบ SSE Progress ประกอบด้วย 3 ส่วนหลัก:

โค้ด 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