@ -4,17 +4,21 @@ import { BubbleList } from '@/components/xt-chat/xt-bubble';
import SenderInput from '../sender-input/index.vue' ;
import { Typography } from 'ant-design-vue' ;
import RightView from './rightView.vue' ;
import type { Ref } from ' vue';
import TextOverTips from '@/components/text-over-tips/index. vue';
import { useRoute } from 'vue-router' ;
import markdownit from 'markdown-it' ;
import { useClipboard } from '@vueuse/core' ;
import { genRandomId } from '@/utils/tools' ;
import { useChatStore } from '@/stores/modules/chat' ;
import { getHeaders } from '@/api/all/chat' ;
import { EventSourcePolyfill } from 'event-source-polyfill' ; // 导入 event-source-polyfill
import type { BubbleListProps } from '@/components/xt-chat/xt-bubble/types' ;
const QUESTION _ROLE = 'question' ;
const ANSWER _ROLE = 'text' ;
const FILE _ROLE = 'file' ;
const THOUGHT _ROLE = 'thought' ; // 新增思考过程角色常量
export default {
setup ( props , { emit , expose } ) {
@ -24,6 +28,7 @@ export default {
const { copy } = useClipboard ( ) ;
const senderRef = ref ( null ) ;
const eventSource = ref ( null ) ;
const rightViewRef = ref ( null ) ;
const bubbleListRef = ref < any > ( null ) ;
const searchValue = ref ( '' ) ;
@ -80,78 +85,7 @@ export default {
} ) ;
currentAnswerId . value = tempId ;
setTimeout ( ( ) => {
const content = ` # 测试数据表格
## 用户信息表
| 表头1 | 表头2 |
|---|---|
| 内容1 | 内容2 |
## 项目统计表
| 项目名称 | 负责人 | 进度 | 状态 | 预算(万元) | 完成度 |
|----------|--------|------|------|------------|--------|
| 电商平台重构 | 张三 | 75% | 进行中 | 50 | 🟡 |
| 移动端APP | 李四 | 90% | 测试中 | 30 | 🟢 |
| 数据分析系统 | 王五 | 45% | 开发中 | 80 | 🟡 |
| 客户管理系统 | 赵六 | 100% | 已完成 | 25 | 🟢 |
| 营销自动化 | 钱七 | 30% | 规划中 | 60 | 🔴 |
## 销售数据
| 月份 | 销售额(万) | 订单数 | 客户数 | 增长率 | 备注 |
|------|------------|--------|--------|--------|------|
| 1月 | 120.5 | 156 | 89 | +12% | 春节促销 |
| 2月 | 98.3 | 134 | 76 | -18% | 淡季 |
| 3月 | 145.2 | 189 | 102 | +48% | 新品上市 |
| 4月 | 167.8 | 203 | 115 | +16% | 稳定增长 |
| 5月 | 189.6 | 234 | 128 | +13% | 持续增长 |
> 以上数据仅供参考,实际数据请以系统为准。
**注意事项:**
- 所有数据均为测试数据
- 表格支持Markdown格式渲染
- 可以包含表情符号和特殊字符 ` ;
showRightView . value = true ;
rightViewContent . value = '' ;
nextTick ( ( ) => {
rightViewContent . value = content ;
} ) ;
searchValue . value = '' ;
conversationList . value . splice ( tempIndex , 1 , {
id : tempId ,
loading : false ,
role : ANSWER _ROLE ,
content ,
messageRender : ( content ) => (
< Typography >
< div v -html = { md.render ( content ) } > < / div >
< / Typography >
) ,
footer : ( { item } ) => {
// 判断当前元素是否是最后一个非QUESTION_ROLE的元素
const nonQuestionElements = conversationList . value . filter ( ( item ) => item . role !== QUESTION _ROLE ) ;
const isLastAnswer = nonQuestionElements [ nonQuestionElements . length - 1 ] ? . id === item . id ;
return (
< div class = "flex items-center" >
< Tooltip title = "复制" onClick = { ( ) => onCopy ( content ) } >
< icon-copy size = { 16 } class = "mr-12px color-#737478 cursor-pointer" / >
< / Tooltip >
{ isLastAnswer && (
< Tooltip title = "重新生成" onClick = { ( ) => onRefresh ( tempId , tempIndex ) } >
< icon-refresh size = { 16 } class = "color-#737478 cursor-pointer" / >
< / Tooltip >
) }
< / div >
) ;
} ,
} ) ;
} , 1000 ) ;
initSse ( ) ;
} ;
const handleCancel = ( ) => {
@ -181,6 +115,39 @@ export default {
margin : '0 auto' ,
} ,
} ,
[ FILE _ROLE ] : {
placement : 'start' ,
variant : 'borderless' ,
typing : { step : 2 , interval : 100 } ,
messageRender : ( items ) => {
return items . map ( ( item ) => (
< div class = "file-card" >
< icon-file class = "w-17px h-20px mr-20px color-#6D4CFE" / >
< div >
< TextOverTips
context = { item . name }
class = "font-family-medium color-#211F24 text-14px font-400 lh-22px mb-4px"
/ >
< span class = "color-#939499 font-family-regular text-12px font-400 lh-22px" > 创建时间 : 08 - 04 12 : 40 < / span >
< / div >
< / div >
) ) ;
} ,
style : {
width : '600px' ,
margin : '0 auto' ,
} ,
} ,
// 新增思考过程角色配置
[ THOUGHT _ROLE ] : {
placement : 'start' ,
variant : 'borderless' ,
style : {
width : '600px' ,
margin : '0 auto' ,
} ,
} ,
[ QUESTION _ROLE ] : {
placement : 'end' ,
shape : 'round' ,
@ -191,16 +158,129 @@ export default {
} ,
} ;
const initSse = ( ) => {
console . log ( 'initSse' , { agentInfo : chatStore . agentInfo , searchValue : chatStore . searchValue } ) ;
const onDownload = ( content ) => {
console . log ( 'onDownload' , content ) ;
} ;
const initSse = ( ) => {
// 先清理可能存在的旧连接
if ( eventSource . value ) {
eventSource . value . close ( ) ;
eventSource . value = null ;
}
// 构建SSE连接URL
const url = new URL ( 'http://localhost:3000/agent/input' ) ;
url . searchParams . append ( 'content' , searchValue . value || '测试' ) ;
url . searchParams . append ( 'session_id' , conversationId . value as string ) ;
url . searchParams . append ( 'agent_id' , chatStore . agentInfo ? . agent _id || '67890' ) ;
try {
eventSource . value = new EventSourcePolyfill ( url . toString ( ) , {
headers : { ... getHeaders ( ) , Accept : 'text/event-stream' } ,
} ) ;
searchValue . value = '' ;
// 任务开始
eventSource . value . addEventListener ( 'start' , function ( ) {
conversationList . value . push ( {
role : ANSWER _ROLE ,
content : (
< div class = "flex items-center " >
< span class = "font-family-medium color-#211F24 text-14px font-400 lh-22px mr-4px" > 智能思考 < / span >
< icon-caret-up size = { 16 } class = "color-#211F24" / >
< / div >
) ,
} ) ;
} ) ;
eventSource . value . addEventListener ( 'node_update' , function ( event ) {
console . log ( 'Node updated:' , event . data ) ;
const data = JSON . parse ( event . data ) ;
switch ( data . status ) {
case 'running' :
conversationList . value . push ( {
content : data . message ,
role : ANSWER _ROLE ,
} ) ;
break ;
case 'success' :
conversationList . value . push ( {
content : data . output ,
role : ANSWER _ROLE ,
} ) ;
break ;
}
} ) ;
eventSource . value . addEventListener ( 'final_result' , function ( event ) {
showRightView . value = true ;
const data = JSON . parse ( event . data ) ;
const _files = data . output ? . files ;
rightViewContent . value = _files [ 0 ] ? . content || '' ;
conversationList . value . push ( {
id : currentAnswerId . value ,
role : FILE _ROLE ,
content : _files ,
footer : ( { item } ) => {
const nonQuestionElements = conversationList . value . filter ( ( item ) => item . role !== QUESTION _ROLE ) ;
const isLastAnswer = nonQuestionElements [ nonQuestionElements . length - 1 ] ? . id === item . id ;
return (
< div class = "flex items-center" >
< Tooltip title = "下载" onClick = { ( ) => onDownload ( rightViewContent . value ) } >
< icon-download size = { 16 } class = "color-#737478 cursor-pointer" / >
< / Tooltip >
{ isLastAnswer && (
< Tooltip
title = "重新生成"
onClick = { ( ) => onRefresh ( currentAnswerId . value , conversationList . value . length ) }
>
< icon-refresh size = { 16 } class = "color-#737478 cursor-pointer" / >
< / Tooltip >
) }
< / div >
) ;
} ,
} ) ;
} ) ;
eventSource . value . addEventListener ( 'end' , function ( event ) {
generateLoading . value = false ;
closeSse ( ) ;
} ) ;
// 处理错误事件
eventSource . value . onerror = function ( error ) {
console . error ( 'EventSource error:' , error ) ;
generateLoading . value = false ;
message . error ( '连接服务器失败' ) ;
closeSse ( ) ;
} ;
} catch ( error ) {
console . error ( 'Failed to initialize SSE:' , error ) ;
message . error ( '初始化连接失败' ) ;
generateLoading . value = false ;
}
} ;
const closeSse = ( ) => {
console . log ( 'closeSse' ) ;
if ( eventSource . value ) {
eventSource . value . close ( ) ;
eventSource . value = null ;
}
// 确保加载状态被重置
generateLoading . value = false ;
} ;
onMounted ( ( ) => {
initSse ( ) ;
searchValue . value = chatStore . searchValue ;
chatStore . clearSearchValue ( ) ;
searchValue . value && initSse ( ) ;
} ) ;
onUnmounted ( ( ) => {
closeSse ( ) ;
} ) ;