본문으로 건너뛰기
채팅 · AI / agent-executor

AgentExecutor

Phase 8 AI-first 컴포지트 — 채팅 셸(AgentExecutor) + 점진 테이블(StreamingTable) + 멀티모달 입력(MultiModalChatInput).
채팅 · AIagent-executorStatic specimenagent에이전트agent executorchat shell
01 · Specimen

먼저 보고, 그다음 계약을 읽습니다

Phase 8 AI-first 컴포지트 — 채팅 셸(AgentExecutor) + 점진 테이블(StreamingTable) + 멀티모달 입력(MultiModalChatInput).

Workbench 불러오는 중…
Component intent의도와 경계 읽기

Phase 1-7 primitive(ChatInput / MessageBubble / StreamingMarkdown / ToolCallCard / Spinner / Button) 위에 얹는 high-level 조합 3종을 한 모듈로 export 한다. AgentExecutor 는 controlled 채팅 셸(messages 는 부모 보유, onSubmit 으로 새 사용자 메시지)이며 assistant 메시지는 StreamingMarkdown, 인라인 tool 은 ToolCallCard 로 렌더하고 sticky-bottom 자동 스크롤을 한다. StreamingTable 은 row 를 하나씩 append 하는 제너릭 테이블(진행 bar·pending spinner 행), MultiModalChatInput 은 ChatInput 을 첨부(drag/drop·paste·file picker)로 확장한 입력이다. 셋 다 barrel 최상위에서 직접 export 된다.

02 · Use

예제

SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.

01AgentExecutor — 기본 채팅 셸tsxfragment
AgentExecutor — 기본 채팅 셸
import { AgentExecutor, type ChatMessage } from "@axe/ui";const [messages, setMessages] = useState<ChatMessage[]>([]);<AgentExecutor  messages={messages}  streaming={pending}  onCancel={abort}  placeholder="무엇이든 물어보세요"  onSubmit={(text) => send(text)}/>
02AgentExecutor — 인라인 tool call + 멀티모달 입력tsxfragment
AgentExecutor — 인라인 tool call + 멀티모달 입력
import { AgentExecutor, MultiModalChatInput, type ChatMessage } from "@axe/ui";const messages: ChatMessage[] = [  {    id: "a1",    role: "assistant",    content: "장부를 조회했습니다.",    tools: [{ id: "t1", name: "query_trial_balance", status: "success", output: "…" }],  },];<AgentExecutor  messages={messages}  onSubmit={(text, files) => send(text, files)}  inputSlot={    <MultiModalChatInput      accept="image/*,application/pdf"      maxFiles={3}      onSubmit={(text, files) => send(text, files)}      onReject={(reason, f) => toast(`${f.name}: ${reason}`)}    />  }/>
03StreamingTable — 행 점진 appendtsxfragment
StreamingTable — 행 점진 append
import { StreamingTable } from "@axe/ui";<StreamingTable  columns={[    { key: "name", header: "종목" },    { key: "irr", header: "IRR", align: "right" },  ]}  rows={rows}  streaming={pending}  expectedRowCount={20}  getRowKey={(r) => r.id}/>
04StreamingTable — CSS-only 정적 (비-React)htmlfragment
StreamingTable — CSS-only 정적 (비-React)
<div class="axe-streaming-table">  <div class="axe-streaming-table__scroll">    <table class="axe-streaming-table__table">      <thead class="axe-streaming-table__thead">        <tr><th class="axe-streaming-table__th">종목</th>            <th class="axe-streaming-table__th axe-streaming-table__th--right">IRR</th></tr>      </thead>      <tbody>        <tr class="axe-streaming-table__tr">          <td class="axe-streaming-table__td">A사</td>          <td class="axe-streaming-table__td axe-streaming-table__td--right">18.4%</td>        </tr>      </tbody>    </table>  </div></div>
03 · React

Props

TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.

