AI 对话
用于沉浸式智能体对话场景的组件。
新会话
我是假期小助手,很高兴见到你哦!
我是你的假期小助手,我可以提供假期余额查询、假期政策查询、还可以帮你请假哦
我可以帮你:
怎么请年休假、事假、产假等
咨询海信的请假政策
休假期间有哪些注意事项,填写请假申请有哪些注意点,等等!!
点击查看代码
vue
<template>
<div style="width: 100%; height: 800px; border: 1px solid #ddd">
<hi-chat
ref="hiChatRef"
:messages="messages"
:historyChats="historyRecords"
:suggestions="suggestions"
:loading="loading"
title="假期小助手"
description="我是你的假期小助手,我可以提供假期余额查询、假期政策查询、还可以帮你请假哦"
:allow-upload="true"
:upload-config="{
action: '/',
}"
@send="send"
@stop="stop"
@newChat="newChat"
@selectHistoryChat="selectHistoryChat"
@scrollTop="scrollTop"
>
</hi-chat>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import HiChat from "pangea-component/hi-chat";
import type { Message } from "pangea-component/hi-chat";
import "pangea-component/dist/style.css";
const hiChatRef = ref();
const messages = ref<Message[]>([]);
const loading = ref(false);
const historyRecords = ref([
{
id: "111",
name: "咨询海信的请假政策",
},
{
id: "222",
name: "请求年休假",
},
]);
const suggestions = [
"怎么请年休假、事假、产假等",
"咨询海信的请假政策",
"休假期间有哪些注意事项,填写请假申请有哪些注意点,等等!!",
].map((i) => ({
subTitle: i,
}));
const send = (value, files) => {
const userMessage: Message = reactive({
type: "user",
id: Date.now(),
content: value,
status: "loading",
});
loading.value = true;
messages.value.push(userMessage);
if (files && files.length) {
userMessage.files = files.map((file) => {
return {
name: file.name,
url: file.url!,
type: file.type,
};
});
}
hiChatRef.value.scrollToBottom();
setTimeout(() => {
userMessage.status = "success";
loading.value = false;
// ai回复消息
const aiMessage: Message = reactive({
type: "ai",
id: Date.now(),
content: "这是一条统一的测试回复信息哦!!!",
thougth: "",
pageJson: null,
pageCode: undefined,
isAgentThought: false,
isThoughtCollapsed: false,
status: "success",
});
messages.value.push(aiMessage);
hiChatRef.value.scrollToBottom();
}, 1000);
};
const stop = () => {};
const newChat = () => {
messages.value = [];
};
const selectHistoryChat = (record) => {
messages.value = [
{
type: "user",
id: 1,
content: "历史会话咨询问题",
status: "success",
},
{
type: "ai",
id: 2,
content: "这是一条统一的历史回复信息哦!!!",
thougth: "",
pageJson: null,
pageCode: undefined,
isAgentThought: false,
isThoughtCollapsed: false,
status: "success",
},
];
};
const scrollTop = () => {};
</script>
安装方式
bash
yarn add pangea-component
引用方式
js
import HiChat from "pangea-component/hi-chat";
import "pangea-component/dist/style.css";
API
Props
参数名 | 描述 | 类型 | 默认值 |
---|---|---|---|
messages | 聊天列表数据 | Message[] | [] |
loading | 聊天问题是否正在查询中(是否显示停止生成操作) | Boolean | false |
historyChats | 历史会话列表 | Chat[] | [] |
historyChatLoading | 历史会话列表加载状态 | boolean | false |
title | 标题 | string | - |
welcomeTitle | 欢迎句标题 | string | - |
description | 描述 | string | - |
suggestions | 建议问题列表 | SuggestionItem[] | [] |
allowUpload | 是否允许上传 | boolean | false |
uploadType | 上传类型 | "image" | "file" | "all" | "all" |
uploadLimit | 上传文件数量限制 | number | - |
uploadConfig | 上传接口配置 | UploadConfig | - |
ctxInjected | 工作卡注入参数方法等对象 | Record<string, any> | - |
javascript
interface Message {
type: "ai" | "user"; // 消息类型
id: number;
content: string; // 消息内容
thougth?: string; // 思考内容
pageJson?: any; // 页面json
isThinking?: boolean; // 是否正在思考中
thinkTime?: boolean; // 思考时间
status?: "loading" | "success" | "fail"; // 消息状态
files?: MessageFileItem[]; // 气泡中显示的文件
}
interface MessageFileItem {
name: string; // 文件名
url: string; // url
type: string; // 文件类型
}
interface Chat {
name: string; // 会话名称
id: string; // 会话id
}
interface SuggestionItem {
title: string; // 主标题
subTitle?: string; // 子标题
icon?: VNode; // 图标
}
interface UploadConfig {
action: string; // 上传接口路径
headers?: Record<string, any>; // 请求头
data?: Record<string, any>; // 请求附加参数
name?: string; // 上传参数文件名 不设置默认file
didFetch: (response) => { url: string, name: string }; // 上传成功回调函数需要返回 url和name数据
}
Events
方法名 | 描述 | 参数 |
---|---|---|
send | 输入框发送事件 | (value: string) => {} |
stop | 打断当前发送事件 | () => {} |
newChat | 新建对话事件 | () => {} |
selectHistoryChat | 选择历史会话记录事件 | (record: Object) => {} |
scrollTop | 消息区域滚动到顶部 | (record: {conversionId: string}) => {} |
Slot
插槽名 | 描述 | 参数 |
---|---|---|
sender-top | 输入框顶部插槽 | - |
sender-prefix | 输入框底部左侧扩展插槽 | - |
conversation-extra-cont | 左侧会话列表内容扩展区域 | - |
consation-footer | 左侧会话列表底部扩展区域 | - |
welcome | 欢迎区域插槽 | - |
bubble-header | 气泡顶部插槽 | item: Message |
bubble-footer | 气泡底部插槽 | item: Message |
bubble-form | 工作卡渲染插槽 | item: Message |
Methods
方法名 | 描述 | 参数 | 返回值 |
---|---|---|---|
scrollToBottom | 会话信息列表内容滚动到底部方法 | - | - |
盘古 3.0 项目工作卡渲染
对于盘古 3.0 脚手的的项目,本身已安装 pangea-ui 组件包,可直接使用页面渲染器渲染工作卡内容,代码示例如下:
vue
<template>
<hi-chat
ref="hiChatRef"
:messages="messages"
:loading="loading"
:historyChats="historyRecords"
:historyChatLoading="historyMsgLoading"
:suggestions="suggestions"
:title="agentInfo.name"
:description="agentConfig.opening_statement"
@send="send"
@stop="stop"
@newChat="newChat"
@selectHistoryChat="selectHistoryChat"
@scrollTop="scrollTop"
>
<!-- 工作卡渲染插槽 -->
<template #bubble-form="{ item }">
<hi-page-template
v-if="item.pageJson"
:json="item.pageJson"
:showAlias="showAlias"
>
</hi-page-template>
</template>
</hi-chat>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from "vue";
import HiChat from "pangea-component/hi-chat";
import useChat from "pangea-component/use-chat";
import HiPageTemplate from "pangea-ui/hi-page-template";
const hiChatRef = ref();
const {
sendChat,
stopChat,
getHisMessages,
getHistoryRecords,
messages,
loading,
historyMsgLoading,
historyRecords,
msgHasMore,
conversationId,
queryAgentConfig,
queryAgentInfo,
agentInfo,
agentConfig,
} = useChat({ chatRef: hiChatRef });
// 在盘古3.0项目中showAlias方法即为use-intl中的showAlias
const showAlias = () => {};
const suggestions = computed(() => {
if (agentConfig.value?.suggested_questions?.length) {
return agentConfig.value.suggested_questions.map((item) => {
return {
subTitle: item,
};
});
}
return [];
});
onMounted(() => {
getHistoryRecords(null);
queryAgentConfig();
queryAgentInfo();
});
const send = (value) => {
sendChat(value);
};
const stop = () => {
stopChat();
};
const newChat = () => {
messages.value = [];
msgHasMore.value = false;
conversationId.value = null;
};
const selectHistoryChat = (record) => {
conversationId.value = record.id;
getHisMessages(record.id, null);
};
const scrollTop = () => {
if (msgHasMore.value) {
getHisMessages(conversationId.value, messages.value[0].id);
}
};
</script>
<style lang="less" scoped></style>
use-chat 代码示例
javascript
// 本示例中调用接口为智能体平台接口,实际使用时可根据自己项目组封装接口进行替换
import { reactive, ref, Ref } from "vue";
import { Message, StreamBlock, FileItem } from "../types";
import {
stopAiChat,
getAiChatForm,
getConversations,
getMessages,
getAgentParameters,
getAgentInfo,
} from "@/services/ai-agent"; // 替换为自己的项目接口
async function sendChat(inputMessage: string, files?: FileItem[]) {
const times: number[] = []; // 思考时间区间
// 用户输入的聊天消息
const userMessage: Message = reactive({
type: "user",
id: Date.now(),
content: inputMessage,
status: "loading",
});
if (files && files.length) {
userMessage.files = files.map((file) => {
return {
name: file.name,
url: file.url!,
type: file.type,
};
});
}
messages.value.push(userMessage);
chatRef?.value?.scrollToBottom?.();
loading.value = true;
let pageJsonId = null; // 工作卡表单id
// 请求接口
const response = await fetch(
`/higpt/api/agent/v1/agents/run?user_key=${userKey}&client_key=${clientkey}&app_id=${appId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: "gaorubin",
conversation_id: conversationId.value,
inputs: {},
query: inputMessage,
response_mode: "streaming",
files: files
? files.map((i) => ({
transfer_method: "remote_url",
type: i.type?.includes("image") ? "image" : "file",
upload_file_id: "",
url: i.url,
}))
: [],
}),
}
);
if (!response.ok || !response.body) {
loading.value = false;
userMessage.status = "fail";
console.error("Failed to fetch chat data");
return;
}
userMessage.status = "success";
// 创建流读取器
const reader = response.body.getReader();
// 创建文本解码器
const decoder = new TextDecoder();
// 创建一个缓冲区,用于存储读取的数据
let buffer = "";
// ai回复消息
const aiMessage: Message = reactive({
type: "ai",
id: Date.now(),
content: "",
thougth: "",
pageJson: null,
pageCode: undefined,
status: "loading",
thinkTime: undefined,
thinking: false,
});
messages.value.push(aiMessage);
while (true) {
// read方法持续读取数据流,直到读取完成
const { done, value } = await reader.read();
if (done) {
// 数据读取完成,关闭流读取器并设置加载状态为false, 清空任务id
loading.value = false;
taskId.value = "";
aiMessage.status = "success";
break;
}
buffer += decoder.decode(value, { stream: true });
const chunks = buffer.split("\n\n");
buffer = chunks.pop() || "";
for (const chunk of chunks) {
try {
const chunkData = chunk.replace(/^data: /, "");
if (!chunkData) continue;
let data: StreamBlock | undefined = undefined;
try {
data = JSON.parse(chunkData);
} catch (error) {
console.error("Error parsing JSON:", error);
}
if (!data) continue;
if (data.event === "agent_thought" && data.thought) {
taskId.value = data.task_id as string;
conversationId.value = data.conversation_id;
}
if (data.event === "agent_message" && data.answer) {
taskId.value = data.task_id as string;
aiMessage.isThinking = data.is_reasoning;
if (data.is_reasoning) {
aiMessage.thougth += data.answer;
if (!times[0]) {
times.push(Date.now());
} else {
times[1] = Date.now();
}
} else {
aiMessage.content += data.answer;
if (!aiMessage.thinkTime && times.length && times.length === 2) {
aiMessage.thinkTime = Math.ceil((times[1] - times[0]) / 1000);
}
}
chatRef?.value?.scrollToBottom?.();
}
if (
data.event === "agent_thought" &&
data.need_show_form &&
data.observation
) {
if (data.need_show_form && data.observation) {
try {
const cardRes = JSON.parse(data.observation)?.[data.tool!];
const cardData = JSON.parse(cardRes);
pageJsonId = cardData?.data;
} catch (error) {
console.error("Error parsing JSON:", error);
}
}
}
if (data.event === "message_end") {
loading.value = false;
taskId.value = "";
aiMessage.status = "success";
}
} catch (error) {
console.error("Error parsing JSON:", error);
}
}
}
// 查询工作卡json数据
if (pageJsonId) {
const res = await queryChatForm(pageJsonId);
aiMessage.pageJson = res?.pageJson;
aiMessage.pageCode = res?.pageCode;
chatRef?.value?.scrollToBottom?.();
}
}
// 停止智能体调用
async function stopChat() {
if (!taskId.value) return;
const response = await stopAiChat({
userKey: userKey,
clientkey: clientkey,
appId: appId,
userId: "zhangsan",
taskId: taskId.value,
});
if (response?.result === "success") {
loading.value = false;
taskId.value = "";
}
}
// 清空聊天
function clearChat() {
messages.value = [];
}
/**
* @description: 查询工作卡json数据
* @param id 工作卡id
*/
async function queryChatForm(id: string) {
const response = await getAiChatForm({
userKey: userKey,
clientkey: clientkey,
appId: appId,
id: id,
});
if (response?.data?.page_json) {
return {
pageJson: JSON.parse(response?.data?.page_json),
pageCode: response?.data?.page_code,
};
}
}
/**
* @description: 查询历史会话记录数据
* @param lastId 当前页最后一条记录的ID,第一页为null
*/
async function getHistoryRecords(lastId: string | null) {
const response = await getConversations({
user_key: userKey,
client_key: clientkey,
app_id: appId,
user_id: "zhangsan",
limit: 5,
last_id: lastId,
});
if (Array.isArray(response?.data)) {
historyRecords.value = historyRecords.value.concat(response?.data);
}
}
/**
* @description: 查询会话消息数据
* @param id 会话ID
* @param firstId 当前页第一条记录的ID,第一页为null
*/
async function getHisMessages(id, firstId) {
const msgs: Message[] = [];
historyMsgLoading.value = true;
const response = await getMessages({
user_key: userKey,
client_key: clientkey,
app_id: appId,
user_id: "zhangsan",
limit: 5,
conversation_id: id,
first_id: firstId,
});
historyMsgLoading.value = false;
msgHasMore.value = response?.has_more;
if (Array.isArray(response?.data)) {
response.data.forEach((item) => {
msgs.push({
type: "user",
id: item.id,
content: item.query || "",
});
msgs.push({
type: "ai",
id: item.id,
content: item.answer || "",
});
});
if (firstId) {
messages.value = msgs.concat(messages.value || []);
} else {
messages.value = msgs;
}
if (!firstId) {
setTimeout(() => {
chatRef?.value?.scrollToBottom?.();
}, 10);
}
}
}
// 查询智能体基本信息
async function queryAgentInfo() {
const res = await getAgentInfo({
user_key: userKey,
client_key: clientkey,
app_id: appId,
});
agentInfo.value = res;
}
// 查询智能体配置信息
async function queryAgentConfig() {
const res = await getAgentParameters({
user_key: "xxx",
client_key: "yyy",
app_id: "zzz",
});
agentConfig.value = res;
}
return {
sendChat,
stopChat,
clearChat,
getHistoryRecords,
messages,
loading,
conversationId,
historyRecords,
getHisMessages,
historyMsgLoading,
msgHasMore,
agentInfo,
agentConfig,
queryAgentInfo,
queryAgentConfig,
};
}