UX Architect

AI & UX: Thiết kế giao diện cho các sản phẩm sử dụng AI (Human-AI Interaction)

7/19/2026 · 14p đọc


title: "AI & UX: Thiết kế giao diện cho các sản phẩm sử dụng AI (Human-AI Interaction)"
series: "UX Architect: 40 bài Expert-Deep Dive"
part: "Phần 4 — Nâng tầm Chuyên gia"
order: 32
audience: "Product Designer & UX Architect"
reading_time: "13 phút"
tags: ["AI UX", "human-AI interaction", "streaming UI", "LLM product design", "confidence score", "fallback design", "progressive rendering"]

AI & UX: Thiết kế giao diện cho các sản phẩm sử dụng AI (Human-AI Interaction)

Một sản phẩm SaaS quản lý khách hàng vừa ra mắt tính năng "AI tóm tắt lịch sử tương tác" — bấm một nút, hệ thống đọc toàn bộ email, cuộc gọi, ghi chú trong 6 tháng của một khách hàng và trả về một đoạn tóm tắt. Đội thiết kế xử lý giao diện y hệt bất kỳ hành động bất đồng bộ nào khác: bấm nút, hiện spinner, chờ, rồi một khối văn bản xuất hiện — không khác gì load một danh sách đơn hàng từ database.

Vấn đề là: API lấy danh sách đơn hàng thường mất 200-500ms, trong khi gọi một mô hình ngôn ngữ lớn (LLM) để tóm tắt 6 tháng dữ liệu có thể mất 8-15 giây, đôi khi lâu hơn. Tệ hơn, kết quả không phải lúc nào cũng đúng — mô hình có thể bỏ sót một cuộc gọi quan trọng, diễn giải sai ý một email, hoặc "bịa" ra một chi tiết không tồn tại trong dữ liệu gốc (hallucination — ảo giác của mô hình). Người dùng nhìn spinner xoay 12 giây không biết hệ thống có treo hay không, rồi nhận một đoạn tóm tắt sai một chi tiết quan trọng mà không có cách sửa ngay tại chỗ — tốn 12 giây chờ, vẫn không tiết kiệm được công sức nào.

Đây là pain point cốt lõi: đội thiết kế coi tính năng AI như một thao tác thông thường, trong khi bản chất kỹ thuật và hành vi của nó hoàn toàn khác — độ trễ cao hơn nhiều bậc, kết quả không tất định (non-deterministic), luôn có xác suất sai khác 0. Thiết kế UX cho AI cần một bộ nguyên tắc riêng, và những nguyên tắc đó không dừng ở giao diện — chúng đòi hỏi kiến trúc backend hỗ trợ theo.

The UX Problem

Pain point cụ thể tái diễn ở phần lớn sản phẩm mới nhúng AI: giao diện được thiết kế theo tư duy "tính năng thông thường + một icon lấp lánh (sparkle icon) đánh dấu là AI", nhưng ba đặc thù bản chất của AI bị bỏ qua.

Đặc thù thứ nhất — độ trễ (latency) cao và biến thiên lớn. Request tới cơ sở dữ liệu quan hệ thường trả về trong vài trăm mili-giây. Request tới mô hình sinh ngôn ngữ dao động từ 2 đến 30 giây tùy độ dài input/output và tải hệ thống. Dùng chung một spinner "đang tải" cho cả hai loại thao tác khiến chờ 12 giây không phản hồi gì tạo cảm giác "hệ thống treo" hoàn toàn khác chờ 300ms.

Đặc thù thứ hai — kết quả không chắc chắn 100% (non-deterministic, có thể sai). Một hàm tính tổng đơn hàng luôn cho cùng kết quả với cùng input. Một mô hình AI, với cùng input, có thể cho ra hai kết quả khác nhau ở hai lần gọi, không đảm bảo đúng tuyệt đối với ngữ cảnh thực tế. Coi kết quả AI là "sự thật cuối cùng" (ground truth) giống một con số từ database là sai lầm về bản chất nhận thức luận (epistemic), không chỉ là sai lầm thẩm mỹ.