이름타입필수기본값설명
messagesChatMessage[]필수AgentExecutor: 렌더할 메시지 배열. ChatMessage = { id, role, content, tools?, attachments? }.
onSubmit(text: string, attachments?: File[]) => void필수AgentExecutor: 사용자 제출 콜백. attachments 는 inputSlot 으로 MultiModalChatInput 을 쓸 때만.
onCancel() => voidAgentExecutor: 주어지면 streaming=true 일 때 streaming-hint 에 '취소' 버튼 노출.
streamingbooleanfalseAgentExecutor: 마지막 assistant 메시지 끝 cursor + 하단 '응답 생성 중…' hint.
toolsToolCallEvent[]AgentExecutor: 메시지에 속하지 않는 free-floating tool 카드(보통 message.tools 권장).
placeholderstringAgentExecutor: 기본 ChatInput 의 placeholder(inputSlot 미사용 시).
disabledbooleanAgentExecutor: 기본 ChatInput 비활성(보통 streaming 중 true).
inputRefReact.Ref<ChatInputHandle>AgentExecutor: 기본 ChatInput 의 외부 ref(focus/clear).
inputSlotReact.ReactNodeAgentExecutor: 입력 자리에 넣을 커스텀 노드 — 지정 시 기본 ChatInput 대체(예: MultiModalChatInput).
ref → AgentExecutorHandle{ focus(): void; scrollToBottom(): void }AgentExecutor: imperative 핸들 — 입력 focus·강제 맨아래 스크롤.
StreamingTable.columnsStreamingTableColumn<R>[]필수StreamingTable: 열 정의. { key, header, render?, align?('left'|'right'|'center'), width? }.
StreamingTable.rowsR[]필수StreamingTable: 현재까지의 행(추가되면 페이드인).
StreamingTable.getRowKey(row: R, idx: number) => string필수StreamingTable: 안정적 row key.
StreamingTable.streamingbooleanfalseStreamingTable: 마지막에 spinner pending 행 노출(다음 행 대기).
StreamingTable.expectedRowCountnumberStreamingTable: 총 행 수를 알면 상단 progress bar(0..100%).
StreamingTable.emptyLabelReact.ReactNode"—"StreamingTable: 행 0 & 비스트리밍 시 placeholder.
MultiModalChatInput.onSubmit(text: string, attachments: File[]) => voidMultiModalChatInput: 텍스트+첨부 제출(ChatInput 과 다른 시그니처 — 그래서 별도 컴포넌트).
MultiModalChatInput.acceptstringMultiModalChatInput: accept 패턴(예 'image/*,application/pdf'). '.pdf'/'image/*'/'application/pdf' 모두 매칭.
MultiModalChatInput.maxFilesnumber5MultiModalChatInput: 최대 첨부 개수(초과 시 onReject('count')).
MultiModalChatInput.maxSizeBytesnumber20 * 1024 * 1024MultiModalChatInput: 파일당 최대 크기(초과 시 onReject('size')).
MultiModalChatInput.onReject(reason: "count" | "size" | "type", file: File) => voidMultiModalChatInput: 거부 콜백(toast 등).
MultiModalChatInput.toolbarLeft / toolbarRight / hintReact.ReactNodeMultiModalChatInput: 툴바 슬롯·힌트(hint 기본 '↵ 보내기 · ⇧↵ 줄바꿈 · 끌어 놓기 / 붙여넣기로 첨부').
MultiModalChatInput.ref → ChatInputHandle{ focus(): void; clear(): void }MultiModalChatInput: imperative 핸들(clear 는 텍스트+첨부 모두 비움).
04 · Any stack

.axe-* 클래스 계약

React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.

