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 gian | Hiển thị khi | Ví dụ |
|---|---|---|
| Elapsed time | Luôn luôn | "1:23 elapsed" |
| Estimated remaining | Progress linear, tính được | "~30s remaining" |
| Typical duration | Khô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ách | Khi nào dùng |
|---|---|
| SSE | Server 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 |
| WebSockets | Cần hai chiều (cancel, pause). Update frequency cao. Đã dùng WebSockets cho features khác rồi |
| Polling | Infrastructure 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ượng | Hiển thị | Ví dụ |
|---|---|---|
| User thông thường | Tên giai đoạn + mô tả đơn giản | "Finding clusters..." |
| Power users | Giai đoạn + metrics | "Finding clusters (1,247 keywords, 47 found)" |
| Developers | Giai đ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] |
+--------------------------------------------------+
| Element | Mục đích |
|---|---|
| Item count | Context cho quy mô thao tác |
| Danh sách giai đoạn | Hiển thị progress qua pipeline |
| Trạng thái giai đoạn | Complete / In progress / Waiting |
| Timing giai đoạn | Bao lâu các giai đoạn đã hoàn thành |
| Elapsed time | Tổng thời gian thao tác |
| Technology disclosure | Minh bạch về cái gì đang chạy |
| Cancel button | User kiểm soát thao tác dài |