Tôi đã tái tạo tính năng Generative UI mới ra mắt của Claude!

@op7418
TIẾNG TRUNG4 tháng trước · 15 thg 3, 2026
265K
446
63
34
815

TL;DR

Bài viết này trình bày chi tiết quá trình tái tạo giao diện người dùng tạo sinh (generative UI) của Claude trong Code Pilot. Nội dung bao gồm các rào cản kỹ thuật như kết xuất sandbox iframe, phân tích cú pháp JSON streaming và các tinh chỉnh UX để đảm bảo trải nghiệm mượt mà, không bị giật lag.

Anthropic vừa ra mắt một tính năng tương tác generative UI mới trong Claude. Nó giúp bạn giới thiệu các khái niệm và thông tin một cách trực quan ngay trong luồng trò chuyện, giúp dễ hiểu hơn nhiều so với văn bản thuần túy. !Image Tôi đã tìm kiếm các giải pháp tương tự, và khi Claude phát hành tính năng của họ, tôi cảm thấy mình phải đẩy nhanh tiến độ công việc. Đồng thời, tôi có thể phân tích ngược cách tiếp cận của họ để tham khảo. Sau hai ngày liên tục thúc đẩy Codex và Claude, tôi thực sự đã làm được! !https://pbs.twimg.com/amplify_video_thumb/2033112086713364480/img/EUmHCjsTK7tfnToZ.jpg Tính năng này cho phép AI vẽ các biểu đồ tương tác trực tiếp trong chat với đầu ra dạng stream, hiển thị ngay trong quá trình tạo. Trước đây, khi yêu cầu AI viết một trang web, bạn phải đợi toàn bộ mã được tạo xong mới hiển thị, mất rất nhiều thời gian. Bây giờ thì khác. Bạn có thể xem các biểu đồ được vẽ từng nét trên canvas, với các nút SVG hiện ra lần lượt. Bản thân quá trình tạo ra đã rất ấn tượng, và nó có thể tương tác ngay sau khi hoàn thành. Trải nghiệm trực tiếp trong sản phẩm Agent của tôi, Code Pilot: https://github.com/op7418/CodePilot Trong bài viết này, tôi sẽ giới thiệu cách sử dụng, quy trình triển khai cụ thể và một số lưu ý. ## Một số trường hợp sử dụng thú vị? Phân tích dữ liệu: Cuối cùng thì các con số cũng có ý nghĩa Ví dụ, yêu cầu nó vẽ biểu đồ cho "Ước tính chi phí hàng ngày của cuộc xung đột Mỹ-Iran." Trước đây, AI đưa cho bạn một đống văn bản khó thấy mối quan hệ giữa các con số. Giờ đây, nó tạo trực tiếp một biểu đồ, giúp bạn thấy rõ số tiền cho từng phần. Văn bản và biểu đồ được xuất ra cùng nhau, giải thích chỗ cần và vẽ chỗ cần. !Image Công cụ nhỏ: Xây dựng máy tính tương tác hoặc thứ gì đó tương tự Yêu cầu nó tạo một máy tính lãi kép. Kéo thanh trượt để thay đổi số tiền ban đầu hoặc số năm đầu tư, các biểu đồ và con số bên dưới sẽ cập nhật theo thời gian thực. Đây không phải là một hình ảnh tĩnh; nó là một công cụ tương tác thực sự. Tính toán khoản vay, chuyển đổi đơn vị và những thứ tương tự đều có thể thực hiện được. !Image Sơ đồ kiến trúc: Món yêu thích của lập trình viên Bạn có thể yêu cầu nó giúp vẽ kiến trúc của một dự án hoặc trực quan hóa một kế hoạch triển khai. Ví dụ, ở đây tôi yêu cầu nó vẽ luồng hoàn chỉnh từ API đến xác thực JWT. So sánh tính năng, sơ đồ luồng và cấu trúc phân cấp đều được đồ họa hóa, giúp hiểu kiến trúc nhanh hơn nhiều so với đọc mô tả văn bản. !Image Phân tích dữ liệu trực tuyến Một cách chơi khác là ném một liên kết kho lưu trữ GitHub vào nó; nó sẽ thu thập dữ liệu và thực hiện phân tích trực quan. Ví dụ, tôi đã gửi địa chỉ dự án của chính mình, CodePilot, để phân tích. Số sao, số fork, ngăn xếp công nghệ, thiết kế kiến trúc và các mô-đun cốt lõi đều được vẽ thành biểu đồ. Bạn có thể thấy toàn cảnh dự án trong nháy mắt, tốt hơn nhiều so với đọc một văn bản dài. !Image Giải thích tương tác và chuyên sâu Phần mạnh nhất là sự tích hợp chặt chẽ với mô hình; nó không chỉ là đầu ra một lần. Bạn có thể tương tác với các sơ đồ đã tạo và yêu cầu giải thích chi tiết hơn. Ví dụ, ở đây tôi yêu cầu nó giải thích mối quan hệ giữa gió mùa và dòng hải lưu. !Image Nếu muốn biết thêm, chúng ta có thể nhấp vào nút cho cơ chế dòng hải lưu. Nó sẽ tự động gửi một lệnh đến mô hình hiện tại để tiếp tục tạo sơ đồ về cơ chế dòng hải lưu. !Image Tất nhiên, chúng ta có thể thực hiện các tương tác phức tạp hơn, chẳng hạn như trực quan hóa các công thức vật lý và toán học phổ biến. Điều này rất hữu ích cho sinh viên; mọi tham số đều có thể được điều khiển thông qua thanh trượt và đầu vào, và hoạt ảnh thay đổi ngay lập tức. !Image Hỗ trợ các mô hình Trung Quốc Sau khi được triển khai trong CodePilot, nó không chỉ dành riêng cho Claude. Kimi K2.5, Minimax M2.5 và các mô hình gốc của Anthropic đều chạy tốt. Tôi nghĩ đồ họa do K2.5 vẽ còn đẹp hơn cả Sonnet 3.5, và phân tích kiến trúc rất chi tiết. Nếu bạn sử dụng tính năng này, tôi khuyên bạn nên dùng thử K2.5 trước. Được rồi, về cơ bản là phần trình diễn mô hình kết thúc. Nếu bạn không quan tâm đến cách nó được triển khai, bạn có thể chỉ cần cài đặt CodePilot và bắt đầu trải nghiệm. ## Nó đã được triển khai như thế nào? !Image Cách Claude thực hiện Claude.ai chính thức sử dụng cơ chế tool_use. Mô hình gọi một công cụ chuyên dụng để xuất nội dung widget có cấu trúc, và giao diện người dùng phân tích cú pháp các tham số đầu vào của lệnh gọi công cụ để hiển thị. Giải pháp này hoạt động tốt dưới kiến trúc riêng của Claude.ai. Nhưng nó không hoạt động với CodePilot vì ba lý do: Thứ nhất, hạn chế của SDK. CodePilot sử dụng chế độ cài sẵn claude_code của Claude Agent SDK, không cho phép đăng ký các công cụ tùy chỉnh. SDK hiển thị một luồng delta văn bản, vì vậy không thể mở rộng ở cấp độ công cụ. Thứ hai, trải nghiệm streaming. Kết quả của tool_use phải đợi input_json_delta kết thúc mới hiển thị được và nó không hỗ trợ hiển thị HTML gia tăng. Với code fences, HTML đến cùng với luồng văn bản, cho phép hiển thị trong khi tạo. Thứ ba, cách ly hiển thị. Claude.ai sử dụng Shadow DOM để cách ly. Chúng tôi chọn một iframe sandbox. Cách ly iframe triệt để hơn—một môi trường thực thi JavaScript hoàn toàn độc lập, CSP kiểm soát chính xác việc tải tài nguyên và không có rò rỉ kiểu dáng hay thoát script. Cách chúng tôi thực hiện Kích hoạt: Code Fences Mô hình xuất ra một code fence Markdown đặc biệt để kích hoạt hiển thị: ``show-widget {"title":"training_flow","widget_code":"<svg width=\"100%\" viewBox=\"0 0 680 400\">...</svg>"} ` Định dạng này tái sử dụng các chế độ code fence hiện có của CodePilot (image-gen-request, batch-plan, v.v.), mà chuỗi phân tích cú pháp giao diện người dùng hỗ trợ một cách tự nhiên. ![Image](https://pbs.twimg.com/media/HDcR5dyasAAtwTN?format=jpg&name=small) **Hiển thị: Sandbox Iframe** Mỗi widget được hiển thị trong một iframe với sandbox="allow-scripts". srcdoc của iframe là một trang nhận được xây dựng cẩn thận. Chính sách CSP chỉ cho phép các script bên ngoài từ 4 tên miền CDN và connect-src 'none' cấm tất cả các yêu cầu mạng. Các bản cập nhật nội dung được nhận qua postMessage. Trong giai đoạn xem trước stream, widget:update được gửi mà không thực thi script. Quá trình hiển thị cuối cùng gửi widget:finalize, thực thi script. Một ResizeObserver theo dõi các thay đổi chiều cao nội dung và báo cáo lại cho trang cha qua postMessage. Tất cả các lần nhấp vào <a> đều bị chặn và chuyển tiếp đến trang cha để mở trong cửa sổ mới. Đồng bộ hóa chủ đề dựa trên việc theo dõi các thay đổi lớp trên trang cha để chuyển đổi giữa chế độ tối/sáng theo thời gian thực. ![Image](https://pbs.twimg.com/media/HDcR8ilbgAATB-8?format=jpg&name=small) **Kết nối biến CSS** Đây là chìa khóa để làm cho widget hòa trộn trực quan với ứng dụng. CodePilot sử dụng các biến CSS trong không gian màu OKLCH. Hướng dẫn thiết kế widget của Anthropic sử dụng các tên biến tiêu chuẩn như --color-background-primary. Lớp kết nối sẽ tiêm các giá trị biến của CodePilot vào :root của iframe khi nó khởi tạo. CSS được mô hình viết theo hướng dẫn có thể trực tiếp sử dụng màu sắc của chủ đề hiện tại. Khi chuyển sang chế độ tối, trang cha phát hiện thay đổi lớp, tính toán lại các giá trị biến và đẩy chúng vào iframe. ![Image](https://pbs.twimg.com/media/HDcR_fLaYAAL88P?format=jpg&name=small) **Hiển thị Streaming** Đây là phần phức tạp nhất trong toàn bộ quá trình triển khai. Mô hình tạo token theo token. Tại bất kỳ thời điểm nào, mã widget nhận được có thể là JSON chưa hoàn chỉnh, HTML chưa hoàn chỉnh hoặc thẻ <script> chưa hoàn chỉnh. Quy trình xử lý như sau: Regex khớp `show-widget để phân biệt giữa trạng thái "chưa đóng" và "đã đóng". Xác định vị trí thủ công nội dung sau "widget_code":" và giải mã thoát từng ký tự. Không thể sử dụng JSON.parse vì JSON chưa hoàn thành. Khi phát hiện thẻ <script> chưa đóng, hãy cắt bỏ trước <script để tránh hiển thị mã JavaScript dưới dạng văn bản nhìn thấy được. Một debounce 120ms ngăn iframe cập nhật quá thường xuyên. Nội dung stream loại bỏ tất cả các script và trình xử lý sự kiện; không cần tương tác trong giai đoạn xem trước. ![Image](https://pbs.twimg.com/media/HDcSDCGb0AAwY7J?format=jpg&name=small) **Đánh bóng trải nghiệm: Các chi tiết không nên bị chú ý** Từ góc độ mã hoặc triển khai, nó không phức tạp lắm; sự phức tạp nằm ở việc đánh bóng trải nghiệm. Có quá nhiều nơi có thể ảnh hưởng đến trải nghiệm và người dùng cần không nhận thấy những chi tiết đó và quá trình tạo. Điều này đòi hỏi phải xử lý từng giai đoạn khác nhau: 1. Các chi tiết trông không giống streaming 2. Nội dung không nên xuất hiện ![Image](https://pbs.twimg.com/media/HDcSKQLaEAABilZ?format=jpg&name=small) **Văn bản biến mất** Mô hình đầu tiên xuất ra một văn bản giới thiệu ("Hãy để tôi giải thích trực quan..."), sau đó bắt đầu widget fence. Ngay khi fence xuất hiện, văn bản phía trước đột nhiên biến mất và chỉ quay lại sau khi widget kết thúc hiển thị. Lý do là parseAllShowWidgets() trả về một mảng rỗng cho văn bản thuần túy. Khi fence vừa xuất hiện nhưng chưa đóng, văn bản trước fence được truyền vào hàm này và bị loại bỏ. Sửa lỗi: Khi văn bản trước fence không chứa một widget fence hoàn chỉnh, hãy hiển thị trực tiếp dưới dạng <MessageResponse>, bỏ qua hàm phân tích cú pháp. **Nhảy chiều cao** Ngay khi widget kết thúc hiển thị, toàn bộ khu vực trò chuyện bị rung. Chiều cao ban đầu của iframe là 0px. Khi nội dung lần đầu tiên báo cáo chiều cao thực tế của nó, nó có thể là 400px+ và một chuyển đổi CSS làm cho sự thay đổi này diễn ra trong 300ms, dẫn đến một bước nhảy đáng chú ý. Sửa lỗi: Tạm thời vô hiệu hóa các chuyển đổi CSS trong báo cáo chiều cao đầu tiên để chiều cao được đặt ngay lập tức. Các điều chỉnh chiều cao tiếp theo sử dụng chuyển đổi mượt mà. **Nhấp nháy khi hoàn tất** Khi widget chuyển từ xem trước stream sang hiển thị cuối cùng, nội dung nhấp nháy. Iframe nhận thay thế toàn bộ DOM bằng root.innerHTML = html trong quá trình hoàn tất. Ngay cả khi nội dung mới và cũ giống hệt nhau (widget SVG thuần túy), trình duyệt vẫn kích hoạt một khung hình vẽ lại. Sửa lỗi: Trong quá trình hoàn tất, trước tiên hãy phân tích cú pháp HTML mới vào một vùng chứa tạm thời và tách các phần tử script. So sánh HTML trực quan (không có script) với DOM hiện tại—nếu chúng giống nhau, hãy bỏ qua việc thay thế innerHTML và chỉ thêm các script để thực thi. Các widget SVG thuần túy đạt được quá trình hoàn tất không vẽ lại. **Nhảy cuộn** Cuộc trò chuyện đang tự động cuộn xuống dưới cùng, sau đó đột nhiên nhảy trở lại vài trăm pixel và nhảy trở lại một lần nữa. Khi streaming kết thúc, thành phần StreamingMessage ngắt kết nối và thành phần MessageItem được gắn kết. Đây là hai thành phần React hoàn toàn khác nhau và WidgetRenderer bên trong bị phá hủy và xây dựng lại. Chiều cao iframe của phiên bản mới bắt đầu từ 0, khiến chiều cao vùng nội dung giảm mạnh, kích hoạt điều chỉnh cuộn trong use-stick-to-bottom. Sửa lỗi: Bộ nhớ đệm chiều cao ở cấp độ mô-đun. Bất cứ khi nào một widget báo cáo chiều cao của nó, nó sẽ được ghi vào bộ nhớ đệm bằng cách sử dụng 200 ký tự đầu tiên của widgetCode làm khóa. Phiên bản WidgetRenderer mới đọc chiều cao từ bộ nhớ đệm trong quá trình khởi tạo useState, do đó iframe bắt đầu hiển thị ở chiều cao chính xác, loại bỏ quá trình chuyển đổi 0→thực tế. **Rò rỉ mã Script** Khi một widget có Chart.js tải, một khối lớn mã JavaScript được hiển thị ở cuối. Các thẻ <script> do mô hình xuất ra đến từng ký tự. Khi thẻ mở đến nhưng thẻ đóng chưa đến, sanitizeForStreaming loại bỏ thẻ mở, nhưng mã JavaScript bên trong trở thành một nút văn bản trần được hiển thị dưới dạng nội dung nhìn thấy được. Sửa lỗi: Sau khi trích xuất mã một phần trong StreamingMessage, hãy kiểm tra xem <script cuối cùng có khớp với </script> hay không. Nếu không, hãy cắt bỏ ở vị trí <script. Hướng dẫn widget chỉ định rằng các script ở cuối, vì vậy việc cắt bỏ không ảnh hưởng đến nội dung trực quan. Trong quá trình cắt bỏ, một mặt nạ shimmer được hiển thị và thanh trạng thái hiển thị "Đang thêm hoạt ảnh tương tác vào hình ảnh trực quan." **Điều kiện cạnh tranh Iframe Ready** Trong một số trường hợp hiếm hoi, widget hoàn toàn không hiển thị, duy trì ở chiều cao 0px. WidgetRenderer đăng ký một trình lắng nghe sự kiện tin nhắn qua useEffect. Tuy nhiên, script nhận của iframe gửi widget:ready ngay khi nó tải. Nếu iframe tải nhanh hơn hiệu ứng React thực thi, widget:ready được gửi trước khi trình lắng nghe được đăng ký và iframeReady không bao giờ trở thành true. Sửa lỗi: Thêm một callback onLoad vào phần tử iframe như một phương án dự phòng. Khi onLoad kích hoạt, script nhận phải đã thực thi xong, cung cấp một tín hiệu sẵn sàng đáng tin cậy. **Tính ổn định của cây thành phần React** Widget nhấp nháy tại thời điểm fence đóng. Hai vấn đề chồng chéo lên nhau: widget stream một phần không có khóa React và sau khi đóng, nó nhận được key="w-0". Sự thay đổi khóa gây ra việc gắn kết lại. Ngoài ra, lớp phủ shimmer được triển khai với một <div> bao bọc, làm thay đổi cấu trúc cây thành phần và gây ra một lần gắn kết lại khác. Sửa lỗi: Gán một khóa ổn định cho widget một phần (w-N, trong đó N là vị trí dự kiến trong mảng các phân đoạn cuối cùng), nhất quán với khóa sau khi đóng. Di chuyển lớp phủ shimmer bên trong WidgetRenderer, được điều khiển thông qua một prop showOverlay. Cây thành phần vẫn là <WidgetRenderer key="w-N"> trong suốt quá trình. ## **Suy nghĩ cuối cùng** Đối với toàn bộ hệ thống generative UI, phần khó không phải là "làm cho một đoạn HTML chạy trong iframe." Điều đó thật đơn giản. Sự phức tạp thực sự nằm ở việc giữ cho iframe đó ổn định về mặt trực quan trong các quá trình chuyển đổi trạng thái như streaming, chuyển đổi vòng đời thành phần và thay đổi chủ đề. Mọi "nhấp nháy," "nhảy," hoặc "biến mất" đều yêu cầu hiểu về sự điều hòa của React, đường ống hiển thị của trình duyệt và thời gian của postMessage`. Hiệu quả cuối cùng là người dùng thấy các biểu đồ và sơ đồ được đan xen một cách tự nhiên trong các câu trả lời của mô hình, như thể chúng luôn ở đó. Hôm nay đến đây thôi. Nếu bạn thấy bài viết này hữu ích, hãy cho tôi một like hoặc chia sẻ nó với những người bạn có thể cần nó.

Lưu một chạm

Đọc sâu bài viết viral bằng AI trong YouMind

Lưu nguồn, đặt câu hỏi tập trung, tóm tắt lập luận và biến một bài viết viral thành các ghi chú có thể tái sử dụng trong một không gian làm việc AI duy nhất.

Khám phá YouMind
Dành cho nhà sáng tạo

Biến Markdown của bạn thành bài viết 𝕏 gọn gàng

Khi bạn đăng bài viết dài của riêng mình, việc định dạng hình ảnh, bảng và khối mã cho 𝕏 rất mệt mỏi. YouMind biến cả bản nháp Markdown thành một bài viết 𝕏 gọn gàng, sẵn sàng để đăng.

Thử Markdown sang 𝕏

Thêm pattern để giải mã

Bài viết viral gần đây

Khám phá thêm bài viết viral