Đặc thù thứ ba — khả năng thất bại toàn phần hoặc một phần. API AI có thể timeout, trả về rỗng, trả nội dung không liên quan, hoặc bị giới hạn hạn ngạch (rate limit) đúng lúc cần nhất. Thiết kế không tính đến các trạng thái này khiến người dùng bị bỏ rơi giữa chừng, không biết nên chờ tiếp, thử lại, hay làm thủ công.

Pain point này phổ biến vì áp lực "tích hợp AI cho kịp trào lưu" khiến nhiều đội coi AI là một microservice bổ sung, cắm vào luồng UI có sẵn mà không thiết kế lại trải nghiệm chờ đợi và sửa lỗi.

Design Framework

Nguyên tắc thiết kế cho tương tác người-AI (Human-AI Interaction) xoay quanh bốn trụ cột, mỗi trụ cột trả lời trực tiếp một đặc thù kể trên.

1. Trạng thái xử lý phải minh bạch và có tiến độ (visible, informative processing state). Không dùng spinner chung chung cho mọi độ trễ. Với thao tác AI kéo dài trên 2-3 giây, giao diện cần truyền tải hệ thống đang làm gì (ví dụ "đang đọc 47 email...", "đang phân tích lịch sử cuộc gọi..."), và tốt hơn nữa, hiển thị kết quả ngay khi có từng phần thay vì bắt người dùng chờ đến khi toàn bộ hoàn tất.

2. Kết quả AI phải luôn có đường sửa/từ chối (editable, dismissible output). Không bao giờ đặt kết quả AI ở vị trí "chỉ đọc" (read-only) hoặc coi nó là hành động cuối cùng không thể đảo ngược. Người dùng phải luôn có nút "Sửa", "Không dùng kết quả này", hoặc "Yêu cầu tạo lại" ngay cạnh kết quả, không ẩn trong menu phụ.

3. Hiển thị mức độ tin cậy khi phù hợp (calibrated confidence signaling). Với tác vụ mà độ chính xác quan trọng (phân loại dữ liệu, gợi ý quyết định kinh doanh, trích xuất thông tin), giao diện nên truyền đạt mức độ chắc chắn — bằng ngôn ngữ tự nhiên, phân loại rời rạc (cao/trung bình/thấp), hoặc trích dẫn nguồn dữ liệu gốc để đối chiếu. Tránh con số phần trăm giả-chính-xác (ví dụ "97.3% chính xác") nếu hệ thống không có cơ sở đo lường đáng tin — tạo cảm giác chắc chắn giả tạo còn nguy hiểm hơn không hiển thị gì.

4. Luôn có phương án dự phòng (graceful fallback) khi AI thất bại. Mọi luồng dùng AI phải có đường lùi về thao tác thủ công hoặc phi-AI khi mô hình timeout, trả lỗi, hoặc trả kết quả rỗng/vô nghĩa. Người dùng không bao giờ bị đặt vào tình huống "bế tắc" chỉ vì một lệnh gọi AI thất bại.

Bốn nguyên tắc này là một hệ thống, không phải danh sách rời rạc: minh bạch trạng thái xử lý giảm lo lắng trong lúc chờ; khả năng sửa/từ chối xử lý hậu quả khi kết quả sai; tín hiệu tin cậy giúp hiệu chỉnh mức độ tin tưởng trước khi hành động; fallback đảm bảo sản phẩm vẫn dùng được khi AI — vốn kém tin cậy hơn logic tất định — không hoạt động.

The "Architect's" Insight

Đây là phần phân biệt một UX Architect với một designer thuần thẩm mỹ: nguyên tắc "trạng thái xử lý phải minh bạch và có tiến độ" ở trên, khi khai triển đúng cho tác vụ AI có độ trễ cao, không dừng ở việc chọn animation cho đẹp — nó đòi hỏi một kiến trúc kỹ thuật khác hẳn ở tầng backend, và quyết định UX này chính là quyết định kiến trúc.

