|
@@ -1,18 +1,18 @@
|
|
|
<!-- Chat.vue -->
|
|
|
<template>
|
|
|
<div class="chat-container">
|
|
|
- <div v-if="!messages.length" class="message-list">
|
|
|
+ <div v-if="!messages.length && !msgLoading" class="message-list">
|
|
|
<pageMask />
|
|
|
</div>
|
|
|
<!-- 消息列表 -->
|
|
|
- <div class="message-list" v-else>
|
|
|
- <el-scrollbar ref="scrollbar" @scroll="handleScroll">
|
|
|
+ <div class="message-list" v-else >
|
|
|
+ <el-scrollbar ref="scrollbar" @scroll="handleScroll" v-loading="msgLoading">
|
|
|
<!-- 加载更多指示器 -->
|
|
|
<div v-if="isLoadingMore" class="loading-more-indicator">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<span>加载更多消息...</span>
|
|
|
</div>
|
|
|
- <div class="messages" ref="messagesContainer">
|
|
|
+ <div class="messages" ref="messagesContainer" >
|
|
|
<div v-for="(message, index) in messages" :key="index"
|
|
|
:class="['message-item', message.role === 'user' ? 'self' : 'other']">
|
|
|
<el-avatar :size="32" :src="message.role === 'user' ? userAvatar : avatar" />
|
|
@@ -75,10 +75,10 @@
|
|
|
|
|
|
<div class="card-btn">
|
|
|
<el-tooltip content="总结当前页面" placement="top">
|
|
|
- <el-button v-if="type !== FunctionList.Intelligent_Form_filling" round @click="handleSummary">总结</el-button>
|
|
|
+ <el-button :disabled="disabledBtn" v-if="type !== FunctionList.Intelligent_Form_filling" round @click="handleSummary">总结</el-button>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="抽取表单信息" placement="top">
|
|
|
- <el-button v-if="type === FunctionList.Intelligent_Form_filling && pageInfoList.length" round @click="handleSummary">抽取</el-button>
|
|
|
+ <el-button :disabled="disabledBtn" v-if="type === FunctionList.Intelligent_Form_filling && pageInfoList.length" round @click="handleSummaryFile">抽取</el-button>
|
|
|
</el-tooltip>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -139,7 +139,9 @@ import { useMsgStore } from '@/store/modules/msg.ts'
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
import { putChat, uploadFile } from "@/api/index.js";
|
|
|
import {askQues,getFormKey} from '@/api/modal.js'
|
|
|
-import {debounce} from 'lodash'
|
|
|
+import { debounce } from 'lodash'
|
|
|
+import request from '@/utils/request'
|
|
|
+import EventSourcePolyfill from 'eventsource-polyfill';
|
|
|
const userStore = useUserStore()
|
|
|
import { getChatDetail } from '@/api/index.js'
|
|
|
// 在其他状态变量附近添加
|
|
@@ -155,9 +157,8 @@ const tareRef = useTemplateRef('textareaRef')
|
|
|
const drawerRef = useTemplateRef('historyComponentRef')
|
|
|
// 获取父组件提供的 Hook 实例
|
|
|
const msgStore = useMsgStore()
|
|
|
-const { pageInfoList, messages, msgUuid, AIModel,page,hasNext } = storeToRefs(useMsgStore())
|
|
|
+const { pageInfoList, messages, msgUuid, AIModel,page,hasNext,msgLoading } = storeToRefs(useMsgStore())
|
|
|
const formInfo = ref('')
|
|
|
-const uploadLoading = ref(false)
|
|
|
const {
|
|
|
taklToHtml,
|
|
|
sendLoading,
|
|
@@ -169,6 +170,7 @@ const {
|
|
|
} = useMsg(scrollbar)
|
|
|
const inputMessage = ref('')
|
|
|
const pageInfo = ref('')
|
|
|
+const summaryHtml = ref(false)
|
|
|
function handleStopAsk() {
|
|
|
if (type.value === FunctionList.Intelligent_Form_filling) {
|
|
|
inputMessage.value = ''
|
|
@@ -246,19 +248,73 @@ async function addMessage(msg, raw, type) {
|
|
|
}
|
|
|
if (!msg) return
|
|
|
messages.value.push(newMessage)
|
|
|
- console.log(scrollbar.value);
|
|
|
- scrollbar.value?.scrollTo(Number.MAX_VALUE)
|
|
|
nextTick(() => {
|
|
|
-
|
|
|
+ if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
+ scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
+ }
|
|
|
})
|
|
|
return newMessage
|
|
|
}
|
|
|
|
|
|
const handleSummary = async () => {
|
|
|
if(sendLoading.value) return
|
|
|
- handleAsk('请帮我总结当前文件')
|
|
|
+ handleAsk(`请帮我总结当前${summaryHtml.value ? '页面' : '文件'}`)
|
|
|
+ summaryHtml.value = false
|
|
|
+}
|
|
|
+const handleSummaryFile = async () => {
|
|
|
+ if (sendLoading.value) return
|
|
|
+ try {
|
|
|
+ sendLoading.value = true
|
|
|
+ const userMsg = await addMessage(JSON.stringify([...pageInfoList.value, { type: 'text', value: '抽取表单' }]), '抽取表单', 'document')
|
|
|
+ userMsg.fileId = pageInfoList.value[0].fileId
|
|
|
+ userMsg.redisKey = pageInfoList.value[0].redisKey
|
|
|
+ await putChat(userMsg)
|
|
|
+ const msg = reactive({
|
|
|
+ type: '',
|
|
|
+ rawContent: '',
|
|
|
+ senderId: -1,
|
|
|
+ receiverId: userStore.userInfo.id, //大模型
|
|
|
+ content: '',
|
|
|
+ sortKey: moment().valueOf(),
|
|
|
+ role: 'system',
|
|
|
+ conversationId: msgUuid.value,
|
|
|
+ })
|
|
|
+ messages.value.push(msg)
|
|
|
+ isShowPage.value = false
|
|
|
+ pageInfoList.value = []
|
|
|
+ const resForm = await askQues({
|
|
|
+ conversationId: msgUuid.value,
|
|
|
+ modelName: '通义千问-Max',
|
|
|
+ question: buildObjPrompt(xlsxData.value,formInfo.value),
|
|
|
+ id: '699637194561691650',
|
|
|
+ // redisKey:msg.redisKey
|
|
|
+ })
|
|
|
+ return console.log(resForm);
|
|
|
+
|
|
|
+ const formResult = resForm.data[0].content
|
|
|
+ let form = null
|
|
|
+ if (formResult.includes('json')) form = JSON.parse(formResult.split('json')[1].split('```')[0])
|
|
|
+ else form = JSON.parse(formResult)
|
|
|
+ msg.type = 'form'
|
|
|
+ // 保存原始内容
|
|
|
+ msg.rawContent = formResult
|
|
|
+ putChat({
|
|
|
+ ...msg
|
|
|
+ })
|
|
|
+ sendLoading.value = false
|
|
|
+ handleInput(xlsxData.value, form)
|
|
|
+ nextTick(() => {
|
|
|
+ if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
+ scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ sendLoading.value = false
|
|
|
+ type.value = FunctionList.File_Operation
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
async function handelIntelligentFillingClick() {
|
|
|
// isShowPage.value = true
|
|
|
// taklToHtml.value = true
|
|
@@ -307,6 +363,7 @@ async function readClick() {
|
|
|
pageInfoList.value = []
|
|
|
type.value = FunctionList.File_Operation
|
|
|
}
|
|
|
+ summaryHtml.value = true
|
|
|
isShowPage.value = true
|
|
|
taklToHtml.value = true
|
|
|
if (pageInfoList.value.length >= Number(import.meta.env.VITE_MAX_FILE_NUMBER)) {
|
|
@@ -344,21 +401,22 @@ function addNewDialogue() {
|
|
|
hasNext.value = false
|
|
|
chrome.storage.local.set({ msgUuid: msgUuid.value })
|
|
|
}
|
|
|
-
|
|
|
+const disabledBtn = computed(() => {
|
|
|
+ return !!(pageInfoList.value.find(_ => _.loading ))
|
|
|
+})
|
|
|
async function handleAsk(value) {
|
|
|
const str = value ?? inputMessage.value.trim()
|
|
|
+ inputMessage.value = ''
|
|
|
if (type.value === FunctionList.Intelligent_Form_filling) {
|
|
|
await addMessage(str)
|
|
|
const res = await fetchRes(str)
|
|
|
if (res.status === 'ok') {
|
|
|
formInfo.value = res.data
|
|
|
- console.log(res, 55558)
|
|
|
} else {
|
|
|
type.value = FunctionList.File_Operation
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
- inputMessage.value = ''
|
|
|
if (sendLoading.value) return
|
|
|
let msg = null
|
|
|
if (pageInfoList.value.length) {
|
|
@@ -368,13 +426,7 @@ async function handleAsk(value) {
|
|
|
} else msg = await addMessage(str)
|
|
|
await putChat(msg)
|
|
|
sendLoading.value = true
|
|
|
- // messages.value.push(obj)
|
|
|
- nextTick(() => {
|
|
|
- if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
- scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
- }
|
|
|
-})
|
|
|
- const obj = reactive({
|
|
|
+ const obj = reactive({
|
|
|
type: type || '',
|
|
|
rawContent: '',
|
|
|
senderId: -1,
|
|
@@ -383,63 +435,158 @@ async function handleAsk(value) {
|
|
|
sortKey: moment().valueOf(),
|
|
|
role: 'system',
|
|
|
conversationId: msgUuid.value,
|
|
|
- })
|
|
|
+ })
|
|
|
messages.value.push(obj)
|
|
|
- const res = await askQues({
|
|
|
+ nextTick(() => {
|
|
|
+ if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
+ scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ request.post('/ai/model/question',{
|
|
|
conversationId: msgUuid.value,
|
|
|
modelName: '通义千问-Max',
|
|
|
question: msg.rawContent,
|
|
|
id: '699637194561691650',
|
|
|
- redisKey:msg.redisKey
|
|
|
+ // redisKey:msg.redisKey
|
|
|
+ }, {
|
|
|
+ onDownloadProgress: progressEvent => {
|
|
|
+ console.log('progressEvent', progressEvent)
|
|
|
+ let { responseText } = progressEvent.event.target
|
|
|
+ const newData = responseText.slice(buffer.length)
|
|
|
+ buffer += newData
|
|
|
+ // const events = buffer.split('\n\n')
|
|
|
+ const events = buffer.split('\n\n').map(item => {
|
|
|
+ const data = item.replace(/^data: /, '').trim()
|
|
|
+ // return data
|
|
|
+ try {
|
|
|
+ return JSON.parse(data)
|
|
|
+ } catch (error) {
|
|
|
+ return ''
|
|
|
+ }
|
|
|
+ })
|
|
|
+ debugger
|
|
|
+ buffer = events.pop() // 剩余部分保留到下一次
|
|
|
+ // events.forEach(event => {
|
|
|
+ // if (event.trim()) {
|
|
|
+ // const data = event.replace(/^data: /, '').trim();
|
|
|
+ // const infoData = JSON.parse(data);
|
|
|
+ // console.log('Received event:', infoData.message);
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ console.log('Received data:', events, 'aaa', buffer)
|
|
|
+ }
|
|
|
})
|
|
|
- const result = res.data[res.data.length - 1].content
|
|
|
- isShowPage.value = false
|
|
|
- pageInfoList.value = []
|
|
|
- // 保存原始内容
|
|
|
- obj.rawContent = result
|
|
|
-
|
|
|
- // 实现打字机效果
|
|
|
- const formattedText = formatMessage(result)
|
|
|
- const typewriterEffect = async (text) => {
|
|
|
- let currentIndex = 0
|
|
|
- const totalLength = text.length
|
|
|
- const typingSpeed = 10 // 打字速度,可以调整
|
|
|
+ // 使用 WebSocket 替代 EventSource
|
|
|
+ // try {
|
|
|
+ // // 创建 WebSocket 连接
|
|
|
+ // await askQues({
|
|
|
+ // conversationId: msgUuid.value,
|
|
|
+ // modelName: '通义千问-Max',
|
|
|
+ // question: msg.rawContent,
|
|
|
+ // id: '699637194561691650',
|
|
|
+ // // redisKey:msg.redisKey
|
|
|
+ // })
|
|
|
+ // const wsUrl = `ws:${wsBaseUrl}/question/${msgUuid.value}`;
|
|
|
+ // const socket = new WebSocket(wsUrl);
|
|
|
+ // // 连接建立时发送消息
|
|
|
+ // socket.onopen = () => {
|
|
|
+ // console.log('WebSocket 连接已建立');
|
|
|
+ // // 发送请求数据
|
|
|
+ // // socket.send(JSON.stringify({
|
|
|
+ // // conversationId: msgUuid.value,
|
|
|
+ // // modelName: '通义千问-Max',
|
|
|
+ // // question: msg.rawContent,
|
|
|
+ // // id: '699637194561691650',
|
|
|
+ // // redisKey: msg.redisKey,
|
|
|
+ // // token: userStore.token
|
|
|
+ // // }));
|
|
|
+ // };
|
|
|
|
|
|
- // 清除之前的内容
|
|
|
- obj.content = ''
|
|
|
+ // // 接收消息
|
|
|
+ // socket.onmessage = (event) => {
|
|
|
+ // try {
|
|
|
+ // const data = JSON.parse(event.data);
|
|
|
+ // console.log('收到数据:', data);
|
|
|
+ // return
|
|
|
+ // // 追加内容
|
|
|
+ // if (data.content) {
|
|
|
+ // obj.content += data.content;
|
|
|
+ // obj.rawContent += data.content;
|
|
|
+
|
|
|
+ // // 滚动到底部
|
|
|
+ // nextTick(() => {
|
|
|
+ // if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
+ // scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight);
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ // }
|
|
|
+
|
|
|
+ // // 如果是最后一条消息,关闭连接
|
|
|
+ // if (data.done) {
|
|
|
+ // socket.close();
|
|
|
+ // sendLoading.value = false;
|
|
|
+
|
|
|
+ // // 保存到数据库
|
|
|
+ // putChat({
|
|
|
+ // ...obj,
|
|
|
+ // content: obj.content
|
|
|
+ // });
|
|
|
+
|
|
|
+ // isShowPage.value = false;
|
|
|
+ // pageInfoList.value = [];
|
|
|
+ // }
|
|
|
+ // } catch (error) {
|
|
|
+ // console.error('解析消息出错:', error);
|
|
|
+ // }
|
|
|
+ // };
|
|
|
|
|
|
- // 逐字添加内容
|
|
|
- const typing = setInterval(() => {
|
|
|
- if (currentIndex < totalLength) {
|
|
|
- obj.content += text[currentIndex]
|
|
|
- currentIndex++
|
|
|
- // 滚动到底部,保持视图跟随
|
|
|
- nextTick(() => {
|
|
|
- if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
- scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
- }
|
|
|
-})
|
|
|
- } else {
|
|
|
- clearInterval(typing)
|
|
|
- sendLoading.value = false
|
|
|
- // 打字完成后再次滚动到底部
|
|
|
- nextTick(() => {
|
|
|
- if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
- scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
- }
|
|
|
-})
|
|
|
- }
|
|
|
- }, typingSpeed)
|
|
|
- }
|
|
|
-
|
|
|
- // 开始打字机效果
|
|
|
- await typewriterEffect(formattedText)
|
|
|
+ // // 处理错误
|
|
|
+ // socket.onerror = (error) => {
|
|
|
+ // console.error('WebSocket 错误:', error);
|
|
|
+ // socket.close();
|
|
|
+ // sendLoading.value = false;
|
|
|
+
|
|
|
+ // // 如果没有收到任何内容,显示错误消息
|
|
|
+ // if (!obj.content) {
|
|
|
+ // obj.content = '连接出错,请重试';
|
|
|
+ // // 保存到数据库
|
|
|
+ // putChat({
|
|
|
+ // ...obj,
|
|
|
+ // content: obj.content
|
|
|
+ // });
|
|
|
+ // }
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // 连接关闭
|
|
|
+ // socket.onclose = () => {
|
|
|
+ // console.log('WebSocket 连接已关闭');
|
|
|
+ // sendLoading.value = false;
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // 添加到控制器列表,以便可以在需要时中断连接
|
|
|
+ // controllerList.value.push({
|
|
|
+ // abort: () => {
|
|
|
+ // socket.close();
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ // } catch (error) {
|
|
|
+ // console.error('创建 WebSocket 失败:', error);
|
|
|
+ // sendLoading.value = false;
|
|
|
+
|
|
|
+ // // 显示错误消息
|
|
|
+ // obj.content = '连接失败,请重试';
|
|
|
+
|
|
|
+ // // 保存到数据库
|
|
|
+ // putChat({
|
|
|
+ // ...obj,
|
|
|
+ // content: obj.content
|
|
|
+ // });
|
|
|
+ // }
|
|
|
|
|
|
- // 保存到数据库
|
|
|
- putChat({
|
|
|
- ...obj,
|
|
|
- content: '' // 保存完整内容
|
|
|
- })
|
|
|
+ // 移除原有的 EventSource 代码
|
|
|
+ // const params = new URLSearchParams({...});
|
|
|
+ // const eventSource = new EventSource(...);
|
|
|
+ // ...
|
|
|
}
|
|
|
function handleCapture() {
|
|
|
ElMessage({
|
|
@@ -448,22 +595,19 @@ function handleCapture() {
|
|
|
showClose: true
|
|
|
})
|
|
|
}
|
|
|
-
|
|
|
function hisRecords() {
|
|
|
drawerRef.value.drawer = true
|
|
|
}
|
|
|
const createFileObj = async (file) => {
|
|
|
- let formData = new FormData()
|
|
|
- formData.append('avatarFile', file)
|
|
|
- handleUpload(formData,file.name)
|
|
|
+ handleUpload(file)
|
|
|
}
|
|
|
-const handleUpload = async (formData,name) => {
|
|
|
+const handleUpload = async (file) => {
|
|
|
if (type.value === FunctionList.Intelligent_Form_filling) {
|
|
|
pageInfoList.value = []
|
|
|
}
|
|
|
isShowPage.value = true
|
|
|
const obj = reactive({
|
|
|
- title: name,
|
|
|
+ title: file.name,
|
|
|
state: '上传中...',
|
|
|
favIconUrl: fileLogo,
|
|
|
loading: true,
|
|
@@ -476,24 +620,35 @@ const handleUpload = async (formData,name) => {
|
|
|
})
|
|
|
pageInfoList.value = [obj]
|
|
|
try {
|
|
|
+ let formData = new FormData()
|
|
|
+ formData.append('avatarFile', file)
|
|
|
const res = await uploadFile(formData)
|
|
|
if (type.value === FunctionList.Intelligent_Form_filling) {
|
|
|
obj.state = '解析中...'
|
|
|
obj.redisKey = res.data.redisKey
|
|
|
obj.id = res.data.file.id
|
|
|
obj.url = res.data.file.url
|
|
|
- console.log({
|
|
|
- body: formInfo.value,
|
|
|
- uploadFile:res.data.data
|
|
|
- },888569);
|
|
|
-
|
|
|
- const result = await getFormKey({
|
|
|
+ const fileExtension = file.name.split('.').pop().toLowerCase()
|
|
|
+ if (fileExtension === 'xlsx') {
|
|
|
+ const readData = await getXlsxValue(file)
|
|
|
+ readData[0].forEach((header, i) => {
|
|
|
+ // if (!xlsxData.value[header]) xlsxData.value[header] = []
|
|
|
+ xlsxData.value[header] = readData[1][i]
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const result = await getFormKey({
|
|
|
body: formInfo.value,
|
|
|
- uploadFile:res.data.data
|
|
|
+ input_data:res.data.data
|
|
|
})
|
|
|
- console.log(result);
|
|
|
-
|
|
|
+ xlsxData.value = JSON.parse(result.data)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ if (scrollbar.value && scrollbar.value.wrapRef) {
|
|
|
+ scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
|
|
|
}
|
|
|
+})
|
|
|
if (type.value === FunctionList.File_Operation) {
|
|
|
obj.loading = false
|
|
|
obj.url = res.data.url
|