前端ai对话框架semi-design-vue_前端 对话框
ztj100 2025-09-19 06:31 3 浏览 0 评论
对于前端使用ai框架探索
semi-design-vue
实现功能 -
sse格式输出
接收指定命令处理
思考过程可折叠 - 适配deepseek等模型
清除上下文
请求错误返回
fetch请求封装
注意:!!!
java使用new Ssemitter推送时是无法识别空格符与换行符的,会将换行符当成一条新的消息发送到客户端,所以就需要后台进行批量处理,将空格符与换行符替换特殊字符,与前端搭配,前端再替换回来即可,要不然会发现消息整个都是乱的。我这里使用&sp;;替换空格,使用&nl;;替换换行。
具体如何处理还需要进一步参考使用的什么库与大模型,看返回值结果如何。
这是一个组件,可以引入vue3项目的任何一个页面里
import { Chat, MarkdownRender, Spin, Toast, Avatar, AvatarGroup, Tooltip, Space,Collapse } from '@kousum/semi-ui-vue';
import { defineComponent, ref, onMounted } from 'vue';
import { IconChevronUp } from '@kousum/semi-icons-vue';
import { getNewAgentSessionApi, sendMessageApi } from "../api/baseinfo";
import http from '../config/httpConfig';
import EventStreamRequest from '../config/httpFetch';
import { baseUrl } from '../config/baseUrl';
// 请求成功
const successMessage = {
role: 'assistant',
id: '1',
createAt: 1715676751920,
content: "请求成功"
};
// 等待中
const wattingMessage = {
id: 'loading',
role: 'assistant',
status: 'loading'
};
// 请求失败
const errorMessage = {
role: 'assistant',
id: 'error',
content: '请求错误',
status: 'error'
};
const defaultMessage = [
{
role: 'assistant',
id: '1',
createAt: 1715676751919,
content: ASSISTANT
}
]
const roleInfo = ROLE_INFO;
const commonOuterStyle = {
border: '1px solid var(--semi-color-border)',
borderRadius: '16px',
minHeight: '100%',
height: '100%',
margin: '0 auto',
width: '100%',
boxSizing: 'border-box'
};
let id = 0;
function getId() {
return `id-${id++}`;
}
// 上传文件地址
const uploadProps = {
action: 'https://api.semi.design/upload'
};
let post_message = ref('');// 指令输出结果
let post_switch = ref(true);//是指令输出还是问答输出
let post_think = ref(false); // 是否有思考过程
const CustomRender = defineComponent(() => {
const sessionId = localStorage.getItem('chatSessionId');
const intervalId = ref();
const message = ref(defaultMessage);
const onChatsChange = (chats) => {
message.value = (chats);
};
const onMessageSend = async (content, attachment) => {
message.value = [
...message.value,
{
role: 'assistant',
status: 'loading',
createAt: Date.now(),
id: getId()
}
];
let data = {
sessionId: sessionId,
question: content
};
const form = new FormData();
const eventStream = new EventStreamRequest(baseUrl + 'llm/chatStream', {
data, onEvent: (eventData) => {
if (eventData.indexOf("is running...") === -1) {
// 判断是指令输出还是正常问答输出
if (eventData.length >= 12) {
// 预检查
const regex = /^data:\{\"code\"/;
const flag = regex.test(eventData);
if (flag) {
post_switch.value = true;
// 指令输出
let msg = eventData.slice(5);
// let json = JSON.stringify(data);
post_message.value = msg;
const newAssistantMessage = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: '问题检索完成',
}
message.value = [...message.value.slice(0, -1), newAssistantMessage]
} else {
const regexEnd = /^data:\[\{\{END\}\}\]/;
const flagEnd = regexEnd.test(eventData);
if (flagEnd) {
if (post_switch.value){
// 指令抛出
window.parent.postMessage(post_message.value, '*');
}else{
// 问答结束
}
}else{
post_switch.value = false;
post_message.value = "";
// 问答输出
// 空格换成 &sp;; ,换行换成&nl;;
// 如果有思考过程 - 截取思考过程
if (eventData.indexOf('<think>') > -1 && eventData.indexOf('</think>') === -1){
post_think.value = true;
let msgStr = eventData.replace(/&sp;;/g, ' ').replace(/&nl;;/g, '\n')
let msg = msgStr.slice(12);
if (msg.indexOf('</think>')> -1){
// 思考结束
let resultStr = msg.slice(msg.indexOf(0,'</think>'));
let msgStr = resultStr.replace(/&sp;;/g, ' ').replace(/&nl;;/g, '\n');
resultThinkResponse(msgStr, msgStr)
}else{
// 思考进行中
let resultStr = msg.slice(msg.indexOf('<think>') + 1);
let msgStr = resultStr.replace(/&sp;;/g, ' ').replace(/&nl;;/g, '\n');
resultThinkResponse(msgStr, msgStr)
}
// let msgStr = eventData.replace(/&sp;;/g, ' ').replace(/&nl;;/g, '<br/>').replace(/<think>/g,'');
// let msg = msgStr.slice(5);
// generateMockResponse(msg);
} else if (eventData.indexOf('<think>') > -1 && eventData.indexOf('</think>') > -1){
// 思考过程之后的回答结果
let msgStr = eventData.replace(/&sp;;/g, ' ').replace(/&nl;;/g, '<br />');
let resultStr = msgStr.slice(msgStr.indexOf('</think>') + 8);
let thinkStr = msgStr.slice(12,msgStr.indexOf('</think>'));
// let msg = msgStr.slice(5);
// post_think.value = msg;
// resultThinkResponse(msg)
console.log(msgStr);
resultThinkResponse(thinkStr, resultStr);
}else{
post_think.value = false;
// 无思考过程返回值
let msgStr = eventData.replace(/&sp;;/g, ' ').replace(/&nl;;/g, '<br/>').replace(/<think>/g,'');
let msg = msgStr.slice(5);
generateMockResponse(msg);
}
}
}
}else{
console.log(eventData);
}
}
},onError:(error)=>{
const newAssistantMessage = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
status: 'error',
content: ERROR_TEXT,
}
message.value = [...message.value.slice(0, -1), newAssistantMessage]
}
});
eventStream.start();
};
// 输出think结果
const resultThinkResponse = (think,content) => {
let newMessage = {
role: 'think',
id: getId(),
createAt: Date.now(),
content: content,
think: think,
post_think:true,
};
message.value = [...message.value.slice(0, -1), newMessage];
intervalId.current = id;
};
// 输出结果
const generateMockResponse = (content) => {
const lastMessage = message.value[message.value.length - 1];
// console.log(content);
let newMessage = {
role: 'assistant',
id: getId(),
createAt: Date.now(),
content: content,
};
// console.log(lastMessage);
message.value = [...message.value.slice(0, -1), newMessage];
intervalId.current = id;
};
// 清除上下文
const clearContext = () => {
getNewAgentSessionApi().then((result) => {
localStorage.setItem('chatSessionId', result);
}).catch((err) => {
console.log(err);
});
};
// 重新提问
const onMessageReset = (msg) => {
generateMockResponse(msg.content);
};
// 停止生成
const onStopGenerator = (msg) => {
console.log(msg);
http.cancelRequest();
Toast.success('已取消');
const cancel = {
role: 'assistant',
id: 'cancel',
content: '已取消',
createAt: 1715676751920,
}
setTimeout(() => {
message.value = [...message.value.slice(0, -1), cancel]
}, 500)
}
// 助手和用户对话背景色
const renderByRole = ({ role, status }) => {
if (status === 'error'){
return { backgroundColor: ERROR_BG_COLOR }//错误消息背景色
}
return role === 'assistant'
? { backgroundColor: ASSISTANT_BG_COLOR } // 助理消息背景色
: { backgroundColor: USER_BG_COLOR }; // 用户消息背景色
}
// 对话渲染
const renderContent = (props) => {
const { role, message, defaultNode, className } = props;
console.log(message.role, post_think.value);
if (message.content) {
return <div class={className} style={renderByRole(message)}>
{message.post_think && message.post_think===true ?(
<Collapse expandIconPosition="left">
<Collapse.Panel header="思考" showArrow={true} itemKey={message.id}>
<MarkdownRender raw={`<myThink>${message.think}</myThink>`} components={components} />
</Collapse.Panel>
</Collapse>
):''}
<MarkdownRender raw={message.content} />
</div>
} else {
return <div class={className}>
<Spin />
</div>
}
};
const components = () => {
const components = {};
components['myThink'] = ({ children, onClick }) => {
return <p style={{ marginBottom: "12px" }}> {children} </p>
}
}
const handleBefore = (file)=>{
console.log(file);
return
}
onMounted(async () => {
try {
const result = await getNewAgentSessionApi();
message.value = defaultMessage;
localStorage.setItem('chatSessionId', result);
} catch (err) {
message.value = [errorMessage];
}
});
return () => (
<Chat
style={commonOuterStyle}
chats={message.value}
roleConfig={roleInfo}
chatBoxRenderConfig={{ renderChatBoxContent: renderContent }}
onChatsChange={onChatsChange}
onMessageSend={onMessageSend}
onStopGenerator={onStopGenerator}
showClearContext={true}
onClear={clearContext}
onMessageReset={onMessageReset}
uploadProps={{ uploadProps: uploadProps, disabled:true }}
uploadTipProps={{ content :'上传功能开发中...'} }
/>
);
})
export default CustomRender;
可修改配置文件 - 我是定义在全局中的
// 基础配置
const ROLE_INFO = {
// 用户头像 - 名称
user: {
name: 'User',
uuid:'user',
avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png'
},
// 智能助手头像 - 名称
assistant: {
name: '智能助手',
uuid:'assistant',
avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png'
},
// 暂时不用管
system: {
name: '智能',
uuid:'system',
avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/logo.png'
}
};
// 助手招呼用语
const ASSISTANT = "我是通用智能助手,请问有什么可以帮助您的?";
// 助手消息背景色
const ASSISTANT_BG_COLOR = '#ccf0ff';
// 用户消息背景色
const USER_BG_COLOR = '#10a2e0';
// 报错消息背景色
const ERROR_BG_COLOR = '#ff3f33';
// 报错消息提示语
const ERROR_TEXT = '请求错误';
fetch请求封装
export default class EventStreamRequest {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.controller = new AbortController();
this.signal = this.controller.signal;
this.isListening = false; // 新增的状态标志
this.retryInterval = this.options.retryInterval || 3000; // 默认重试间隔为3秒
}
async start() {
if (this.isListening) return; // 如果已经在监听,则不再启动新的监听
this.isListening = true;
const attemptConnect = async () => {
try {
const response = await fetch(this.url, {
method: 'POST',
responseType:'text/event-stream; charset=utf-8',
headers: {
'Content-Type': 'application/json',
// ...this.options.headers,
},
signal: this.signal,
body: JSON.stringify(this.options.data)
});
if (!response.ok) {
throw new Error(`Failed to fetch event stream with status ${response.status}`);
}
this.processStream(response.body.getReader());
} catch (error) {
this.handleError(error);
// setTimeout(attemptConnect, this.retryInterval); // 错误发生后尝试重新连接
}
};
attemptConnect(); // 尝试连接
}
processStream(reader) {
const decoder = new TextDecoder();
let buffer = '';
const processChunk = async ({ done, value }) => {
if (done) {
this.isListening = false; // 流结束时更新状态标志
return;
}
buffer += decoder.decode(value, { stream: true });
let index;
while ((index = buffer.indexOf('\n\n')) !== -1) {
const eventData = buffer.slice(0, index).trim();
buffer = buffer.slice(index + 2);
this.handleEvent(eventData);
}
reader.read().then(processChunk);
};
reader.read().then(processChunk);
}
handleEvent(eventData) {
// console.log('Received event:', eventData);
// 可以在这里调用外部传入的处理器
if (typeof this.options.onEvent === 'function') {
this.options.onEvent(eventData);
}
}
handleError(error) {
if (typeof this.options.onError === 'function') {
this.options.onError(error);
}
}
abort() {
if (this.isListening) {
this.controller.abort();
this.isListening = false;
console.log('EventStream request abortexiaog
效果图
相关推荐
- 离谱!写了5年Vue,还不会自动化测试?
-
前言大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。Playwright是一个功能强大的端到...
- package.json 与 package-lock.json 的关系
-
模块化开发在前端越来越流行,使用node和npm可以很方便的下载管理项目所需的依赖模块。package.json用来描述项目及项目所依赖的模块信息。那package-lock.json和...
- Github 标星35k 的 SpringBoot整合acvtiviti开源分享,看完献上膝盖
-
前言activiti是目前比较流行的工作流框架,但是activiti学起来还是费劲,还是有点难度的,如何整合在线编辑器,如何和业务表单绑定,如何和系统权限绑定,这些问题都是要考虑到的,不是说纯粹的把a...
- Vue3 + TypeScript 前端研发模板仓库
-
我们把这个Vue3+TypeScript前端研发模板仓库的初始化脚本一次性补全到可直接运行的状态,包括:完整的目录结构所有配置文件研发规范文档示例功能模块(ExampleFeature)...
- Vue 2迁移Vue 3:从响应式到性能优化
-
小伙伴们注意啦!Vue2已经在2023年底正式停止维护,再不升级就要面临安全漏洞没人管的风险啦!而且Vue3带来的性能提升可不是一点点——渲染速度快40%,内存占用少一半,更新速度直接翻倍!还在...
- VUE学习笔记:声明式渲染详解,对比WEB与VUE
-
声明式渲染是指使用简洁的模板语法,声明式的方式将数据渲染进DOM系统。声明式是相对于编程式而言,声明式是面向对象的,告诉框架做什么,具体操作由框架完成。编程式是面向过程思想,需要手动编写代码完成具...
- 苏州web前端培训班, 苏州哪里有web前端工程师培训
-
前端+HTML5德学习内容:第一阶段:前端页面重构:PC端网站布局、HTML5+CSS3基础项目、WebAPP页面布局;第二阶段:高级程序设计:原生交互功能开发、面向对象开发与ES5/ES6、工具库...
- 跟我一起开发微信小程序——扩展组件的代码提示补全
-
用户自定义代码块步骤:1.HBuilderX中工具栏:工具-代码块设置-vue代码块2.通过“1”步骤打开设置文件...
- JimuReport 积木报表 v1.9.3发布,免费可视化报表
-
项目介绍积木报表JimuReport,是一款免费的数据可视化报表,含报表、大屏和仪表盘,像搭建积木一样完全在线设计!功能涵盖:数据报表、打印设计、图表报表、门户设计、大屏设计等!...
- 软开企服开源的无忧企业文档(V2.1.3)产品说明书
-
目录1....
- 一款面向 AI 的下一代富文本编辑器,已开源
-
简介AiEditor是一个面向AI的下一代富文本编辑器。开箱即用、支持所有前端框架、支持Markdown书写模式什么是AiEditor?AiEditor是一个面向AI的下一代富文本编辑...
- 玩转Markdown(2)——抽象语法树的提取与操纵
-
上一篇玩转Markdown——数据的分离存储与组件的原生渲染发布,转眼已经鸽了大半年了。最近在操纵mdast生成md文件的时候,心血来潮,把玩转Markdown(2)给补上了。...
- DeepseekR1+ollama+dify1.0.0搭建企业/个人知识库(入门避坑版)
-
找了网上的视频和相关文档看了之后,可能由于版本不对或文档格式不对,很容易走弯路,看完这一章,可以让你少踩三天的坑。步骤和注意事项我一一列出来:1,前提条件是在你的电脑上已配置好ollama,dify1...
- 升级JDK17的理由,核心是降低GC时间
-
升级前后对比升级方法...
- 一个vsCode格式化插件_vscode格式化插件缩进量
-
ESlint...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)