useArtChat 数据管理
配合 Agent hook 进行对话数据管理。
何时使用
通过 Agent 进行会话数据管理,并产出供页面渲染使用的数据。
代码演示
基本
基础用法。
ts
<script setup lang="ts">
import type { BubbleListProps } from '@artmate/chat'
import { BubbleList, Sender, useArtAgent, useArtChat } from '@artmate/chat'
import { Promotion } from '@element-plus/icons-vue'
import { ElAvatar, ElButton, ElIcon, ElSpace } from 'element-plus'
import { computed, ref } from 'vue'
const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000))
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
typing: { step: 5, interval: 20 },
},
local: {
placement: 'end',
},
}
const mockSuccess = ref(false)
const content = ref('')
const senderLoading = ref(false)
// Agent for request
const [agent] = useArtAgent<string, { message: string }, string>({
request: async ({ message }, { onSuccess, onError }) => {
senderLoading.value = true
await sleep()
senderLoading.value = false
mockSuccess.value = !mockSuccess.value
if (mockSuccess.value) {
onSuccess([`Mock success return. You said: ${message}`])
}
onError(new Error('Mock request failed'))
},
})
// Chat messages
const { onRequest, messages } = useArtChat({
agent,
requestPlaceholder: 'Waiting...',
requestFallback: 'Mock failed return. Please try again later.',
})
const messageList = computed(() => {
return messages.value.map(({ id, message, status }) => ({
key: id,
loading: status === 'loading',
role: status === 'local' ? 'local' : 'ai',
content: message,
}))
})
function submit() {
onRequest(content.value)
content.value = ''
}
</script>
<template>
<ElSpace direction="vertical" style="width: 100%" fill>
<BubbleList :roles="roles" :style="{ maxHeight: '300px' }" :items="messageList">
<template #avatar="{ info }">
<ElAvatar>
{{ info.role === 'ai' ? 'AI' : 'You' }}
</ElAvatar>
</template>
</BubbleList>
<Sender v-model="content" :loading="senderLoading">
<template #actions>
<ElButton circle type="primary" :disabled="senderLoading || !content" @click="submit">
<ElIcon color="white">
<Promotion />
</ElIcon>
</ElButton>
</template>
</Sender>
</ElSpace>
</template>
查看源代码
流式输出
使用流式输出更新内容。
ts
<script setup lang="ts">
import type { BubbleListProps } from '@artmate/chat'
import { BubbleList, Sender, useArtAgent, useArtChat } from '@artmate/chat'
import { Promotion } from '@element-plus/icons-vue'
import { ElAvatar, ElButton, ElIcon, ElSpace } from 'element-plus'
import { computed, ref } from 'vue'
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
typing: { step: 5, interval: 20 },
},
local: {
placement: 'end',
},
}
const content = ref('')
const senderLoading = ref(false)
// Agent for request
const [agent] = useArtAgent<string, { message: string }, string>({
request: async ({ message }, { onUpdate, onSuccess }) => {
senderLoading.value = true
const fullContent = `Streaming output instead of Bubble typing effect. You typed: ${message}`
let currentContent = ''
const id = setInterval(() => {
currentContent = fullContent.slice(0, currentContent.length + 2)
onUpdate(currentContent)
if (currentContent === fullContent) {
senderLoading.value = false
clearInterval(id)
onSuccess([fullContent])
}
}, 100)
},
})
// Chat messages
const { onRequest, messages } = useArtChat({
agent,
})
const messageList = computed(() => {
return messages.value.map(({ id, message, status }) => ({
key: id,
role: status === 'local' ? 'local' : 'ai',
content: message,
}))
})
function submit() {
onRequest(content.value)
content.value = ''
}
</script>
<template>
<ElSpace direction="vertical" style="width: 100%" fill>
<BubbleList :roles="roles" :style="{ maxHeight: '300px' }" :items="messageList">
<template #avatar="{ info }">
<ElAvatar>
{{ info.role === 'ai' ? 'AI' : 'You' }}
</ElAvatar>
</template>
</BubbleList>
<Sender v-model="content" :loading="senderLoading">
<template #actions>
<ElButton circle type="primary" :disabled="senderLoading || !content" @click="submit">
<ElIcon color="white">
<Promotion />
</ElIcon>
</ElButton>
</template>
</Sender>
</ElSpace>
</template>
查看源代码
打断输出
打断正在流式输出的内容。
ts
<script setup lang="ts">
import type { BubbleListProps } from '@artmate/chat'
import { ArtStream, BubbleList, Sender, useArtAgent, useArtChat } from '@artmate/chat'
import { Promotion } from '@element-plus/icons-vue'
import { ElAvatar, ElButton, ElIcon, ElSpace } from 'element-plus'
import { computed, ref } from 'vue'
import SenderLoading from './loading.vue'
const roles: BubbleListProps['roles'] = {
ai: {
placement: 'start',
typing: { step: 5, interval: 20 },
},
local: {
placement: 'end',
},
}
const contentChunks = [
'He',
'llo',
', w',
'or',
'ld!',
' Ant',
' Design',
' X',
' is',
' the',
' best',
'!',
]
function mockReadableStream() {
const sseChunks: string[] = []
for (let i = 0; i < contentChunks.length; i++) {
const sseEventPart = `event: message\ndata: {"id":"${i}","content":"${contentChunks[i]}"}\n\n`
sseChunks.push(sseEventPart)
}
return new ReadableStream({
async start(controller) {
for (const chunk of sseChunks) {
await new Promise((resolve) => setTimeout(resolve, 300))
controller.enqueue(new TextEncoder().encode(chunk))
}
controller.close()
},
})
}
const content = ref('')
const senderLoading = ref(false)
const abort = ref(() => {})
// Agent for request
const [agent] = useArtAgent<string, { message: string }, string>({
request: async (_, { onUpdate, onSuccess }) => {
senderLoading.value = true
const stream = ArtStream({
readableStream: mockReadableStream(),
})
const reader = stream.getReader()
abort.value = () => reader.cancel()
let current = ''
while (reader) {
const { value, done } = await reader.read()
if (done) {
senderLoading.value = false
onSuccess([current])
break
}
if (!value) continue
const data = JSON.parse(value.data)
current += data.content || ''
onUpdate(current)
}
},
})
// Chat messages
const { onRequest, messages } = useArtChat({
agent,
})
const messageList = computed(() => {
return messages.value.map(({ id, message, status }) => ({
key: id,
role: status === 'local' ? 'local' : 'ai',
content: message,
}))
})
function submit() {
console.log(senderLoading.value)
if (senderLoading.value) {
abort.value()
senderLoading.value = false
} else {
if (!content.value) return
onRequest(content.value)
content.value = ''
}
}
</script>
<template>
<ElSpace direction="vertical" style="width: 100%" fill>
<BubbleList :roles="roles" :style="{ maxHeight: '300px' }" :items="messageList">
<template #avatar="{ info }">
<ElAvatar>
{{ info.role === 'ai' ? 'AI' : 'You' }}
</ElAvatar>
</template>
</BubbleList>
<Sender v-model="content" :loading="senderLoading">
<template #actions>
<ElButton circle type="primary" @click="submit">
<ElIcon v-if="!senderLoading" color="white">
<Promotion />
</ElIcon>
<ElIcon v-else color="white" size="32">
<SenderLoading />
</ElIcon>
</ElButton>
</template>
</Sender>
</ElSpace>
</template>
查看源代码
多项建议
通过定制能力,返回多个推荐内容。
ai
Hello, what can I do for you?
ts
<script setup lang="ts">
import type { BubbleDataType, BubbleListProps, PromptProps } from '@artmate/chat'
import { BubbleList, Prompts, Sender, useArtAgent, useArtChat } from '@artmate/chat'
import { Promotion } from '@element-plus/icons-vue'
import { ElAvatar, ElButton, ElIcon, ElSpace } from 'element-plus'
import { computed, ref } from 'vue'
import SenderLoading from './loading.vue'
const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000))
const roles: BubbleListProps['roles'] = {
user: {
placement: 'end',
},
text: {
placement: 'start',
typing: true,
},
suggestion: {
placement: 'start',
variant: 'borderless',
},
}
interface AgentUserMessage {
type: 'user'
content: string
}
interface AgentAIMessage {
type: 'ai'
content?: string
list?: {
type: 'text' | 'suggestion'
content?: string
prompts?: PromptProps[]
}[]
}
type AgentMessage = AgentUserMessage | AgentAIMessage
const content = ref('')
const senderLoading = ref(false)
// Agent for request
const [agent] = useArtAgent<AgentMessage, { message: AgentMessage }, AgentMessage>({
request: async ({ message }, { onSuccess }) => {
senderLoading.value = true
await sleep()
const { content } = message || {}
senderLoading.value = false
onSuccess([
{
type: 'ai',
list: [
{
type: 'text',
content: `Do you want?`,
},
{
type: 'suggestion',
prompts: [
{
key: '1',
description: `Look at: ${content}`,
},
{
key: '2',
description: `Search: ${content}`,
},
{
key: '3',
description: `Try: ${content}`,
},
],
},
],
},
])
},
})
// Chat messages
const { onRequest, parsedMessages } = useArtChat({
agent,
defaultMessages: [
{
id: 'init',
message: {
type: 'ai',
content: 'Hello, what can I do for you?',
},
status: 'success',
},
],
requestPlaceholder: {
type: 'ai',
content: 'Waiting...',
},
// Convert AgentMessage to BubbleMessage
parser: (agentMessages) => {
const list = agentMessages.content ? [agentMessages] : (agentMessages as AgentAIMessage).list
return (list || []).map((msg) => ({
role: msg.type,
content: msg?.content || '',
prompts: msg.type === 'suggestion' ? msg.prompts : undefined,
}))
},
})
const messageList = computed(() => {
// 处理消息列表,将最后一条消息的 prompts 传递给 BubbleList
return parsedMessages.value.map(({ id, message, status }, index) => ({
key: id,
loading: status === 'loading',
...message,
prompts: index === parsedMessages.value.length - 1 ? message?.prompts : undefined,
})).filter(item => item.content || item.prompts?.length) as BubbleDataType[]
})
function submit() {
if (!content.value) return
onRequest({
type: 'user',
content: content.value,
})
content.value = ''
}
</script>
<template>
<ElSpace direction="vertical" style="width: 100%" fill>
<BubbleList :roles="roles" :style="{ maxHeight: '300px' }" :items="messageList">
<template #avatar="{ info }">
<ElAvatar :style="info.role === 'suggestion' ? { visibility: 'hidden' } : {}">
<template v-if="info.role !== 'suggestion'">
{{ info.role || info.key }}
</template>
</ElAvatar>
</template>
<template #content="{ info }">
<template v-if="info.role === 'suggestion'">
<Prompts :items="info.prompts" />
</template>
</template>
</BubbleList>
<Sender v-model="content" :loading="senderLoading">
<template #actions>
<ElButton circle type="primary" @click="submit">
<ElIcon v-if="!senderLoading" color="white">
<Promotion />
</ElIcon>
<ElIcon v-else color="white" size="32">
<SenderLoading />
</ElIcon>
</ElButton>
</template>
</Sender>
</ElSpace>
</template>
查看源代码
API
ts
type useArtChat<AgentMessage, ParsedMessage = AgentMessage> = (
config: ArtChatConfig<AgentMessage, ParsedMessage>
) => ArtChatConfigReturnType
ArtChatConfig
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
agent | 通过 useArtAgent 生成的 agent ,当使用 onRequest 方法时, agent 参数是必需的。 | ArtAgent | - | |
defaultMessages | 默认展示信息 | { status, message }[] | - | |
parser | 将 AgentMessage 转换成消费使用的 ParsedMessage,不设置时则直接消费 AgentMessage。支持将一条 AgentMessage 转换成多条 ParsedMessage | (message: AgentMessage) => BubbleMessage | BubbleMessage[] | - | |
requestFallback | 请求失败的兜底信息,不提供则不会展示 | AgentMessage | () => AgentMessage | - | |
requestPlaceholder | 请求中的占位信息,不提供则不会展示 | AgentMessage | () => AgentMessage | - |
ArtChatConfigReturnType
属性 | 说明 | 类型 | 版本 |
---|---|---|---|
messages | 当前管理的内容 | AgentMessages[] | |
parsedMessages | 经过 parser 转译过的内容 | ParsedMessages[] | |
onRequest | 添加一条 Message,并且触发请求 | (message) => void | |
setMessages | 直接修改 messages,不会触发请求 | (messages: { message, status }[]) => void |