@fses/ai-chat
v0.2.15
Published
基于 Vue 3 的 AI 对话组件,支持 SSE 流式响应、多轮会话管理、Markdown 渲染、工具调用状态展示和结构化图表卡片渲染。
Readme
@fses/ai-chat
基于 Vue 3 的 AI 对话组件,支持 SSE 流式响应、多轮会话管理、Markdown 渲染、工具调用状态展示和结构化图表卡片渲染。
安装
pnpm add @fses/ai-chat对等依赖
宿主项目需要提供以下依赖:
pnpm add vue@^3.5.32 ant-design-vue@^4.2.6 @ant-design/icons-vue@^6.0.0 @opentiny/tiny-robot@^0.4.1 @opentiny/tiny-robot-svgs@^0.4.1 marked@^18.0.0 echarts@^5.4.0快速开始
方式一:Vue 插件
全局注册组件并配置 API,之后可在任意模板中直接使用 <FsesAiChat />。
// main.ts
import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import { FsesAiChatPlugin } from '@fses/ai-chat'
import 'ant-design-vue/dist/reset.css'
import '@fses/ai-chat/dist/style.css'
import App from './App.vue'
const app = createApp(App)
app.use(Antd)
app.use(FsesAiChatPlugin, {
apiBaseUrl: '/ai/runtime/api',
enableChartCards: true,
headers: () => ({
Authorization: `Bearer ${getToken()}`,
}),
onError: (err) => {
console.error('[Chat Error]', err)
},
})
app.mount('#app')在模板中使用:
<template>
<div style="width: 100%; height: 100vh;">
<FsesAiChat />
</div>
</template>方式二:独立组件
按需引入组件,通过 Props 逐个配置。
<script setup lang="ts">
import { FsesAiChat } from '@fses/ai-chat'
import '@fses/ai-chat/dist/style.css'
function getToken() {
return localStorage.getItem('token') || ''
}
</script>
<template>
<FsesAiChat
api-base-url="/ai/runtime/api"
enable-chart-cards
:headers="() => ({ Authorization: `Bearer ${getToken()}` })"
:on-error="(err) => console.error(err)"
placeholder="请输入问数需求..."
welcome-text="欢迎使用 AI 助手"
/>
</template>方式三:Headless
直接使用 useChat composable,自行构建界面。
import { useChat } from '@fses/ai-chat'
const chat = useChat({
apiBaseUrl: '/ai/runtime/api',
enableChartCards: true,
headers: () => ({ Authorization: `Bearer ${getToken()}` }),
onConversationChange(id) {
console.log('切换到会话', id)
},
onError(err) {
console.error(err)
},
})
await chat.sendMessage('本月回款趋势怎么样?')
console.log(chat.messages.value)
console.log(chat.isLoading.value)图表卡片
开启 enableChartCards 后,助手消息可以携带 cards?: ChartCardSpec[]。组件会先渲染 Markdown 正文,再在正文下方渲染结构化图表卡片。
一张图表卡片包含标题、统计周期、一个主图表、摘要、数据来源、权限说明、标准动作按钮和可选版本信息。图表区域由前端通过 ECharts 渲染,后端只需要返回稳定的 ChartCardSpec 数据。
ChartCardSpec
export interface ChartCardSpec {
type: 'chart-card'
id: string
title: string
period?: string
chart: {
type: 'mini-bar' | 'bar' | 'line' | 'area' | 'pie' | 'grouped-bar'
xField: string
yFields: string[]
data: Record<string, unknown>[]
sort?: 'none' | 'asc' | 'desc'
limit?: number
hiddenSeries?: string[]
}
summary?: string
source: {
label: string
value?: string
}
permission: {
label: string
level?: 'self' | 'team' | 'org'
downloadable?: boolean
}
actions?: Array<'view' | 'download' | 'insert-left' | 'save-doc' | 'save-kb' | 'upgrade'>
meta?: {
generatedBy?: 'agent'
prompt?: string
version?: number
}
}API
Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| apiBaseUrl | string | 插件配置或 '' | API 基础地址 |
| headers | () => Record<string, string> | 插件配置 | 请求头生成函数 |
| onError | (error: Error) => void | 插件配置 | 错误回调 |
| enableChartCards | boolean | false | 是否启用结构化图表卡片渲染 |
| placeholder | string | '请输入消息...' | 输入框占位文字 |
| welcomeText | string | '开始新的对话' | 无消息时的欢迎文字 |
Props 优先级高于插件配置,未传入的 Props 会从插件配置中读取。
插件配置
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| apiBaseUrl | string | 是 | API 基础地址 |
| headers | () => Record<string, string> | 否 | 请求头生成函数,每次请求前调用 |
| onError | (error: Error) => void | 否 | 全局错误回调 |
| enableChartCards | boolean | 否 | 是否默认启用图表卡片能力 |
useChat Composable
参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| apiBaseUrl | string | 是 | API 基础地址 |
| headers | () => Record<string, string> | 否 | 请求头生成函数 |
| enableChartCards | boolean | 否 | 是否保留并渲染结构化图表卡片 |
| onConversationChange | (id: string) => void | 否 | 会话切换回调 |
| onError | (error: Error) => void | 否 | 错误回调 |
返回值
| 字段 | 类型 | 说明 |
|------|------|------|
| conversations | Ref<Conversation[]> | 所有会话列表 |
| currentConversationId | Ref<string \| null> | 当前活跃会话 ID |
| messages | Ref<ChatMessage[]> | 当前会话的消息列表 |
| isLoading | Ref<boolean> | 是否正在请求中 |
| streamingMessageId | Ref<string \| null> | 当前正在流式输出的消息 ID |
| sendMessage | (content: string) => Promise<void> | 发送消息 |
| abortStream | () => void | 中止当前流式请求 |
| createConversation | () => Promise<string> | 创建新会话,返回会话 ID |
| deleteConversation | (id: string) => Promise<void> | 删除会话 |
| renameConversation | (id: string, name: string) => Promise<void> | 重命名会话 |
| switchConversation | (id: string) => Promise<void> | 切换会话并加载历史 |
| loadHistory | (id: string) => Promise<void> | 加载指定会话的消息历史 |
类型定义
interface ChatMessage {
id: string
role: 'user' | 'assistant' | 'status'
content: string
loading?: boolean
error?: string
detail?: string
timestamp: string
startTime?: number
cards?: ChartCardSpec[]
}
interface Conversation {
id: string
title: string
messages: ChatMessage[]
}后端 API 约定
组件依赖以下后端接口:
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | {apiBaseUrl}/chat/conversations | 获取会话列表 |
| GET | {apiBaseUrl}/chat/{id}/history | 获取会话消息历史 |
| POST | {apiBaseUrl}/chat | 发送消息,返回 SSE 流式响应 |
| DELETE | {apiBaseUrl}/chat/{id} | 删除会话 |
| PATCH | {apiBaseUrl}/chat/{id}/title | 重命名会话,请求体:{ title: string } |
SSE 事件协议
POST /chat 返回 SSE 流式响应,支持以下事件类型:
| 事件类型 | 数据字段 | 说明 |
|----------|----------|------|
| start-step | 无 | 开始一个新的回复步骤,创建空消息 |
| text-delta | delta | 增量文本,追加到当前消息 |
| text-end | 无 | 文本生成结束 |
| finish-step | 无 | 当前步骤完成 |
| tool-input-start | toolName, toolCallId | 工具开始执行,显示状态条 |
| tool-output-available | toolCallId, duration | 工具执行成功 |
| tool-output-error | toolCallId, error, duration | 工具执行失败 |
| data-oh:subagent.start | agentName, taskId | 子代理开始执行 |
| data-oh:subagent.done | taskId, duration | 子代理执行完成 |
| error | message | 服务端错误 |
工具名称映射
组件将内部工具名映射为中文标签展示:
| 工具名 | 显示标签 |
|--------|----------|
| load_ontology | 加载本体知识 |
| get_connection_config | 获取数据源配置 |
| search_business_glossary | 搜索术语表 |
| match_metrics | 匹配指标 |
| match_dimensions | 匹配维度 |
| validate_sql | 校验 SQL |
| explain_plan | 分析执行计划 |
| execute_sql_api | 执行 SQL 查询 |
| skill | 调用技能 |
| task | 执行子任务 |
功能特性
- SSE 流式响应:实时展示 AI 回复,支持中止
- 多会话管理:创建、切换、重命名、删除会话
- Markdown 渲染:支持 GFM 表格、代码块、列表、引用等
- 工具调用展示:可视化工具执行状态和耗时
- 子代理状态:展示子代理执行进度
- 图表卡片:基于结构化
ChartCardSpec渲染业务图表卡片 - 安全渲染:通过 DOMPurify 对 Markdown HTML 进行安全过滤,不执行用户提供的 HTML、CSS、JavaScript 或原始 ECharts option
- 自动滚动:新消息到达时自动滚动到底部
- 错误处理:流式错误和工具错误均有独立展示
- 快速操作:首次使用时提供快捷提问按钮
TinyRobotPage 演示
apps/web-agent/src/pages/TinyRobotPage.vue 作为集成演示页面,只负责开启图表卡片能力:
<script setup lang="ts">
import { FsesAiChat } from '@fses/ai-chat'
</script>
<template>
<FsesAiChat api-base-url="/ai_run/api/agent" enable-chart-cards />
</template>构建
pnpm build产出物:
dist/fses-ai-chat.js:ES Moduledist/fses-ai-chat.umd.cjs:UMDdist/style.css:样式文件dist/*.d.ts:TypeScript 类型声明
测试
pnpm test