@struggler/smart-ai-renderer
@struggler/smart-ai-renderer
Vue 3 renderer for Smart AI chat messages. It renders ordinary Markdown and the platform's fenced structured output, including cards, forms, tables, charts, reports, citations, and maps.
Install
For an existing Vue 3.5+ application:
npm install @struggler/smart-ai-renderer
For a new application, install the Vue peer dependency as well:
npm install vue@^3.5 @struggler/smart-ai-renderer
With pnpm:
pnpm add vue@^3.5 @struggler/smart-ai-renderer
The renderer installs its own runtime dependencies, including Naive UI, ECharts, marked, DOMPurify, highlight.js, jsonrepair, and Vue Router. Consumers do not need to install those packages separately. Vue is a peer dependency and must be provided by the host application.
Local example
cd packages/smart-ai-renderer
make install
make example
The example provides a gallery covering every registered component, editable Markdown/render JSON, live preview, theme switching, streaming-state switching, and a host event log.
Use
<script setup>
import { ChatMessageRenderer, configureRenderer } from "@struggler/smart-ai-renderer"
import "@struggler/smart-ai-renderer/style.css"
configureRenderer({
onOpenLink(url) {
// Return false to let the built-in smartai:// and HTTP handlers continue.
console.log("open", url)
return false
},
async amapKeyProvider() {
return { key: "YOUR_AMAP_KEY", security_code: "YOUR_SECURITY_CODE" }
},
})
function sendFollowUp(text) {
console.log("send to agent", text)
}
</script>
<template>
<ChatMessageRenderer
:content="message.content"
:streaming="message.streaming"
:theme="{ brand: '#087f5b', radiusMd: '6px' }"
@follow-up="sendFollowUp"
/>
</template>
Styling and themes
Use the theme prop for common design tokens:
<ChatMessageRenderer
:content="content"
:theme="{
brand: '#087f5b',
brandStrong: '#066649',
brandSoft: '#e6fcf5',
text: '#172033',
textMuted: '#667085',
surface: '#ffffff',
surfaceSunken: '#f6f8fa',
border: '#d9e2e8',
radiusSm: '4px',
radiusMd: '6px',
fontFamily: 'Inter, sans-serif'
}"
/>
For advanced customization, add a class and override the same CSS variables from the host stylesheet:
<ChatMessageRenderer class="finance-theme" :content="content" />
.finance-theme {
--c-brand: #0057b8;
--c-brand-strong: #003f87;
--c-brand-soft: #eaf2fb;
--c-brand-text: #004b9b;
--c-brand-border: #9fc3e8;
--c-text: #172033;
--c-text-muted: #667085;
--c-surface: #ffffff;
--c-surface-sunken: #f6f8fa;
--c-border: #d9e2e8;
--r-sm: 4px;
--r-md: 6px;
--sar-font-family: Inter, sans-serif;
}
The package stylesheet is required and should be imported once:
import "@struggler/smart-ai-renderer/style.css"
Use an external stylesheet
You can keep all renderer overrides in a separate CSS file. Import it after the package stylesheet so that your rules take precedence:
import "@struggler/smart-ai-renderer/style.css"
import "./smart-ai-theme.css"
Add a class to the renderer to keep the overrides scoped to this instance:
<ChatMessageRenderer
class="custom-renderer"
:content="content"
/>
/* smart-ai-theme.css */
.custom-renderer {
--c-brand: #087f5b;
--c-brand-strong: #066649;
--c-brand-soft: #e6fcf5;
--c-text: #172033;
--c-text-muted: #667085;
--c-surface: #ffffff;
--c-border: #d9e2e8;
--r-md: 4px;
}
.custom-renderer .rt-action {
min-height: 40px;
border-radius: 4px;
}
.custom-renderer .rt-subcard {
box-shadow: none;
}
Prefer CSS variables for colors, typography, spacing, and radii. Use internal classes only for targeted layout changes, because template markup may evolve between major versions.
Render protocol
The component accepts Markdown mixed with fenced render JSON:
这是普通 Markdown 正文。
```render
{
"type": "link_card",
"title": "查看办理页面",
"description": "打开业务办理入口",
"url": "https://example.com"
}
```
The package currently includes 33 registered templates across business cards, interactive controls, information displays, charts, tables, maps, and citations. Run the local example and open the All components tab to inspect every template with working sample data.
Location request template
location_request renders a user-consented geolocation card. It is useful when an agent asks for “near me”, “current location”, or “within N km” searches.
{
"type": "location_request",
"intro": "To search nearby services, we need your current location.",
"title": "Share current location",
"desc": "Only used for this nearby search.",
"button_text": "Allow location",
"purpose": "nearby_search",
"radius_km": 3,
"submit_action": "location_request.submit"
}
When the user clicks the button, the component calls the browser Geolocation API and emits a follow-up string with hidden comments:
已授权当前位置<!-- {"action":"location_request.submit","data":{"status":"granted","latitude":22.543096,"longitude":114.057865,"accuracy":35}} --><!-- smartai:location
{"status":"granted","latitude":22.543096,"longitude":114.057865,"accuracy":35}
-->
If the user denies permission, the browser does not support geolocation, or the request times out, the component still emits a structured status such as denied, unavailable, or timeout. The host should send this follow-up back to the agent so it can ask for a manual address fallback. The card uses the same submitted-state restore mechanism as other interactive templates: after refresh, it stays locked and shows the previous status when the paired follow-up comment is present. Browser geolocation requires HTTPS or localhost in most modern browsers.
Custom render templates
Register a Vue component in the host application, then let render JSON select it by type:
<script setup>
import StatusCard from "./StatusCard.vue"
import {
ChatMessageRenderer,
registerRenderTemplate,
} from "@struggler/smart-ai-renderer"
registerRenderTemplate("status_card", StatusCard, {
validate(data) {
if (!data.title) return "title is required"
if (!data.status) return "status is required"
return true
},
})
</script>
<template>
<ChatMessageRenderer :content="content" />
</template>
The custom component receives a data prop containing the complete render object. It can emit follow-up with a string, just like a built-in interactive template:
<script setup>
defineProps({ data: { type: Object, required: true } })
const emit = defineEmits(["follow-up"])
</script>
<template>
<section class="status-card">
<strong>{{ data.title }}</strong>
<span>{{ data.status }}</span>
<button @click="emit('follow-up', data.action)">继续</button>
</section>
</template>
The Agent can now return ordinary render JSON:
```render
{
"type": "status_card",
"title": "数据准备完成",
"status": "可生成",
"action": "请继续生成报告"
}
```
Available registry APIs:
import {
getRenderTemplateTypes,
hasRenderTemplate,
registerRenderTemplate,
registerRenderTemplates,
unregisterRenderTemplate,
} from "@struggler/smart-ai-renderer"
registerRenderTemplates({
status_card: StatusCard,
result_card: {
component: ResultCard,
validate: (data) => data.items?.length > 0 || "items is required",
},
})
hasRenderTemplate("status_card")
getRenderTemplateTypes()
unregisterRenderTemplate("status_card")
Duplicate types are rejected. Use { override: true } only when intentionally replacing an existing template. Unregistering an override restores the built-in component.
Registration is supported after mount and updates active renderers. Registering templates during application startup is still recommended so the first message never flashes the fallback.
Custom fallback
Use a host component when JSON has an unknown type, fails custom validation, or a template throws while rendering:
import { configureRenderer } from "@struggler/smart-ai-renderer"
import UnknownRenderCard from "./UnknownRenderCard.vue"
configureRenderer({ fallbackRenderer: UnknownRenderCard })
The fallback receives data, reason, and validationError props. Set fallbackRenderer: null to restore the built-in JSON fallback.
Security boundary
Render JSON is data only. It may select a registered type and provide fields consumed by that component, but it cannot contain executable code, import paths, component URLs, or arbitrary JavaScript. Components and validators must always be registered by trusted host code.
Host integration
onOpenLink(url): intercept HTTP orsmartai://navigation. Returnfalseto use the built-in handler.onUnsupportedLink(action, params, message): show a toast or log unsupported protocol actions.router: optional Vue Router-compatible object used by internal chat/back actions.webviewPagePath: host mini-program WebView page path.amapKeyProvider(): load the AMap key at runtime without embedding it in the npm package.fallbackRenderer: optional Vue component used when a render type is unknown or invalid.
Interactive templates emit follow-up text. The host decides how to send it to the Agent API.
Parser-only use
import { tryParseRenderable, splitMixedRenderable } from "@struggler/smart-ai-renderer"
Adding and distributing templates
Adding template metadata, registering a host-specific component, and publishing a new built-in SDK template are different workflows. Read TEMPLATE-DEVELOPMENT-GUIDE.md before changing the registry or publishing a new package version.
The guide includes the decision matrix, implementation paths, SemVer rules, release commands, consumer upgrade steps, security constraints, and completion checklists. It is included in the npm tarball so consuming teams and coding agents can follow the same process without access to the administration application source.
The npm tarball contains compiled assets, type declarations, styles, documentation, and the license. It does not include the Smart AI administration application source.
Release
make pack
make publish
Both commands use https://registry.npmjs.org/; make pack performs the same checks without publishing.
Related Packages
Vue 3 renderer adapter for ai-slot: render validated component trees as real Vue...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.3.3 | >=3.5.0 <4 | MIT | |
| 0.3.2 | >=3.5.0 <4 | MIT | |
| 0.3.1 | >=3.5.0 <4 | MIT | |
| 0.3.0 | >=3.5.0 <4 | MIT | |
| 0.2.0 | >=3.5.0 <4 | MIT | |
| 0.1.0 | >=3.5.0 <4 | MIT |