Cách Mình Hiển Thị Tiến Độ

Nguyên tắc giúp các thao tác AI cảm thấy responsive và đáng tin cậy

Embedding 1,000 items mất 10 giây. Clustering thêm 5 giây. Labeling thêm 15 giây nữa. Tổng cộng 30 giây mà chỉ hiện cái spinner quay quay thì user tưởng app treo rồi. Họ sẽ refresh, mất hết data, rồi bỏ luôn.

Vấn đề không phải tốc độ -- AI workloads vốn chậm mà. Vấn đề là im lặng. User cần biết chuyện gì đang xảy ra, còn phải chờ bao lâu, và có kiểm soát được gì không.

Câu hỏi cốt lõi: "Tại bất kỳ thời điểm nào trong quá trình xử lý, user có biết chuyện gì đang xảy ra và phải chờ khoảng bao lâu không?"

Hiển thị giai đoạn, không chỉ phần trăm

Một thanh progress duy nhất chạy từ 0% đến 100% không nói lên điều gì cả. User không biết đang ở bước nào, còn mấy bước nữa, hay cái 47% kia nghĩa là gì.

Thay vào đó, chia thao tác thành các giai đoạn mà user hiểu được: "Analyzing text", "Computing similarities", "Finding clusters", "Generating labels". Hiển thị giai đoạn nào đang active, giai đoạn nào xong rồi.

Tại sao? Vì giai đoạn tạo ra cảm giác chuyển động. Thấy chuyển từ "Computing similarities" sang "Finding clusters" thì yên tâm hơn nhiều so với nhìn thanh progress nhích từ 47% lên 48%.

Stream updates real-time

Đừng buffer hết rồi mới hiện. User chờ 30 giây không thấy gì, rồi bùm -- mọi thứ hiện ra cùng lúc. Trải nghiệm đó tệ lắm.

Dùng Server-Sent Events (SSE) hoặc WebSockets để push updates ngay khi có. Mỗi lần một giai đoạn xong hoặc progress thay đổi, update UI liền. Đừng bắt user chờ kết quả cuối cùng mới cho xem thông tin.

Mỗi update nhỏ là một lời cam kết: "App vẫn đang chạy, yên tâm đi." Không có update thì user sẽ tự hỏi app chết rồi hay chưa.

Thời gian: hiển thị cái gì, giấu cái gì

Luôn hiển thị elapsed time -- cái này chính xác 100%, không có gì phải sợ.

Estimated time thì khác. Chỉ hiện khi tính được hợp lý, ví dụ embedding thì biết rõ đã xử lý bao nhiêu items trên tổng số. Còn clustering hay optimization thì thời gian không đoán được -- đừng cố đoán, hiện sai còn tệ hơn không hiện.

Khi không ước tính được thì ghi "Usually takes 1-2 minutes" -- vừa thật thà vừa hữu ích.

Loại thời gianHiển thị khiVí dụ
Elapsed timeLuôn luôn"1:23 elapsed"
Estimated remainingProgress linear, tính được"~30s remaining"
Typical durationKhông tính được estimate"Usually takes 1-2 minutes"

Giải thích bằng ngôn ngữ người dùng

"Running HDBSCAN with min_cluster_size=15" -- user đọc xong vẫn không hiểu app đang làm gì cho mình.

"Finding groups of similar keywords" -- à, vậy là nó đang nhóm keyword giống nhau lại. Hiểu ngay.

Nguyên tắc đơn giản: giải thích mục đích, không phải thuật toán. User quan tâm kết quả họ sẽ nhận được, không phải algorithm nào đang chạy bên dưới.

Khi lỗi xảy ra: cho phép recovery

Error message kiểu "Something went wrong. Please try again." là vô dụng. User không biết sai ở đâu, không biết retry có ích gì không, và quan trọng nhất -- mất hết progress đã chạy.

Cách làm đúng:

  • Nói rõ giai đoạn nào fail: "Label generation failed"
  • Giải thích tại sao: "API rate limit exceeded"
  • Đề xuất bước tiếp: "Retry in 60 seconds"
  • Giữ lại progress đã hoàn thành -- cho phép retry từ giai đoạn fail, không phải từ đầu

Ai mà muốn chạy lại embedding 47 giây chỉ vì labeling fail ở bước cuối =))

Chọn cách push updates