Vấn đề gốc: request-response truyền thống không đủ cho tác vụ AI dài hơi. Trong mô hình HTTP request-response cổ điển, client gửi request, server xử lý toàn bộ, rồi trả về một response duy nhất khi xong. Với API tính tổng đơn hàng mất 300ms, mô hình này hoàn toàn ổn. Nhưng với API gọi LLM tóm tắt 6 tháng dữ liệu, mất 10-15 giây, mô hình này buộc client giữ kết nối mở suốt thời gian đó mà không nhận tín hiệu nào cho đến khi hoàn tất — chính là nguyên nhân trực tiếp của màn hình trắng-rồi-hiện-cả-đoạn-văn mà pain point đầu bài mô tả.

Giải pháp "hiển thị dần" (progressive rendering / streaming) đòi hỏi giao thức truyền dữ liệu khác ở tầng API. Để chữ xuất hiện dần đúng như mô hình sinh ra nó (token-by-token, giống cách trợ lý AI dạng chat hiển thị câu trả lời), backend không thể trả về một JSON response duy nhất sau khi toàn bộ quá trình sinh văn bản hoàn tất. Backend phải mở kết nối streaming — phổ biến nhất là Server-Sent Events (SSE) hoặc chunked transfer encoding, một số kiến trúc chọn WebSocket khi cần hai chiều — và đẩy từng chunk/token về client ngay khi model sinh ra, thay vì gom lại rồi trả một lần.

Điều này kéo theo hàng loạt hệ quả kiến trúc cụ thể, không chỉ là "thêm một tính năng nhỏ":

// Kiến trúc KHÔNG hỗ trợ streaming — request-response truyền thống
// Client phải chờ toàn bộ response, không có tín hiệu tiến độ
@Post('summarize')
async summarize(@Body() dto: SummarizeDto): Promise<{ text: string }> {
  const fullText = await this.aiService.generateSummary(dto); // chờ 10-15s
  return { text: fullText }; // chỉ trả về MỘT LẦN khi xong toàn bộ
}
// Kiến trúc HỖ TRỢ streaming — NestJS với Server-Sent Events
@Sse('summarize/stream')
summarizeStream(@Query() dto: SummarizeDto): Observable<MessageEvent> {
  return this.aiService.generateSummaryStream(dto).pipe(
    map((chunk) => ({
      data: { token: chunk, done: false },
    } as MessageEvent)),
  );
  // Mỗi chunk được đẩy về client NGAY khi model sinh ra,
  // client render dần, không chờ toàn bộ hoàn tất
}

Ở tầng frontend, render dần từng chunk không đơn thuần là "thêm animation gõ chữ" — nó đòi hỏi component quản lý một trạng thái tích lũy (accumulating state) thay vì trạng thái tĩnh gán một lần, cùng xử lý kết nối bị ngắt giữa chừng (client phải khôi phục hoặc hiển thị phần đã nhận được thay vì mất trắng).

Hệ quả tới cơ sở dữ liệu. Khi kết quả AI có thể bị chỉnh sửa/từ chối một phần (nguyên tắc thứ hai), schema không thể chỉ có một cột content: text như lưu ghi chú thông thường. Cần tách bạch nội dung gốc AI sinh ra (immutable, để audit và đối chiếu khi "tạo lại") và nội dung đã chỉnh sửa, cùng metadata độ tin cậy và trạng thái chấp nhận:

CREATE TABLE ai_summary (
  id BIGINT PRIMARY KEY,
  source_type VARCHAR(50),        -- 'customer_interaction_history'
  source_ref_id BIGINT,
  model_name VARCHAR(100),        -- ghi lại model đã dùng, để audit khi có khiếu nại
  raw_output TEXT,                -- kết quả gốc AI sinh ra, KHÔNG được sửa
  edited_output TEXT NULL,        -- nội dung sau khi user chỉnh sửa, NULL nếu chưa sửa
  confidence_level ENUM('high','medium','low') NULL,
  status ENUM('generated','accepted','edited','rejected','regenerated'),
  generated_at TIMESTAMP,
  reviewed_at TIMESTAMP NULL
);

