AI Chat <mv-ai-chat>
AI 채팅 인터페이스: 캐럿이 있는 스트리밍 응답, 반짝이는 “thinking” 상태, 안전한 Markdown 렌더링(목록, 굵게, 복사 버튼이 있는 코드), 첨부 파일을 지원하며 자동으로 커지는 입력창, 모델 선택기, 제안, 중지 / 다시 생성, 사용자가 스크롤하면 물러나는 자동 스크롤.
일시 정지 재생
카테고리 블록 유형 Web Component (<mv-ai-chat>) 상태 안정 함께 설치되는 항목 button , kbd , shimmer-text , textarea Keywords ai, chat, assistant, llm, streaming, markdown, composer, conversation
When to use
A product needs a full assistant conversation with streamed replies, a thinking state and stop / regenerate Users must attach files, pick a model or start from suggested prompts in one composer Assistant replies contain Markdown lists and code blocks that users will want to copy
Avoid when
Only a single request is typed and its outcome, constraints and limits must be made explicit → use Intent Composer instead The need is a quick search or command launcher rather than a conversation → use Command instead
설치
node scripts/add.mjs ai-chat --out ./src/marvelous
Marvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["ai-chat"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, components/button/button.css, components/kbd/kbd.css, components/shimmer-text/shimmer-text.css, core/dom.js, core/element.js, components/textarea/textarea.css, components/textarea/char-count.js, core/motion.js, components/ai-chat/ai-chat.js, components/ai-chat/ai-chat.css.
사용법
기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:
<mv-ai-chat id="aic-demo" label="Solen" model="solen-pro" style="height:680px;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);overflow:hidden">
<header class="mv-ai-chat-header">
<mv-menu placement="bottom-start">
<button class="mv-button mv-ai-chat-model" data-variant="ghost" data-size="sm" type="button">
<span data-ai-chat-model-label>Solen Pro</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div data-content style="--mv-menu-width:17rem">
<div class="mv-menu-label">Model</div>
<div role="group" data-name="model">
<button role="menuitemradio" value="solen-pro" data-label="Solen Pro" aria-checked="true"><span>Solen Pro<span class="mv-ai-chat-menu-desc">Deep reasoning</span></span></button>
<button role="menuitemradio" value="solen-swift" data-label="Solen Swift"><span>Solen Swift<span class="mv-ai-chat-menu-desc">Fast everyday answers</span></span></button>
<button role="menuitemradio" value="solen-code" data-label="Solen Code"><span>Solen Code<span class="mv-ai-chat-menu-desc">Built for coding</span></span></button>
</div>
</div>
</mv-menu>
<div class="mv-ai-chat-header-end">
<button class="mv-button" data-variant="ghost" data-size="sm" type="button" data-ai-chat-reset>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.4 3.6a2 2 0 0 1 2.9 2.9L7 18.8 3 20l1.2-4z"/></svg>
<span class="mv-ai-chat-header-hide-sm">New chat</span>
</button>
</div>
</header>
<div class="mv-ai-chat-empty">
<span class="mv-ai-chat-empty-mark" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.5c.4 3.9 1.9 6.6 4.2 8 1.3.8 2.9 1.3 4.8 1.5-3.9.4-6.6 1.9-8 4.2-.8 1.3-1.3 2.9-1.5 4.8-.4-3.9-1.9-6.6-4.2-8-1.3-.8-2.9-1.3-4.8-1.5 3.9-.4 6.6-1.9 8-4.2.8-1.3 1.3-2.9 1.5-4.8z"/></svg></span>
<h2>Hi Maya, ready to dive in?</h2>
<p>Ask a question, attach a file or start from a suggestion.</p>
<div class="mv-ai-chat-suggestions">
<button class="mv-ai-chat-suggestion" type="button" data-prompt="Write a follow-up email about Ms. Chen’s quote"><strong>Write a follow-up</strong><span>about Ms. Chen’s quote</span></button>
<button class="mv-ai-chat-suggestion" type="button" data-prompt="Summarize September sales"><strong>Summarize sales</strong><span>for September, in 5 points</span></button>
<button class="mv-ai-chat-suggestion" type="button" data-prompt="Explain Web Components with an example"><strong>Explain Web Components</strong><span>with a code example</span></button>
</div>
</div>
<form class="mv-ai-chat-composer">
<div class="mv-textarea-group">
<ul class="mv-ai-chat-attachments" aria-label="Attachments" hidden></ul>
<textarea class="mv-textarea" rows="1" placeholder="Message Solen…" aria-label="Message Solen" aria-keyshortcuts="Enter"></textarea>
<div class="mv-textarea-toolbar">
<label class="mv-button mv-ai-chat-attach" data-variant="ghost" data-size="icon" title="Attach a file">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 11-8.6 8.6a5.5 5.5 0 0 1-7.8-7.8l8.6-8.6a3.7 3.7 0 0 1 5.2 5.2l-8.6 8.6a1.8 1.8 0 0 1-2.6-2.6L15 7"/></svg>
<input type="file" multiple aria-label="Attach files">
</label>
<span class="mv-textarea-toolbar-spacer"></span>
<button class="mv-button" data-size="icon" type="submit" data-ai-chat-send aria-label="Send message">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
</button>
</div>
</div>
<p class="mv-ai-chat-hint"><kbd class="mv-kbd" data-size="sm">Enter</kbd> to send · <kbd class="mv-kbd" data-size="sm">Shift</kbd> + <kbd class="mv-kbd" data-size="sm">Enter</kbd> for a new line · Solen can make mistakes. Check important info.</p>
</form>
<script type="application/json">[
{ "role": "user", "content": "How do I build a Web Component that shows a counter?", "files": [{ "name": "counter-brief.pdf", "size": 184320 }] },
{ "role": "assistant", "content": "Here’s a minimal, dependency-free counter:\n\n```js\nclass MvCounter extends HTMLElement {\n #count = 0;\n connectedCallback() {\n this.innerHTML = `<button type=\"button\">0</button>`;\n this.querySelector(\"button\").addEventListener(\"click\", (e) => {\n e.currentTarget.textContent = ++this.#count;\n });\n }\n}\ncustomElements.define(\"mv-counter\", MvCounter);\n```\n\nA few best practices:\n\n- prefix the tag name (`mv-`) to avoid collisions\n- clean up your listeners in `disconnectedCallback()`\n- expose state through **attributes** to stay compatible with every framework" }
]</script>
</mv-ai-chat>
API
Attributes
Name 유형 Default Description labelstring AssistantAssistant name (accessible labels, announcements). modelstring Current model; synced with a radio menu data-name="model" and passed to the responder. user-labelstring YouAccessible name for the user’s messages. thinking-labelstring ThinkingText for the waiting state. data-statethinking | streaming Set while generating. data-emptyboolean Set when the conversation is empty.
Properties
Name 유형 Description responder(messages, { signal, model }) => AsyncIterable<string> | Promise<string> | string Response source. Default: local simulation (canned English replies, no network). Honor signal for stopping. messages{ role, content, files? }[] Reads/writes the history. Also settable via a child <script type="application/json">. busyboolean Read-only: a response is in progress.
Methods
Name Description send(text)Sends a message (with pending attachments) and generates the response. stop()Stops generating (also Escape in the field, or the send button while it reads Stop). regenerate()Replaces the last response. reset()Clears the conversation. renderMarkdown(source, element)Export: lightweight, safe Markdown rendering (textContent only).
Events
Name Description mv-sendCancelable. detail: { content, files }. mv-responseResponse finished. detail: { content, stopped }. mv-stop / mv-reset / mv-errorStop, reset, responder error (detail.error).
Content structure
Name Description .mv-ai-chat-headerFree-form header; [data-ai-chat-model-label] shows the selected model, [data-ai-chat-reset] clears the conversation. .mv-ai-chat-emptyEmpty state (hidden after the first message); any [data-prompt] sends its text. form.mv-ai-chat-composerComposer: textarea (Enter sends, Shift + Enter = new line), input[type=file], ul.mv-ai-chat-attachments, [data-ai-chat-send] button with two icons (send / stop).
CSS classes
Name Description mv-ai-chat-log / -thread / -message / -content / -code / -actionsGenerated parts (message: data-role user | assistant, data-pending). mv-ai-chat-suggestions / -suggestion / -empty-markSuggestion grid and empty-state badge. mv-ai-chat-chip / -attach / -hint / -model / -menu-descAttachment, file button, hint, model trigger and model description.
CSS variables
Name Default Description --mv-ai-chat-max-width46remReading width of the thread and composer. --mv-ai-chat-user-bgBackground of the user’s bubbles.
Accessibility
Thread with role=log (aria-busy while generating); each message is a named <article> (“You” / assistant name). Streaming isn’t read word by word: a role=status region announces “… is writing a reply”, then the final response (code is summarized). Send button: label toggles between Send / Stop. Focusable code blocks (horizontal scrolling by keyboard), named Copy / Regenerate buttons. Reduced motion: no typing animation or blinking caret.