CáchKhi nào dùng
SSEServer push, client chỉ nhận. HTTP infrastructure bình thường, cần auto-reconnect. Đa số trường hợp của mình dùng cái này
WebSocketsCần hai chiều (cancel, pause). Update frequency cao. Đã dùng WebSockets cho features khác rồi
PollingInfrastructure không hỗ trợ SSE/WS. Updates thưa (5+ giây). Ưu tiên đơn giản tuyệt đối

Mức chi tiết cho từng đối tượng

Đối tượngHiển thịVí dụ
User thông thườngTên giai đoạn + mô tả đơn giản"Finding clusters..."
Power usersGiai đoạn + metrics"Finding clusters (1,247 keywords, 47 found)"
DevelopersGiai đoạn + technical params"HDBSCAN: min_cluster_size=12, method=eom"

Mặc định cho user thông thường. Thêm toggle "Show details" cho ai muốn xem kỹ hơn.

Khi nào nên hiển thị estimated time?

Hiện estimates khi progress linear (X of Y items), có historical data, và accuracy trên 50%.

Giấu estimates khi progress không đoán được (clustering, optimization), khi chỉ đang đoán mò, hoặc khi variability quá cao.

Một con số sai còn tệ hơn không có con số. User thấy "2 minutes remaining" mà chờ 10 phút thì mất niềm tin vào toàn bộ app luôn.

Những lỗi hay mắc

Progress bar nhảy lung tung -- hiện 60%, rồi tụt 45%, rồi lên 80%. Nguyên nhân thường là dùng metric không monotonic. Fix: dựa progress trên items processed hoặc stages completed -- chỉ đi lên, không bao giờ đi xuống.

Updates quá dày hoặc quá thưa -- UI nhấp nháy vì update mỗi 10ms, hoặc đơ 30 giây giữa hai updates. Throttle khoảng 200-500ms là vừa, và đảm bảo ít nhất một update mỗi 5 giây dù giai đoạn đó chạy lâu.

Không có nút Cancel -- user muốn dừng thì phải đóng browser tab. Đây là UX tệ nhất. Luôn có cancel button, dừng sạch sẽ, và giữ lại partial results nếu dùng được.

Giấu hoàn toàn công nghệ -- user không tin kết quả vì nó trông như magic. Thêm dòng nhỏ "Using: OpenAI embeddings + HDBSCAN clustering" cho transparency. Không cần giải thích chi tiết, chỉ cần cho biết.

Coi mọi giai đoạn bằng nhau -- 5 giai đoạn, mỗi cái chiếm 20% progress, nhưng giai đoạn 2 thực tế mất 80% thời gian. User thấy progress kẹt ở 20% mãi rồi nhảy lên 80%. Weight các giai đoạn theo typical duration, hoặc hiện stage progress riêng bên cạnh overall progress.

Checklist đánh giá

Đang tốt nếu:

  • User thấy giai đoạn nào đang chạy
  • Progress updates xuất hiện trong 2-3 giây sau actual progress
  • Elapsed time luôn hiển thị trong thao tác dài
  • User hiểu hệ thống đang làm gì mà không cần kiến thức kỹ thuật
  • Thao tác fail giải thích sai ở đâu và gợi ý bước tiếp theo

Cần cải thiện nếu:

  • User hỏi "nó bị kẹt rồi à?" trong quá trình hoạt động bình thường
  • Chỉ hiển thị spinner không kèm thông tin gì khác
  • Errors hiện "Something went wrong" không chi tiết
  • Muốn cancel phải đóng browser
  • Phần trăm progress không phản ánh actual progress

Tham khảo nhanh

+--------------------------------------------------+
|  Analyzing your 1,247 keywords                   |
|                                                  |
|  [====] Preprocessing          Complete          |
|  [====] Computing embeddings   Complete (47s)    |
|  [==  ] Finding clusters       In progress...    |
|  [    ] Generating labels      Waiting           |
|  [    ] Creating visualization Waiting           |
|                                                  |
|  Elapsed: 1:23                                   |
|  Using: OpenAI embeddings + HDBSCAN clustering   |
|                                                  |
|  [Cancel]                                        |
+--------------------------------------------------+
ElementMục đích
Item countContext cho quy mô thao tác
Danh sách giai đoạnHiển thị progress qua pipeline
Trạng thái giai đoạnComplete / In progress / Waiting
Timing giai đoạnBao lâu các giai đoạn đã hoàn thành
Elapsed timeTổng thời gian thao tác
Technology disclosureMinh bạch về cái gì đang chạy
Cancel buttonUser kiểm soát thao tác dài