Việc tách raw_outputedited_output không phải chi tiết vặt vãnh — nó là hệ quả trực tiếp của nguyên tắc "cho phép sửa/từ chối kết quả AI dễ dàng". Nếu UX không cho phép sửa, schema chỉ cần một cột duy nhất. Chính vì UX yêu cầu sửa được và hệ thống cần biết "đây là bản gốc AI hay bản người dùng đã can thiệp" (quan trọng để đánh giá chất lượng model theo thời gian, và để chứng minh nội dung nào do máy sinh khi cần tuân thủ quy định), quyết định UX buộc schema mở rộng thêm ít nhất ba trường.

Hệ quả tới xử lý lỗi và timeout. Nguyên tắc "luôn có fallback khi AI thất bại" đòi hỏi backend triển khai timeout có kiểm soát (thường 15-30 giây), cơ chế retry có giới hạn, và một exception path riêng khỏi luồng lỗi HTTP thông thường — vì lỗi "AI timeout" cần thông điệp và hành động khác hẳn lỗi "server 500". Điều này thường kéo theo tách một module riêng (ví dụ modules/ai-gateway) chịu trách nhiệm bọc mọi lệnh gọi tới AI provider với timeout, retry, và circuit breaker (ngắt mạch tạm thời khi provider liên tục lỗi), thay vì gọi thẳng AI SDK rải rác trong các service nghiệp vụ.

Tóm lại, quyết định "hiển thị dần thay vì spinner" kéo theo: (1) giao thức API chuyển từ request-response sang streaming, (2) frontend quản lý trạng thái tích lũy, (3) schema database tách bạch nội dung gốc/đã sửa và lưu metadata tin cậy, (4) backend cần module gateway riêng xử lý timeout/retry/fallback. Đề xuất "hãy làm cho nó streaming" thực chất là đề xuất thay đổi kiến trúc API và schema — cần trình bày rõ với đội kỹ thuật, không phải một ghi chú UI trong file thiết kế.

Practical Example

Quay lại tính năng "tóm tắt lịch sử tương tác khách hàng bằng AI" ở đầu bài.

Không áp dụng nguyên tắc: bấm nút → spinner tròn xoay, không chữ giải thích → sau 12 giây, một đoạn văn bản 200 từ xuất hiện đột ngột toàn bộ → không nút sửa, không cách đối chiếu dữ liệu gốc → nếu API lỗi, chỉ thấy thông báo "Đã có lỗi xảy ra" không rõ nguyên nhân, không hướng xử lý tiếp theo.

Áp dụng đầy đủ bốn nguyên tắc:

Bước Trải nghiệm người dùng thấy Cơ chế kỹ thuật đứng sau
1. Bấm "Tóm tắt bằng AI" Nút chuyển loading, kèm chữ "Đang đọc 47 tương tác gần nhất..." Backend fetch dữ liệu, đếm bản ghi, trả ngay số lượng qua event đầu của stream
2. Sau ~1.5 giây Văn bản xuất hiện dần từng câu, con trỏ nhấp nháy như đang gõ SSE đẩy từng chunk token ngay khi model sinh ra, client append vào state tích lũy
3. Hoàn tất Đoạn tóm tắt đầy đủ, kèm nhãn "Được tạo bởi AI · Dựa trên 47 tương tác" và nút "Sửa" + "Tạo lại" status = 'generated', confidence_level tính theo số lượng nguồn và độ dài input
4. Sửa 1 câu sai Câu vừa sửa đánh dấu nhẹ, bản gốc AI vẫn giữ lại để đối chiếu edited_output được ghi, raw_output giữ nguyên, status = 'edited'
5. AI timeout ở bước 2 "AI đang mất nhiều thời gian hơn dự kiến. Bạn có thể chờ thêm hoặc xem lịch sử tương tác gốc bên dưới." kèm link dữ liệu thô Timeout 20s ở gateway kích hoạt fallback UI, không để client treo vô thời hạn

Mỗi dòng ở cột "trải nghiệm người dùng thấy" đều có cơ chế kỹ thuật cụ thể đứng sau, không chỉ là quyết định giao diện.

Common Pitfalls

