Skip to content

自定义组件开发

自定义组件是 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:

属性类型说明
blockStreamBlockData当前区块的完整结构化数据
attrsRecord<string, string | boolean>标签属性
contentstring标签内文本
isClosedboolean标签是否已经闭合
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.md

CLI 会扫描组件目录,合并 @stream-ui 注释和 defineProps 类型,生成可放入 system message 的标签说明。

使用 CLI 创建模板

你也可以用 CLI 生成一个可编辑的自定义组件模板:

bash
npx stream-ui create user-profile-card

生成的 .vue 文件已经包含:

  • @stream-ui 元数据注释
  • 预设的 blockattrscontentisClosedreportData props
  • 一份本地的轻量 StreamBlockComponentProps 类型
  • 基础样式和流式状态展示

生成后把组件放进 <StreamContains> 默认插槽即可注册。

Released under the MIT License.