Anthropic 이 최근 Claude 에 새로운 생성형 UI 인터랙션을 출시했습니다. 채팅 스트림 내에서 개념과 정보를 시각적으로 소개해 주어, 일반 텍스트보다 훨씬 이해하기 쉽습니다. !Image 저도 비슷한 솔루션을 찾고 있었는데, Claude 가 자체 솔루션을 출시하면서 작업 속도를 높여야겠다고 생각했습니다. 동시에 그들의 접근 방식을 리버스 엔지니어링하여 참고할 수도 있었습니다. 이틀 동안 Codex 와 Claude 에 집중적으로 프롬프트를 입력한 끝에, 실제로 해냈습니다! !https://pbs.twimg.com/amplify_video_thumb/2033112086713364480/img/EUmHCjsTK7tfnToZ.jpg 이 기능을 사용하면 AI 가 채팅에서 직접 스트리밍 출력으로 대화형 차트를 그리고, 생성됨에 따라 렌더링할 수 있습니다. 이전에는 AI 에게 웹페이지를 작성하도록 요청할 때 전체 코드가 생성되어 렌더링될 때까지 기다려야 했고, 시간이 매우 오래 걸렸습니다. 이제는 다릅니다. 캔버스에 차트가 한 획씩 그려지고 SVG 노드가 하나둘씩 나타나는 것을 볼 수 있습니다. 생성 과정 자체가 놀랍고, 완료된 후에는 즉시 상호 작용이 가능합니다. 제 Agent 제품인 Code Pilot 에서 직접 경험해 보세요: https://github.com/op7418/CodePilot 이 글에서는 그 사용법, 구체적인 구현 과정, 그리고 몇 가지 주의사항을 소개하겠습니다. ## 재미있는 사용 사례는 무엇이 있을까요? 데이터 분석: 드디어 숫자가 이해가 됩니다 예를 들어, "미-이란 분쟁의 일일 비용 추정"에 대한 차트를 그리도록 요청해 보세요. 이전에는 AI 가 숫자 관계를 알아보기 어려운 텍스트 덩어리를 제공했습니다. 이제는 차트를 직접 생성하여 각 부분의 금액을 한눈에 명확하게 보여줍니다. 텍스트와 차트가 함께 출력되어, 필요한 곳에 설명하고 필요한 곳에 그림을 그립니다. !Image 소형 도구: 대화형 계산기 등 만들기 복리 계산기를 만들도록 요청하세요. 슬라이더를 드래그하여 초기 금액이나 투자 기간을 변경하면 아래의 차트와 숫자가 실시간으로 업데이트됩니다. 이것은 정적 이미지가 아니라 진정한 대화형 도구입니다. 대출 계산, 단위 변환 등도 모두 가능합니다. !Image 아키텍처 다이어그램: 프로그래머가 가장 좋아하는 기능 프로젝트의 아키텍처를 그리거나 구현 계획을 시각화하도록 요청할 수 있습니다. 예를 들어, 여기서는 API 에서 JWT 인증까지의 전체 흐름을 그리도록 요청했습니다. 기능 비교, 순서도, 계층 구조 등이 모두 그래픽으로 표현되어, 텍스트 설명을 읽는 것보다 아키텍처를 훨씬 빨리 이해할 수 있습니다. !Image 온라인 데이터 분석 또 다른 활용 방법은 GitHub 저장소 링크를 던져주면, 데이터를 크롤링하여 시각적 분석을 수행하는 것입니다. 예를 들어, 제 프로젝트 주소인 CodePilot 을 보내 분석을 요청했습니다. Stars, Forks, 기술 스택, 아키텍처 설계, 핵심 모듈 등이 모두 차트로 그려집니다. 프로젝트의 전체적인 모습을 한눈에 볼 수 있어, 긴 텍스트를 읽는 것보다 훨씬 좋습니다. !Image 대화형 심층 설명 가장 강력한 점은 모델과의 긴밀한 통합입니다. 일회성 출력이 아닙니다. 생성된 다이어그램과 상호 작용하고 더 자세한 설명을 요청할 수 있습니다. 예를 들어, 여기서는 몬순과 해류의 관계를 설명하도록 요청했습니다. !Image 더 자세히 알고 싶다면 해류 메커니즘 버튼을 클릭할 수 있습니다. 그러면 현재 모델에 명령을 자동으로 보내 해류 메커니즘에 대한 다이어그램을 계속 생성합니다. !Image 물론, 일반적인 물리 및 수학 공식을 시각화하는 등 더 복잡한 상호 작용도 수행할 수 있습니다. 이것은 학생들에게 매우 유용합니다. 모든 매개변수를 슬라이더와 입력으로 제어할 수 있으며, 애니메이션이 즉시 변경됩니다. !Image 중국어 모델 지원 CodePilot 에 구현된 후에는 Claude 에서만 사용할 수 있는 것이 아닙니다. Kimi K2.5, Minimax M2.5, Anthropic 의 네이티브 모델 모두에서 잘 작동합니다. 제 생각에는 K2.5 가 그린 그래픽이 Sonnet 3.5 보다 더 보기 좋고, 아키텍처 분석도 매우 상세합니다. 이 기능을 사용한다면 K2.5 를 먼저 사용해 보시길 권장합니다. 자, 모델 데모는 여기까지입니다. 구현 방법에 관심이 없다면, 그냥 CodePilot 을 설치하고 즐기시면 됩니다. ## 어떻게 구현되었나요? !Image Claude 의 방식 Claude.ai 는 공식적으로 tool_use 메커니즘을 사용합니다. 모델이 전용 도구를 호출하여 구조화된 위젯 콘텐츠를 출력하고, 프론트엔드가 도구 호출의 입력 매개변수를 파싱하여 렌더링합니다. 이 솔루션은 Claude.ai 자체 아키텍처에서는 잘 작동합니다. 하지만 CodePilot 에서는 세 가지 이유로 작동하지 않습니다. 첫째, SDK 제한입니다. CodePilot 은 Claude Agent SDK 의 claude_code 사전 설정 모드를 사용하는데, 이는 사용자 정의 도구 등록을 허용하지 않습니다. SDK 는 텍스트 델타 스트림을 노출하므로 도구 수준에서 확장할 수 없습니다. 둘째, 스트리밍 경험입니다. tool_use 의 결과는 input_json_delta 가 완료될 때까지 기다렸다가 렌더링해야 하며, 증분 HTML 렌더링을 지원하지 않습니다. 코드 펜스를 사용하면 HTML 이 텍스트 스트림과 함께 도착하므로 생성하면서 렌더링할 수 있습니다. 셋째, 렌더링 격리입니다. Claude.ai 는 Shadow DOM 을 사용하여 격리합니다. 우리는 샌드박스 iframe 을 선택했습니다. Iframe 격리가 더 철저합니다. 완전히 독립적인 JS 실행 환경, CSP 가 리소스 로딩을 정밀하게 제어하며, 스타일 누출이나 스크립트 탈출이 없습니다. 우리의 방식 트리거: 코드 펜스 모델은 특수 Markdown 코드 펜스를 출력하여 렌더링을 트리거합니다: ``show-widget {"title":"training_flow","widget_code":"<svg width=\"100%\" viewBox=\"0 0 680 400\">...</svg>"} ` 이 형식은 CodePilot 의 기존 코드 펜스 모드(image-gen-request, batch-plan 등)를 재사용하며, 프론트엔드 파서 체인이 자연스럽게 지원합니다.  **렌더링: 샌드박스 Iframe** 각 위젯은 sandbox="allow-scripts" 가 설정된 iframe 에서 렌더링됩니다. iframe 의 srcdoc 은 신중하게 구성된 수신자 페이지입니다. CSP 정책은 4 개의 CDN 도메인에서만 외부 스크립트를 허용하고, connect-src 'none' 은 모든 네트워크 요청을 금지합니다. 콘텐츠 업데이트는 postMessage 를 통해 수신됩니다. 스트리밍 미리보기 단계에서는 스크립트를 실행하지 않고 widget:update 가 전송됩니다. 최종 렌더링 시 widget:finalize 가 전송되어 스크립트를 실행합니다. ResizeObserver 는 콘텐츠 높이 변경을 모니터링하고 postMessage 를 통해 부모 페이지에 보고합니다. 모든 <a> 클릭은 가로채어 부모 페이지로 전달되어 새 창에서 열립니다. 테마 동기화는 부모 페이지의 클래스 변경을 모니터링하여 다크/라이트 모드를 실시간으로 전환하는 데 의존합니다.  **CSS 변수 브리징** 이것은 위젯이 앱과 시각적으로 조화를 이루도록 하는 핵심입니다. CodePilot 은 OKLCH 색상 공간에서 CSS 변수를 사용합니다. Anthropic 의 위젯 디자인 가이드라인은 --color-background-primary 와 같은 표준 변수 이름을 사용합니다. 브리징 레이어는 iframe 이 초기화될 때 CodePilot 의 변수 값을 iframe 의 :root 에 주입합니다. 가이드라인에 따라 모델이 작성한 CSS 는 현재 테마의 색상을 직접 사용할 수 있습니다. 다크 모드로 전환하면 부모 페이지가 클래스 변경을 감지하고, 변수 값을 다시 계산하여 iframe 으로 푸시합니다.  **스트리밍 렌더링** 이것은 전체 구현에서 가장 복잡한 부분입니다. 모델은 토큰 단위로 생성합니다. 어느 순간에든 수신된 위젯 코드는 불완전한 JSON, 불완전한 HTML 또는 불완전한 <script> 태그일 수 있습니다. 처리 흐름은 다음과 같습니다: 정규식이 `show-widget 과 일치하여 "미완료" 및 "완료" 상태를 구분합니다. "widget_code":" 이후의 콘텐츠를 수동으로 찾아 문자별로 이스케이프를 해제합니다. JSON 이 아직 완료되지 않았기 때문에 JSON.parse 를 사용할 수 없습니다. 닫히지 않은 <script> 태그가 감지되면 <script 이전에서 잘라내어 JavaScript 코드가 표시되는 텍스트로 나타나는 것을 방지합니다. 120ms 디바운스는 iframe 이 너무 자주 업데이트되는 것을 방지합니다. 스트리밍 콘텐츠는 모든 스크립트와 이벤트 핸들러를 제거합니다. 미리보기 단계에서는 상호 작용이 필요하지 않습니다.  **경험 다듬기: 눈에 띄지 않아야 할 세부 사항** 코드나 구현 관점에서 보면 그렇게 복잡하지 않습니다. 복잡성은 경험을 다듬는 데 있습니다. 경험에 영향을 줄 수 있는 부분이 너무 많으며, 사용자는 이러한 세부 사항과 생성 과정을 눈치채지 못해야 합니다. 따라서 각 단계를 다르게 처리해야 합니다: 1. 스트리밍처럼 보이지 않는 세부 사항 2. 나타나지 말아야 할 콘텐츠  **텍스트 사라짐** 모델이 먼저 소개 텍스트("시각적으로 설명하겠습니다...")를 출력한 다음 위젯 펜스를 시작합니다. 펜스가 나타나자마자 앞의 텍스트가 갑자기 사라지고 위젯 렌더링이 완료된 후에야 다시 나타납니다. 이유는 parseAllShowWidgets() 가 일반 텍스트에 대해 빈 배열을 반환하기 때문입니다. 펜스가 막 나타나고 아직 닫히지 않았을 때, 펜스 앞의 텍스트가 이 함수에 전달되어 삭제됩니다. 수정: 펜스 앞의 텍스트에 완성된 위젯 펜스가 포함되어 있지 않으면, 파싱 함수를 우회하여 <MessageResponse> 로 직접 렌더링합니다. **높이 점프** 위젯 렌더링이 완료되는 순간 전체 채팅 영역이 흔들립니다. iframe 의 초기 높이는 0px 입니다. 콘텐츠가 처음으로 실제 높이를 보고할 때 400px+ 일 수 있으며, CSS 전환으로 인해 이 변경이 300ms 동안 발생하여 눈에 띄는 점프가 발생합니다. 수정: 첫 번째 높이 보고 중에 CSS 전환을 일시적으로 비활성화하여 높이가 즉시 제자리를 잡도록 합니다. 이후 높이 조정은 부드러운 전환을 사용합니다. **최종 확정 깜빡임** 위젯이 스트리밍 미리보기에서 최종 렌더링으로 전환될 때 콘텐츠가 깜빡입니다. 수신자 iframe 은 최종 확정 중에 root.innerHTML = html 로 전체 DOM 을 교체합니다. 새 콘텐츠와 이전 콘텐츠가 동일하더라도(순수 SVG 위젯), 브라우저는 한 프레임의 리페인트를 트리거합니다. 수정: 최종 확정 중에 먼저 새 HTML 을 임시 컨테이너로 파싱하고 스크립트 요소를 분리합니다. 시각적 HTML(스크립트 제외)을 현재 DOM 과 비교합니다. 동일하면 innerHTML 교체를 건너뛰고 실행을 위해 스크립트만 추가합니다. 순수 SVG 위젯은 리페인트 없는 최종 확정을 달성합니다. **스크롤 점프** 채팅이 자동으로 맨 아래로 스크롤되다가 갑자기 수백 픽셀 위로 점프했다가 다시 점프합니다. 스트리밍이 종료되면 StreamingMessage 컴포넌트가 마운트 해제되고 MessageItem 컴포넌트가 마운트됩니다. 이들은 완전히 다른 두 개의 React 컴포넌트이며, 내부 WidgetRenderer 가 파괴되고 재구축됩니다. 새 인스턴스의 iframe 높이는 0 부터 시작하므로 콘텐츠 영역 높이가 급격히 떨어지고, 이는 use-stick-to-bottom 에서 스크롤 조정을 트리거합니다. 수정: 모듈 수준 높이 캐싱. 위젯이 높이를 보고할 때마다 widgetCode 의 처음 200 자를 키로 사용하여 캐시에 기록됩니다. 새 WidgetRenderer 인스턴스는 useState 초기화 중에 캐시에서 높이를 읽어 iframe 이 올바른 높이에서 렌더링을 시작하므로 0→실제 전환이 제거됩니다. **스크립트 코드 누출** Chart.js 가 포함된 위젯이 로드될 때 하단에 대량의 JavaScript 코드 블록이 표시됩니다. 모델이 출력하는 <script> 태그는 문자별로 도착합니다. 여는 태그가 도착했지만 닫는 태그가 아직 도착하지 않은 경우, sanitizeForStreaming 이 여는 태그를 제거하지만 내부의 JavaScript 코드는 베어 텍스트 노드가 되어 표시되는 콘텐츠로 렌더링됩니다. 수정: StreamingMessage 에서 부분 코드를 추출한 후 마지막 <script 에 일치하는 </script> 가 있는지 확인합니다. 없으면 <script 위치에서 잘라냅니다. 위젯 가이드라인은 스크립트가 끝에 위치하도록 지정하므로 잘라내기가 시각적 콘텐츠에 영향을 미치지 않습니다. 잘라내는 동안 shimmer 마스크가 표시되고 상태 표시줄에 "시각화에 대화형 애니메이션을 추가하는 중"이라고 표시됩니다. **Iframe 준비 경쟁 조건** 드물게 위젯이 전혀 렌더링되지 않고 0px 높이로 유지됩니다. WidgetRenderer 는 useEffect 를 통해 메시지 이벤트 리스너를 등록합니다. 그러나 iframe 의 수신자 스크립트는 로드되자마자 widget:ready 를 보냅니다. iframe 이 React effect 보다 빠르게 로드되면 리스너가 등록되기 전에 widget:ready 가 전송되고 iframeReady 는 절대 true 가 되지 않습니다. 수정: iframe 요소에 onLoad 콜백을 폴백으로 추가합니다. onLoad 가 트리거되면 수신자 스크립트가 실행을 완료했음이 보장되므로 안정적인 준비 신호를 제공합니다. **React 컴포넌트 트리 안정성** 펜스가 닫히는 순간 위젯이 깜빡입니다. 두 가지 문제가 겹칩니다. 스트리밍 부분 위젯에는 React 키가 없고, 닫힌 후에는 key="w-0" 이 생깁니다. 키 변경으로 인해 리마운트가 발생합니다. 또한, shimmer 오버레이는 래핑 <div> 로 구현되어 컴포넌트 트리 구조를 변경하고 또 다른 리마운트를 유발했습니다. 수정: 부분 위젯에 안정적인 키(w-N, 여기서 N 은 최종 세그먼트 배열의 예상 위치)를 할당하여 닫힌 후의 키와 일치시킵니다. Shimmer 오버레이를 WidgetRenderer 내부로 이동하고 showOverlay prop 을 통해 제어합니다. 컴포넌트 트리는 전체적으로 <WidgetRenderer key="w-N"> 으로 유지됩니다. ## **마지막 생각** 생성형 UI 시스템 전체에서 어려운 부분은 "HTML 조각을 iframe 에서 실행하는 것"이 아닙니다. 그것은 간단합니다. 진정한 복잡성은 스트리밍, 컴포넌트 수명 주기 전환, 테마 변경과 같은 상태 전환 중에 해당 iframe 을 시각적으로 안정적으로 유지하는 데 있습니다. 모든 "깜빡임", "점프", "사라짐"은 React 의 재조정, 브라우저의 렌더링 파이프라인, postMessage` 의 타이밍을 이해해야 합니다. 최종 효과는 사용자가 마치 원래부터 거기에 있었던 것처럼 모델의 응답에 자연스럽게 차트와 다이어그램이 섞여 있는 것을 보는 것입니다. 오늘은 여기까지입니다. 도움이 되셨다면 좋아요를 눌러주시거나 필요로 할 친구들과 공유해 주세요.
Claude 가 새롭게 선보인 생성형 UI 인터랙션을 직접 구현해 보았습니다!
- 조회
- 265K
- 좋아요
- 446
- 리포스트
- 63
- 댓글
- 34
- 북마크
- 815
TL;DR
이 글은 Code Pilot 에서 Claude 의 생성형 UI를 재현하는 과정을 상세히 다룹니다. 샌드박스 iframe 렌더링, 스트리밍 JSON 파싱, 그리고 끊김 없는 매끄러운 사용자 경험을 보장하기 위한 UX 개선 등 기술적 난관들을 어떻게 해결했는지 설명합니다.
한국어 번역을 보는 중





