自定义组件开发
自定义组件是 Stream UI 的主要扩展方式。你可以把模型输出中的自定义标签映射成 Vue 组件,并通过结构化数据接收交互结果。
创建组件
组件名会自动映射为标签名。比如 UserProfileCard 会匹配 <user-profile-card>。
vue
<!-- UserProfileCard.vue -->
<template>
<section class="user-profile-card" :data-closed="isClosed">
<header>{{ attrs?.name || '用户资料' }}</header>
<div class="body">
<slot>{{ content }}</slot>
</div>
</section>
</template>
<script setup lang="ts">
import type { StreamBlockComponentProps } from '@huiol/stream-ui'
defineOptions({
name: 'UserProfileCard'
})
interface UserProfileCardProps extends StreamBlockComponentProps {
attrs?: {
id?: string
name?: string
}
}
defineProps<UserProfileCardProps>()
</script>StreamBlockComponentProps 包含拦截组件常用的 props:
| 属性 | 类型 | 说明 |
|---|---|---|
block | StreamBlockData | 当前区块的完整结构化数据 |
attrs | Record<string, string | boolean> | 标签属性 |
content | string | 标签内文本 |
isClosed | boolean | 标签是否已经闭合 |
reportData | (payload: unknown) => void | 回传交互数据 |
如果你使用 defineComponent 写法,也可以复用运行时 props:
ts
import { defineComponent, h } from 'vue'
import { streamBlockProps } from '@huiol/stream-ui'
export default defineComponent({
name: 'UserProfileCard',
props: streamBlockProps,
setup(props) {
return () => h('section', props.content)
}
})注册组件
推荐把组件放在 <StreamContains> 默认插槽中注册。这种方式最符合 Vue 的组件组合习惯。
vue
<script setup lang="ts">
import { StreamContains } from '@huiol/stream-ui'
import UserProfileCard from './UserProfileCard.vue'
const text = '<user-profile-card id="user-1" name="Ada">核心开发者</user-profile-card>'
</script>
<template>
<StreamContains :model-value="text" mode="accurate">
<UserProfileCard />
</StreamContains>
</template>也可以通过 components 显式注册,适合组件来自配置对象、自动导入或运行时组合的场景。
vue
<script setup lang="ts">
import UserProfileCard from './UserProfileCard.vue'
const components = {
'user-profile-card': UserProfileCard
}
</script>
<template>
<StreamContains :model-value="text" :components="components" />
</template>默认插槽和 components 会合并;同名时 components 显式注册优先。
稳定区块 ID
标签上的字符串 id 属性会优先作为 StreamBlockData.id。
html
<user-profile-card id="user-1" name="Ada">核心开发者</user-profile-card>这对交互组件尤其重要:当流式内容前面插入新文本或新标签时,已有的 payload 仍能按稳定 ID 保留。没有字符串 id 时,Stream UI 会使用自动生成的区块 ID。
回传交互数据
组件可以调用 reportData 更新当前区块的 payload。父组件通过 v-model:data 接收最新结构化数据。
vue
<template>
<button :disabled="!isClosed" @click="confirm">
{{ attrs?.label || '确认' }}
</button>
</template>
<script setup lang="ts">
import type { StreamBlockComponentProps } from '@huiol/stream-ui'
const props = defineProps<StreamBlockComponentProps>()
const confirm = () => {
props.reportData?.({
confirmed: true,
at: Date.now()
})
}
</script>vue
<script setup lang="ts">
import { ref } from 'vue'
import { StreamContains, type StreamBlockData } from '@huiol/stream-ui'
import ConfirmBlock from './ConfirmBlock.vue'
const text = '<confirm-block id="confirm-profile" label="确认资料" />'
const blocks = ref<StreamBlockData[]>([])
</script>
<template>
<StreamContains v-model:data="blocks" :model-value="text">
<ConfirmBlock />
</StreamContains>
</template>写给模型的标签协议
如果你希望 stream-ui prompt 自动生成标签协议,请在组件中添加 @stream-ui 元数据注释。
vue
<!--
@stream-ui
tag: user-profile-card
description: Render a user profile summary card.
attrs:
id: Stable block id for preserving payload
name: Display name
content: Short profile summary shown in the card body.
example: <user-profile-card id="user-1" name="Ada">核心开发者</user-profile-card>
-->然后运行:
bash
npx stream-ui prompt --out stream-ui.prompt.mdCLI 会扫描组件目录,合并 @stream-ui 注释和 defineProps 类型,生成可放入 system message 的标签说明。
使用 CLI 创建模板
你也可以用 CLI 生成一个可编辑的自定义组件模板:
bash
npx stream-ui create user-profile-card生成的 .vue 文件已经包含:
@stream-ui元数据注释- 预设的
block、attrs、content、isClosed、reportDataprops - 一份本地的轻量
StreamBlockComponentProps类型 - 基础样式和流式状态展示
生成后把组件放进 <StreamContains> 默认插槽即可注册。