클래스안정성용도
.axe-agent-executorstableAgentExecutor 루트(세로 flex, 높이 100%, 테두리 카드).
.axe-agent-executor__scrollstable메시지 스크롤 영역(reduced-motion 시 smooth 해제).
.axe-agent-executor__emptystablemessages 0 일 때 '대화를 시작하세요' 빈 상태.
.axe-agent-executor__user-contentstable사용자 메시지 본문(pre-wrap).
.axe-agent-executor__toolsstable메시지 내부 ToolCallCard 목록 래퍼. --free 변형 = free-floating tools.
.axe-agent-executor__attachmentsstable사용자 메시지의 첨부 chip 행.
.axe-agent-executor__streaming-hintstable하단 '응답 생성 중…' 상태 바(Spinner + 선택 취소 버튼).
.axe-agent-executor__inputstable입력 도크(상단 구분선, ChatInput 또는 inputSlot).
.axe-chat-input--dragoverstableMultiModal: drag-over 하이라이트(accent 테두리·ring).
.axe-chat-input--disabledstableMultiModal: 비활성(opacity·pointer-events none).
.axe-chat-input__drop-overlaystableMultiModal: '끌어 놓아 첨부' 드롭 오버레이(aria-hidden).
.axe-attach-liststable첨부 chip 목록(입력 위).
.axe-attach-chipstable첨부 chip. __thumb(이미지 썸네일)/__icon(비이미지)/__name/__size/__remove 파트. --compact = AgentExecutor 사용자 메시지용 mini.
.axe-attach-buttonstable'📎 첨부' 파일 피커 버튼(maxFiles 도달 시 disabled).
.axe-attach-file-inputinternal숨긴 <input type=file>(display:none).
.axe-streaming-tablestableStreamingTable 루트(elevated 카드). data-streaming 속성.
.axe-streaming-table__progressstable상단 progress 트랙. __progress-bar 는 --axe-progress(0..1) scaleX 로 채움.
.axe-streaming-table__scrollstable가로 스크롤 래퍼(min-width 480px 테이블).
.axe-streaming-table__tablestable테이블 본체(<table>).
.axe-streaming-table__thstable헤더 셀. --right/--center 정렬 변형.
.axe-streaming-table__tdstable데이터 셀(tabular-nums). --right/--center 정렬 변형.
.axe-streaming-table__tr--ininternal새 행 페이드인(axe-stream-row-in, reduced-motion 시 off).
.axe-streaming-table__pendingstable'다음 행 대기 중…' spinner 행(streaming 시).
.axe-streaming-table__emptystable행 0 & 비스트리밍 시 emptyLabel 셀.
비-React 소비 노트
StreamingTable 은 시맨틱 <table> 마크업이라 서버 렌더(maud/jinja)로도 그대로 재현 가능하고, progress 는 __progress-bar 에 --axe-progress(0..1)만 주면 된다(구 inline width:% 도 하위호환). AgentExecutor·MultiModalChatInput 은 드래그·붙여넣기·자동 스크롤·첨부 상태가 모두 JS 이므로 CSS-only 로는 정적 셸/입력 스냅샷만 재현된다. 이 모듈 스타일(agent.css)은 아직 components.css 에 통합되지 않아 소비 측에서 명시적으로 import 해야 한다.
05 · Inclusive

접근성

키보드, ARIA, 구현 노트를 함께 검토합니다.

Keyboard
  • AgentExecutor: 기본 ChatInput 은 ↵ 보내기 · ⇧↵ 줄바꿈
  • MultiModalChatInput: ↵ 보내기 · ⇧↵ 줄바꿈(IME 조합 중이면 submit 억제) · 붙여넣기/끌어 놓기로 첨부
  • MultiModalChatInput: 각 chip 의 제거 버튼(aria-label "<파일명> 제거")로 삭제
ARIA

AgentExecutor 빈 상태는 aria-live="polite", 하단 streaming-hint 는 role="status" aria-live="polite"(Spinner label="Streaming")로 진행을 고지한다. StreamingTable 은 시맨틱 <table> 이고 pending 행이 aria-live="polite" 로 '다음 행 대기 중' 을 알리지만, 상단 progress bar 는 aria-hidden 장식이라 수치 진행은 시각으로만 전달된다. MultiModalChatInput 의 파일 버튼은 aria-label="파일 첨부"(maxFiles 도달 시 disabled), 드롭 오버레이는 aria-hidden 이다.

Notes

AgentExecutor 의 자동 스크롤은 rAF 후 scrollTop 이동이며 사용자가 바닥에서 32px 이상 위로 스크롤하면 stick 해제된다(read 방해 없음). scroll-behavior 는 prefers-reduced-motion 에서 auto 로, StreamingTable 의 행 페이드인·progress transition 도 reduced-motion 에서 정지된다. MultiModal 의 dragenter/leave 는 중첩 카운팅으로 자식 위 호버 시 깜빡임을 막는다.

06 · Judgment

권장 · 지양

권장
  • tool call 은 되도록 해당 message.tools 에 넣는다 — free-floating tools prop 은 pre-warm 등 예외용.
  • streaming 중에는 disabled 로 기본 ChatInput 을 잠그거나 onCancel 을 제공해 4-state 를 완성한다.
  • MultiModalChatInput 은 onSubmit 시그니처(text, files)가 ChatInput 과 다름을 인지하고 호출 측을 맞춘다.
지양
  • messages 를 컴포넌트 내부에서 변형하려 하지 말 것 — controlled(부모 소유)다.
  • StreamingTable 의 progress 를 스크린리더 진행 고지로 기대하지 말 것 — bar 는 aria-hidden 시각 전용.
  • AgentExecutorHandle(focus/scrollToBottom)과 ChatInputHandle(focus/clear)을 혼동하지 말 것 — 서로 다른 ref.
검색창을 열면 컴포넌트 인덱스를 불러옵니다.