The main component for rendering AI-generated content. Automatically detects and renders artifacts including code blocks, forms, social previews, images, videos, and more.
Import
import { ArtifactuseAgentMessage } from 'artifactuse/vue'
import { ArtifactuseAgentMessage } from 'artifactuse/vue2'
import { ArtifactuseAgentMessage } from 'artifactuse/react'
import { ArtifactuseAgentMessage } from 'artifactuse/svelte'
Usage
<template>
<ArtifactuseAgentMessage
:content="message.content"
:message-id="message.id"
:typing="isStreaming"
:is-last-message="isLastMessage"
@artifact-detected="onDetected"
@artifact-open="onOpen"
@form-submit="onFormSubmit"
@form-cancel="onFormCancel"
@form-button-click="onFormButtonClick"
@social-copy="onSocialCopy"
@media-open="onMediaOpen"
/>
</template>
<ArtifactuseAgentMessage
content={message.content}
messageId={message.id}
typing={isStreaming}
isLastMessage={isLastMessage}
onArtifactDetected={onDetected}
onArtifactOpen={onOpen}
onFormSubmit={onFormSubmit}
onFormCancel={onFormCancel}
onFormButtonClick={onFormButtonClick}
onSocialCopy={onSocialCopy}
onMediaOpen={onMediaOpen}
/>
<ArtifactuseAgentMessage
content={message.content}
messageId={message.id}
typing={isStreaming}
isLastMessage={isLastMessage}
on:artifact-detected={onDetected}
on:artifact-open={onOpen}
on:form-submit={onFormSubmit}
on:form-cancel={onFormCancel}
on:form-button-click={onFormButtonClick}
on:social-copy={onSocialCopy}
on:media-open={onMediaOpen}
/>
Props
| Prop | Type | Default | Description |
|---|
content | string | Required | Raw message content from AI (Markdown, code blocks, JSON artifacts) |
messageId | string | Required | Unique identifier for the message |
typing | boolean | false | Show typing indicator (use while streaming) |
inlineCards | boolean | true | Show clickable cards for panel artifacts inline. Also configurable globally. |
isLastMessage | boolean | false | Whether this is the last message in the conversation. Keeps forms active after page reload. |
inlinePreview | object | null | Override global inlinePreview config for this message |
inlineCode | object | null | Override global inlineCode config for this message |
tabs | string[] | null | Override visible panel tabs for artifacts from this message |
viewMode | string | null | Override initial panel view mode for artifacts from this message |
Events
| Event | Payload | Description |
|---|
artifact-detected | Artifact[] | Fired when artifacts are detected in content |
artifact-open | Artifact | Fired when user clicks an artifact card to open panel |
form-submit | { formId, action, values, timestamp } | Fired when an inline form is submitted |
form-cancel | { formId, action, buttonName, timestamp } | Fired when an inline form is cancelled |
form-button-click | { formId, action, buttonName, buttonLabel, values, timestamp } | Fired when a custom button is clicked |
social-copy | { platform, text } | Fired when social preview text is copied |
media-open | { type, src, alt, caption } | Fired when image/PDF is opened in lightbox viewer |
Event names use kebab-case in Vue (@form-submit) and camelCase in React (onFormSubmit). Svelte uses kebab-case with on: prefix (on:form-submit).
Artifact Rendering
The component automatically renders different artifact types:
| Artifact Type | Rendering |
|---|
code (HTML, React, Vue, etc.) | Clickable card → Opens in Panel |
form with display: "inline" | Inline form directly in message |
form with display: "panel" | Clickable card → Opens in Panel |
social | Inline social media preview |
| Images | Inline with lightbox on click |
| Videos | Inline embed (YouTube, Vimeo, etc.) |
| Other embeds | Inline (maps, documents, etc.) |
Inline Code Preview
By default, code artifacts render as clickable cards. Enable inlinePreview in your SDK config to show truncated syntax-highlighted code previews directly in the message:
// Global config
provideArtifactuse({
inlinePreview: {
maxLines: 15,
languages: ['smartdiff', 'html', 'javascript', 'jsx'],
},
})
<!-- Or per-message override -->
<ArtifactuseAgentMessage
:content="msg.content"
:message-id="msg.id"
:inline-preview="{ maxLines: 10, languages: ['html'] }"
/>
| Option | Type | Default | Description |
|---|
maxLines | number | 15 | Max lines to show before truncation |
languages | string[] or true | [] | Languages to show inline preview for, or true for all |
excludeLanguages | string[] | [] | Languages to exclude from inline preview (used with languages: true) |
minClickableLines | number or object | null | Disable clicking for short code blocks. Number shorthand or { lines, ignoreLanguages } |
actionLabel | string or object | null | Customize the fade overlay label. String shorthand or { langKey: label, default: label } |
Clicking a truncated preview opens the full artifact in the panel. Languages not listed still render as cards.
Inline code preview requires Prism.js for syntax highlighting. Load Prism and language grammars via CDN or npm.
Exclude Languages
When using languages: true, exclude specific languages from inline preview with excludeLanguages. Excluded languages fall back to artifact cards:
provideArtifactuse({
inlinePreview: {
languages: true,
excludeLanguages: ['typescript', 'go', 'rust'],
},
})
This is only meaningful when languages: true. When languages is an array, simply omit the language from the array instead.
Non-Clickable Short Code
When code is shorter than maxLines, the full code is visible — clicking opens a panel that shows the same content. Use minClickableLines to disable clicking for these short blocks:
provideArtifactuse({
inlinePreview: {
maxLines: 15,
languages: ['html', 'javascript', 'python'],
minClickableLines: 10, // short code (< 10 lines) is non-clickable
},
})
For languages where the panel adds value (e.g., HTML renders a live preview), use ignoreLanguages to keep them clickable:
minClickableLines: {
lines: 10,
ignoreLanguages: ['html', 'markdown', 'jsx', 'vue'],
},
Custom Action Labels
Customize the fade overlay label text per language:
provideArtifactuse({
inlinePreview: {
maxLines: 15,
languages: ['html', 'javascript', 'markdown'],
actionLabel: {
html: 'Open preview',
markdown: 'Open document',
default: 'View full code', // fallback for unlisted languages
},
},
})
String shorthand applies to all languages:
actionLabel: 'Click to expand',
The (N lines) suffix is always appended automatically (e.g., “Open preview (24 lines)”).
Inline Code
To show full syntax-highlighted code directly in the message without extraction or panel, enable inlineCode:
// Global config
provideArtifactuse({
inlineCode: {
languages: ['css', 'bash', 'sql'], // or true for all
},
})
<!-- Or per-message override -->
<ArtifactuseAgentMessage
:content="msg.content"
:message-id="msg.id"
:inline-code="{ languages: true }"
/>
| Option | Type | Default | Description |
|---|
languages | string[] or true | [] | Languages to show inline, or true for all |
Code blocks for listed languages stay as normal <pre><code> blocks with Prism.js syntax highlighting. No artifact card, no panel, no extraction.
When a language matches both inlineCode and inlinePreview, inlineCode takes priority (no extraction).
Tabs & View Mode
Control which panel tabs are visible and the initial view mode, globally or per-message:
// Global config
provideArtifactuse({
tabs: ['preview', 'code'],
viewMode: 'code',
})
<!-- Or per-message override -->
<ArtifactuseAgentMessage
:content="msg.content"
:message-id="msg.id"
:tabs="['code']"
view-mode="code"
/>
Available tabs: 'preview', 'code', 'split', 'edit'. Available view modes match the tab names.
Config Precedence
All options support both global config and component prop levels. Precedence:
Component prop → Global config → Default
// Global: all messages get code-only tabs
provideArtifactuse({ tabs: ['preview', 'code'] })
// Override: this specific message gets all tabs
<ArtifactuseAgentMessage :tabs="['preview', 'code', 'split', 'edit']" />
Inline forms automatically collapse after user interaction to prevent duplicate submissions and keep the chat clean.
| State | Description | Visual |
|---|
active | Interactive, user can fill and submit | Full form with fields |
submitted | User clicked submit or action button | Collapsed with ✓ checkmark |
cancelled | User clicked cancel | Collapsed with ✗ icon |
inactive | Historical form (page refresh) | Collapsed with — dash |
Behavior Rules
- Current session: Forms stay active until user interacts
- After submit/cancel/custom action: Form collapses immediately
- After page refresh:
- Last message forms stay active (via
isLastMessage prop)
- Older message forms collapse as inactive
- Reset action: Form stays active (doesn’t collapse)
Example with isLastMessage
<template>
<ArtifactuseAgentMessage
v-for="(msg, index) in messages"
:key="msg.id"
:content="msg.content"
:message-id="msg.id"
:is-last-message="index === messages.length - 1"
@form-submit="handleFormSubmit"
/>
</template>
Images and PDFs automatically open in a fullscreen lightbox viewer when clicked. The viewer supports:
- Zoom - Click image or zoom button to toggle zoom
- Download - Download the original file
- Keyboard - Press
Escape to close
- Click outside - Click overlay to close
<ArtifactuseAgentMessage
:content="content"
:message-id="id"
@media-open="({ type, src }) => console.log('Opened:', type, src)"
/>
Typing Indicator
Show a typing indicator while streaming AI responses:
<ArtifactuseAgentMessage
:content="partialContent"
:message-id="id"
:typing="isStreaming"
/>
The typing indicator displays animated loading bars that disappear when typing becomes false.
Example: Full Integration
<template>
<div class="chat">
<ArtifactuseAgentMessage
v-for="(msg, index) in messages"
:key="msg.id"
:content="msg.content"
:message-id="msg.id"
:typing="msg.isStreaming"
:is-last-message="index === messages.length - 1"
@form-submit="handleFormSubmit"
@form-cancel="handleFormCancel"
@form-button-click="handleFormButtonClick"
@social-copy="handleSocialCopy"
@media-open="handleMediaOpen"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ArtifactuseAgentMessage } from 'artifactuse/vue'
const messages = ref([])
function handleFormSubmit({ formId, values }) {
// Send form data back to AI
sendToAI(`User submitted form: ${JSON.stringify(values)}`)
}
function handleFormCancel({ formId }) {
// User cancelled the form
console.log('Form cancelled:', formId)
}
function handleFormButtonClick({ formId, action, buttonName, values }) {
// Handle custom button actions
console.log('Button clicked:', buttonName, action)
}
function handleSocialCopy({ platform, text }) {
// Track analytics
analytics.track('social_copy', { platform })
}
function handleMediaOpen({ type, src }) {
// Track media views
analytics.track('media_view', { type, src })
}
</script>
import { useState } from 'react'
import { ArtifactuseAgentMessage } from 'artifactuse/react'
function Chat() {
const [messages, setMessages] = useState([])
const handleFormSubmit = ({ formId, values }) => {
// Send form data back to AI
sendToAI(`User submitted form: ${JSON.stringify(values)}`)
}
const handleFormCancel = ({ formId }) => {
// User cancelled the form
console.log('Form cancelled:', formId)
}
const handleFormButtonClick = ({ formId, action, buttonName, values }) => {
// Handle custom button actions
console.log('Button clicked:', buttonName, action)
}
const handleSocialCopy = ({ platform, text }) => {
// Track analytics
analytics.track('social_copy', { platform })
}
const handleMediaOpen = ({ type, src }) => {
// Track media views
analytics.track('media_view', { type, src })
}
return (
<div className="chat">
{messages.map((msg, index) => (
<ArtifactuseAgentMessage
key={msg.id}
content={msg.content}
messageId={msg.id}
typing={msg.isStreaming}
isLastMessage={index === messages.length - 1}
onFormSubmit={handleFormSubmit}
onFormCancel={handleFormCancel}
onFormButtonClick={handleFormButtonClick}
onSocialCopy={handleSocialCopy}
onMediaOpen={handleMediaOpen}
/>
))}
</div>
)
}