1. Thiết kế như thể AI luôn đúng 100%, không có đường sửa. Lỗi nghiêm trọng và phổ biến nhất: đặt kết quả AI trực tiếp vào luồng nghiệp vụ (tự động điền trường dữ liệu chính thức, tự động gửi email khách hàng) mà không có bước xác nhận của con người ở giữa. Khi mô hình "ảo giác" ra một nhận định sai, sai sót lan thẳng vào dữ liệu vận hành thật mà không ai kịp chặn. Nguyên tắc đúng: mọi output AI ảnh hưởng tới quyết định quan trọng phải qua trạng thái "chờ xác nhận" (pending review) trước khi thành dữ liệu chính thức, trừ phi rủi ro sai thực sự thấp.

2. Ẩn hoàn toàn việc một tính năng dùng AI, khiến người dùng bối rối khi kết quả không nhất quán. Khi một tính năng thông thường luôn cho cùng kết quả với cùng input, người dùng mặc định mọi tính năng đều theo logic tất định đó. Nếu tính năng AI ngụy trang y hệt (không nhãn, không chỉ dấu) và hai lần bấm cho ra kết quả hơi khác nhau, người dùng sẽ nghĩ sản phẩm có bug thay vì hiểu đó là đặc tính vốn có của AI. Nhãn nhỏ "Được tạo bởi AI" là công cụ hiệu chỉnh kỳ vọng (expectation calibration) giúp diễn giải đúng bản chất kết quả.

3. Nhầm lẫn giữa "spinner đẹp" và "streaming thật". Lỗi tinh vi hơn: đội thiết kế yêu cầu hiệu ứng "chữ chạy dần", nhưng đội kỹ thuật giả lập bằng cách chờ toàn bộ response về rồi dùng JavaScript hiển thị từng ký tự máy móc (typewriter effect giả). Hai cách trông giống nhau trong demo, nhưng bản chất khác hẳn: cách giả lập vẫn bắt người dùng chờ đủ 12 giây "im lặng" ở backend trước khi hiệu ứng giả chạy — không giải quyết vấn đề gốc là độ trễ AI, chỉ che giấu bằng animation. Cần yêu cầu đúng streaming thật (dữ liệu render ngay khi tới) khi làm việc với đội kỹ thuật.

Checklist

  • Mọi tác vụ AI có độ trễ dự kiến trên 2-3 giây có trạng thái xử lý minh bạch, mô tả rõ hệ thống đang làm gì (không spinner chung chung không ngữ cảnh).
  • Với tác vụ tạo nội dung dài (tóm tắt, soạn thảo, phân tích), đã đánh giá triển khai streaming/progressive rendering thay vì chờ toàn bộ rồi hiển thị một lần — và xác nhận với backend về giao thức (SSE/WebSocket) khả thi.
  • Mọi kết quả AI hiển thị cho người dùng đều có nút sửa, từ chối, hoặc yêu cầu tạo lại ngay cạnh, không ẩn trong menu phụ.
  • Có nhãn/chỉ dấu rõ ràng cho biết nội dung nào do AI sinh ra, đặt ở vị trí người dùng chắc chắn thấy trước khi tin tưởng kết quả.
  • Với tác vụ rủi ro cao khi sai (quyết định tài chính, dữ liệu khách hàng chính thức), có cơ chế hiển thị mức độ tin cậy hoặc trích dẫn nguồn dữ liệu để đối chiếu, không hiển thị phần trăm tin cậy giả tạo thiếu cơ sở đo lường.
  • Đã thiết kế rõ trạng thái thất bại (timeout, lỗi, kết quả rỗng) với thông điệp cụ thể và luôn có đường lùi về thao tác thủ công.
  • Đã trao đổi với đội kỹ thuật về schema: có tách bạch nội dung AI gốc và nội dung đã qua chỉnh sửa hay chưa, phục vụ audit và cải thiện model về sau.

🔗 Bài viết liên quan

  • Error Handling & Edge Cases — nguyên tắc thiết kế fallback khi AI thất bại là một trường hợp mở rộng của tư duy xử lý lỗi và edge case được bàn sâu ở bài này.
  • Designing for Complexity — tính bất định của kết quả AI là một dạng phức tạp cần thiết kế tường minh, không nên che giấu khỏi người dùng.

Bài trước: UX Writing · Bài tiếp theo: Data Visualization

AI & UX: Thiết kế giao diện cho các sản phẩm sử dụng AI (Human-AI Interaction)