← กรณีการใช้งาน

ความคืบหน้าของงานที่ใช้เวลานานและการสตรีมคำตอบจาก AI

การส่งออก นำเข้า เข้ารหัสวิดีโอ และงาน AI: แถบความคืบหน้าที่เดินตามงานจริง ผลลัพธ์ที่ปรากฏทันทีที่เสร็จ และคำตอบที่แสดงขณะกำลังเขียน

การส่งออกข้อมูล การเข้ารหัสวิดีโอ การสร้างรายงาน การนำเข้า และงาน AI อาจใช้เวลาตั้งแต่สิบวินาทีถึงสิบนาที ไอคอนหมุนที่ไม่บอกอะไรเลยทำให้คนรีโหลด กดซ้ำ หรือเลิกไป การแสดงความคืบหน้าจริงและแสดงผลลัพธ์ทันทีที่พร้อม ทำให้คนอยู่ต่อบนหน้า ใช้กับฟีเจอร์ AI ได้ด้วย สตรีมคำตอบของผู้ช่วยขณะกำลังเขียน แม้งานจะทำงานอยู่ในแบ็กกราวด์จ็อบ

สิ่งที่ผู้ใช้ได้รับ

  • แถบความคืบหน้าที่เดินตามงานจริง: “กำลังประมวลผลแถวที่ 3,200 จาก 10,000”
  • ลิงก์ดาวน์โหลดหรือผลลัพธ์ปรากฏทันทีที่งานเสร็จ แม้อยู่ในอีกแท็บ
  • คำตอบของ AI ที่ค่อยๆ ปรากฏ แทนที่จะต้องรอนานแล้วมาทีเดียว
  • ออกจากหน้าไปได้ เพราะจะได้รับแจ้งเมื่องานเสร็จ

ฟีเจอร์ที่ใช้

  • การยืนยันตัวตนผู้ใช้และ sendToUser อัปเดตของงานไปถึงเฉพาะคนที่เริ่มงาน ในทุกแท็บ
  • ช่องส่วนตัว (private-job-<id>) เมื่อหลายคนติดตามงานเดียวกัน
  • Worker ภาษาไหนก็ได้: ไลบรารีเซิร์ฟเวอร์ทางการของ Pusher สำหรับ Node.js, PHP, Python, Go, Ruby, Java และ .NET เผยแพร่จาก worker ของคิวได้
  • การแจ้งเตือนแบบพุช บอกผู้ใช้ที่ปิดแอปไปแล้วว่างานที่ใช้เวลานานเสร็จแล้ว

ขั้นที่ 1: เริ่มงานและรับ ID

app.post('/exports', requireLogin, async (req, res) => {
  const job = await queue.add('export', { userId: req.user.id, filters: req.body });
  res.status(202).json({ jobId: job.id });
});

ขั้นที่ 2: รายงานความคืบหน้าจาก worker

รายงานความคืบหน้าในขั้นที่มีความหมาย ไม่ใช่ทุกแถว:

worker.process('export', async (job) => {
  const user = String(job.data.userId);
  const rows = await db.orders.query(job.data.filters);
  for (let i = 0; i < rows.length; i += 1000) {
    await writeChunk(rows.slice(i, i + 1000));
    await pusher.sendToUser(user, 'job-progress', { jobId: job.id, done: Math.min(i + 1000, rows.length), total: rows.length });
  }
  const url = await uploadAndSign();
  await pusher.sendToUser(user, 'job-done', { jobId: job.id, url });
});

ใน worker ภาษา Python ที่ใช้แพ็กเกจ pusher ก็เหมือนกัน:

pusher_client.send_to_user(str(user_id), "job-progress", {"jobId": job_id, "done": done, "total": total})

ขั้นที่ 3: แสดงในเบราว์เซอร์

เมื่อผู้ใช้ลงชื่อเข้าใช้บนซ็อกเก็ตแล้ว (ดูที่การแจ้งเตือนในแอป):

const { jobId } = await fetch('/exports', { method: 'POST', body: form }).then((r) => r.json());

pusher.user.bind('job-progress', (p) => {
  if (p.jobId === jobId) progress.set(p.done / p.total);
});
pusher.user.bind('job-done', (p) => {
  if (p.jobId === jobId) showDownload(p.url);
});

สตรีมคำตอบจาก AI

เมื่อโมเดล AI เขียนคำตอบอยู่ในแบ็กกราวด์จ็อบ ให้ส่งต่อผลลัพธ์ให้ผู้ใช้เป็นชุดเล็กๆ คำตอบยาวๆ จะใช้แค่หลักสิบข้อความ ไม่ใช่หลักพัน:

let buffer = '';
let last = Date.now();
for await (const chunk of llm.stream(prompt)) {
  buffer += chunk.text;
  if (Date.now() - last > 150 || buffer.length > 2000) {
    await pusher.sendToUser(user, 'ai-delta', { id: answerId, text: buffer });
    buffer = '';
    last = Date.now();
  }
}
await pusher.sendToUser(user, 'ai-done', { id: answerId, text: buffer });
pusher.user.bind('ai-delta', ({ id, text }) => answers.get(id).append(text));
pusher.user.bind('ai-done', ({ id, text }) => answers.get(id).append(text).finish());

เก็บคำตอบฉบับเต็มไว้บนเซิร์ฟเวอร์ด้วย เพื่อให้ยังอยู่หลังรีโหลด

เคล็ดลับ

  • ทุกอัปเดตคือหนึ่งข้อความ อัปเดตทุก 1–5% หรือทุก 150 มิลลิวินาทีก็ดูลื่นแล้ว และหนึ่งงานใช้แค่ไม่กี่สิบข้อความ
  • สมัครรับก่อนเริ่มงาน หรือดึงสถานะงานหนึ่งครั้งเมื่อเปิดหน้า จะได้ไม่พลาดอัปเดตที่ส่งมาก่อนหน้าจะเชื่อมต่อ
  • ถ้าผู้ใช้ปิดแท็บไปแล้ว ให้ส่งพุชตามคู่มือการแจ้งเตือนแบบพุชเมื่องานเสร็จ