Chat.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. <!-- Chat.vue -->
  2. <template>
  3. <div class="chat-container">
  4. <div v-if="!messages.length && !msgLoading" class="message-list">
  5. <pageMask />
  6. </div>
  7. <!-- 消息列表 -->
  8. <div class="message-list" v-else >
  9. <el-scrollbar ref="scrollbar" @scroll="handleScroll" v-loading="msgLoading">
  10. <!-- 加载更多指示器 -->
  11. <div v-if="isLoadingMore" class="loading-more-indicator">
  12. <div class="loading-spinner"></div>
  13. <span>加载更多消息...</span>
  14. </div>
  15. <div class="messages" ref="messagesContainer" >
  16. <div v-for="(message, index) in messages" :key="index"
  17. :class="['message-item', message.role === 'user' ? 'self' : 'other']">
  18. <el-avatar :size="32" :src="message.role === 'user' ? userAvatar : avatar" />
  19. <div class="message-content">
  20. <div class="content" v-if="message.role ==='system'"
  21. :class="{ 'loading-content': message.content === '' }">
  22. <formTable v-if="message.type === 'form'" :content="message.rawContent" />
  23. <span v-else v-html="message.content"></span>
  24. <span class="loading-indicator" v-if="sendLoading && index === messages.length - 1">
  25. <span class="dot"></span>
  26. <span class="dot"></span>
  27. <span class="dot"></span>
  28. </span>
  29. </div>
  30. <document v-else-if="message.type === 'document' && message.role === 'user'" :content="message.content"
  31. :rawContent="message.rawContent" />
  32. <div v-else class="content">
  33. <span v-if="message.type === ''">{{ message.content }}</span>
  34. <span class="loading-indicator" v-if="sendLoading && index === messages.length - 1">
  35. <span class="dot"></span>
  36. <span class="dot"></span>
  37. <span class="dot"></span>
  38. </span>
  39. </div>
  40. <div class="timestamp ">{{ moment(message.sortKey).format('MM-DD HH:mm') }}
  41. <!-- <span v-if="message.add" style="cursor: pointer;" @click="handleInput">填充</span> -->
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. </el-scrollbar>
  47. <ScrollToBottom :target="scrollbar" ref="scrollToBottomRef" />
  48. </div>
  49. <Tools @read-click="readClick" @upload-file="(file) => createFileObj(file)" @handle-capture="handleCapture" @his-records="hisRecords"
  50. @add-new-dialogue="addNewDialogue" @handle-current-change="handleCurrentChange"
  51. @handel-intelligent-filling-click="handelIntelligentFillingClick" />
  52. <div>
  53. <!-- 输入区域 -->
  54. <div class="input-area">
  55. <div v-show="isShowPage" style="border-bottom: 1px solid #F0F0F0;">
  56. <div class="card_list">
  57. <div v-for="(v, i) in pageInfoList" :key="i"
  58. :class="`card-content ${pageInfoList.length > 1 ? 'card_width' : ''}`">
  59. <div class="loading-more-indicator" v-if="v.loading">
  60. <div class="loading-spinner"></div>
  61. </div>
  62. <img v-else :src="v?.favIconUrl" style="width: 24px;display: block" />
  63. <div class="title-wrapper">
  64. <span class="els title-scroller">{{ v?.title }}</span>
  65. <span class="els url-scroller">{{ v.loading ? v?.state : v?.url }}</span>
  66. </div>
  67. <el-icon class="closeIcon" size="16px" color="#909399" @click="deletePageInfo(i)">
  68. <CircleClose />
  69. </el-icon>
  70. </div>
  71. </div>
  72. <div class="card-btn">
  73. <el-tooltip content="总结当前页面" placement="top">
  74. <el-button :disabled="disabledBtn" v-if="type !== FunctionList.Intelligent_Form_filling" round @click="handleSummary">总结</el-button>
  75. </el-tooltip>
  76. <el-tooltip content="抽取表单信息" placement="top">
  77. <el-button :disabled="disabledBtn" v-if="type === FunctionList.Intelligent_Form_filling && pageInfoList.length" round @click="handleSummaryFile">抽取</el-button>
  78. </el-tooltip>
  79. </div>
  80. </div>
  81. <el-input ref="textareaRef" v-model="inputMessage" type="textarea" :rows="3" placeholder="输入消息..."
  82. @keyup.enter="() => handleAsk()" />
  83. <div class="chat_area_op">
  84. <el-button
  85. :style="`background-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'};border-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'}`"
  86. v-if="inputMessage.trim() || !sendLoading" type="primary" circle @click="() => handleAsk()"
  87. :disabled="!inputMessage.trim() || sendLoading">
  88. <svg-icon icon-class="send" color="#000000" />
  89. </el-button>
  90. <el-button style="background-color:#ffffff;border:2px solid rgb(134 143 153);" v-else circle
  91. @click="handleStopAsk">
  92. <svg-icon icon-class="stop" color="red" />
  93. </el-button>
  94. </div>
  95. </div>
  96. </div>
  97. <!-- 历史记录 -->
  98. <historyComponent :msgUuid="msgUuid" ref="historyComponentRef" @currentData="(e) => handleCurrentData(e)" />
  99. </div>
  100. </template>
  101. <script setup>
  102. import { ref, onMounted, nextTick, inject, useTemplateRef, reactive } from 'vue'
  103. import { ElScrollbar, ElAvatar, ElInput, ElButton } from 'element-plus'
  104. import moment from 'moment'
  105. import fileLogo from '@/assets/svg/file.svg'
  106. import {
  107. buildExcelUnderstandingPrompt,
  108. getSummaryPrompt,
  109. getFileContent,
  110. buildObjPrompt,
  111. modelFileUpload,
  112. controllerList,
  113. formatMessage
  114. } from '@/entrypoints/sidepanel/utils/ai-service.js'
  115. import { storeToRefs } from 'pinia'
  116. import { ElMessage } from 'element-plus'
  117. import { useMsg } from '@/entrypoints/sidepanel/hook/useMsg.ts'
  118. import Tools from '@/entrypoints/sidepanel/component/tools.vue'
  119. import historyComponent from '@/entrypoints/sidepanel/component/historyComponent.vue'
  120. import document from '@/entrypoints/sidepanel/component/document.vue'
  121. import pageMask from '@/entrypoints/sidepanel/component/pageMask.vue'
  122. import ScrollToBottom from '@/entrypoints/sidepanel/component/ScrollToBottom.vue'
  123. import formTable from '@/entrypoints/sidepanel/component/formTable.vue'
  124. import userAvatar from '@/assets/images/user.png'
  125. import avatar from '@/public/icon/32.png'
  126. import { mockData, startMsg, mockData2, options, FunctionList } from '@/entrypoints/sidepanel/mock'
  127. import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResizeTextarea.ts'
  128. import { getPageInfo, getXlsxValue, handleInput } from './utils/index.js'
  129. import { useMsgStore } from '@/store/modules/msg.ts'
  130. import { useUserStore } from '@/store/modules/user'
  131. import { putChat, uploadFile } from "@/api/index.js";
  132. import {askQues,getFormKey} from '@/api/modal.js'
  133. import { debounce } from 'lodash'
  134. import request from '@/utils/request'
  135. import EventSourcePolyfill from 'eventsource-polyfill';
  136. const userStore = useUserStore()
  137. import { getChatDetail } from '@/api/index.js'
  138. // 在其他状态变量附近添加
  139. const isLoadingMore = ref(false)
  140. import { v4 as uuidv4 } from 'uuid';
  141. import content from '../content.js'
  142. // 滚动条引用
  143. const scrollbar = ref(null)
  144. const scrollToBottomRef = ref(null)
  145. const xlsxData = ref({})
  146. const isShowPage = ref(false)
  147. const tareRef = useTemplateRef('textareaRef')
  148. const drawerRef = useTemplateRef('historyComponentRef')
  149. // 获取父组件提供的 Hook 实例
  150. const msgStore = useMsgStore()
  151. const { pageInfoList, messages, msgUuid, AIModel,page,hasNext,msgLoading } = storeToRefs(useMsgStore())
  152. const formInfo = ref('')
  153. const {
  154. taklToHtml,
  155. sendLoading,
  156. type,
  157. streamRes,
  158. getFormKeyAndValue,
  159. requestFlowFn,
  160. fetchRes
  161. } = useMsg(scrollbar)
  162. const inputMessage = ref('')
  163. const pageInfo = ref('')
  164. const summaryHtml = ref(false)
  165. function handleStopAsk() {
  166. if (type.value === FunctionList.Intelligent_Form_filling) {
  167. inputMessage.value = ''
  168. pageInfoList.value = []
  169. isShowPage.value = false
  170. taklToHtml.value = false
  171. type.value = FunctionList.File_Operation
  172. }
  173. controllerList.value[0].abort()
  174. controllerList.value = []
  175. sendLoading.value = false
  176. }
  177. function handleScroll(a) {
  178. const { wrapRef } = scrollbar.value
  179. const { scrollHeight, clientHeight } = wrapRef
  180. scrollToBottomRef.value.showButton = scrollHeight - a.scrollTop - clientHeight >= 350
  181. // 检测是否滚动到顶部
  182. if (a.scrollTop <= 10 && hasNext.value) {
  183. handleScrollToTop()
  184. }
  185. }
  186. const messagesContainer = ref(null)
  187. let innerText = ''
  188. // 处理滚动到顶部的事件
  189. const handleScrollToTop = debounce(async function () {
  190. console.log('滚动到顶部,可以加载更多历史消息')
  191. if (!sendLoading.value && !isLoadingMore.value) {
  192. isLoadingMore.value = true
  193. try {
  194. // 记录当前第一条消息的位置
  195. const firstMessage = messagesContainer.value?.firstElementChild.querySelector('.timestamp')
  196. innerText = firstMessage.innerText
  197. const oldHeight = firstMessage?.offsetTop || 0
  198. await msgStore.changePage()
  199. setTimeout(() => {
  200. if (firstMessage) {
  201. console.log([...messagesContainer.value.querySelectorAll('.timestamp')].find(_ => _.innerText === innerText).offsetTop, innerText, 778);
  202. const newHeight = [...messagesContainer.value.querySelectorAll('.timestamp')].find(_ => _.innerText === innerText).offsetTop
  203. const scrollOffset = newHeight - oldHeight
  204. scrollbar.value?.setScrollTop(scrollOffset)
  205. }
  206. }, 400);
  207. // 恢复滚动位置
  208. } finally {
  209. isLoadingMore.value = false
  210. }
  211. }
  212. }, 500, { leading: false, trailing: true })
  213. /**
  214. * @param {string} msg
  215. * @param {string} raw
  216. * @param {string} type 发送的类型 document 、text
  217. * **/
  218. async function addMessage(msg, raw, type) {
  219. const newMessage = reactive({
  220. id:+new Date(),
  221. type: type || '',
  222. rawContent: raw ?? msg,
  223. senderId: userStore.userInfo.id,
  224. receiverId:-1, //大模型
  225. content: msg,
  226. sortKey: moment().valueOf(),
  227. role: 'user',
  228. conversationId:msgUuid.value,
  229. addToHistory: `${!taklToHtml.value}`,
  230. redisKey: undefined,
  231. fileId:undefined
  232. })
  233. if (type === 'document') {
  234. newMessage.content = msg
  235. newMessage.rawContent = raw
  236. }
  237. if (!msg) return
  238. messages.value.push(newMessage)
  239. nextTick(() => {
  240. if (scrollbar.value && scrollbar.value.wrapRef) {
  241. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  242. }
  243. })
  244. return newMessage
  245. }
  246. const handleSummary = async () => {
  247. if(sendLoading.value) return
  248. handleAsk(`请帮我总结当前${summaryHtml.value ? '页面' : '文件'}`)
  249. summaryHtml.value = false
  250. }
  251. const handleSummaryFile = async () => {
  252. if (sendLoading.value) return
  253. try {
  254. sendLoading.value = true
  255. const userMsg = await addMessage(JSON.stringify([...pageInfoList.value, { type: 'text', value: '抽取表单' }]), '抽取表单', 'document')
  256. userMsg.fileId = pageInfoList.value[0].fileId
  257. userMsg.redisKey = pageInfoList.value[0].redisKey
  258. await putChat(userMsg)
  259. const msg = reactive({
  260. type: '',
  261. rawContent: '',
  262. senderId: -1,
  263. receiverId: userStore.userInfo.id, //大模型
  264. content: '',
  265. sortKey: moment().valueOf(),
  266. role: 'system',
  267. conversationId: msgUuid.value,
  268. })
  269. messages.value.push(msg)
  270. isShowPage.value = false
  271. pageInfoList.value = []
  272. const resForm = await askQues({
  273. conversationId: msgUuid.value,
  274. modelName: '通义千问-Max',
  275. question: buildObjPrompt(xlsxData.value,formInfo.value),
  276. id: '699637194561691650',
  277. // redisKey:msg.redisKey
  278. })
  279. return console.log(resForm);
  280. const formResult = resForm.data[0].content
  281. let form = null
  282. if (formResult.includes('json')) form = JSON.parse(formResult.split('json')[1].split('```')[0])
  283. else form = JSON.parse(formResult)
  284. msg.type = 'form'
  285. // 保存原始内容
  286. msg.rawContent = formResult
  287. putChat({
  288. ...msg
  289. })
  290. sendLoading.value = false
  291. handleInput(xlsxData.value, form)
  292. nextTick(() => {
  293. if (scrollbar.value && scrollbar.value.wrapRef) {
  294. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  295. }
  296. })
  297. } catch (error) {
  298. } finally {
  299. sendLoading.value = false
  300. type.value = FunctionList.File_Operation
  301. }
  302. }
  303. async function handelIntelligentFillingClick() {
  304. // isShowPage.value = true
  305. // taklToHtml.value = true
  306. // const tempPageInfo = await getPageInfo()
  307. // pageInfo.value = tempPageInfo
  308. // pageInfoList.value = [tempPageInfo]
  309. inputMessage.value = '/智能填表 '
  310. type.value = FunctionList.Intelligent_Form_filling
  311. }
  312. function handleCurrentChange(e) {
  313. options.forEach((item) => {
  314. item.options.forEach((item2) => {
  315. if (item2.value === e) {
  316. msgStore.updateAIModel(item2)
  317. }
  318. })
  319. })
  320. if (AIModel.value.file === true) {
  321. isShowPage.value = false
  322. taklToHtml.value = false
  323. pageInfoList.value = []
  324. }
  325. }
  326. async function handleCurrentData(e) {
  327. drawerRef.value.drawer = false
  328. if (!e) {
  329. addNewDialogue()
  330. return
  331. }
  332. messages.value = []
  333. page.value = 1
  334. msgUuid.value = e
  335. chrome.storage.local.set({ msgUuid: msgUuid.value })
  336. await msgStore.initMsg()
  337. nextTick(() => {
  338. if (scrollbar.value && scrollbar.value.wrapRef) {
  339. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  340. }
  341. })
  342. }
  343. async function readClick() {
  344. if (type.value === FunctionList.Intelligent_Form_filling) {
  345. pageInfoList.value = []
  346. type.value = FunctionList.File_Operation
  347. }
  348. summaryHtml.value = true
  349. isShowPage.value = true
  350. taklToHtml.value = true
  351. if (pageInfoList.value.length >= Number(import.meta.env.VITE_MAX_FILE_NUMBER)) {
  352. ElMessage.warning(`最多添加${import.meta.env.VITE_MAX_FILE_NUMBER}个文件`)
  353. return
  354. }
  355. const tempPageInfo = await getPageInfo()
  356. const blob = new Blob([tempPageInfo.content.mainContent], { type: 'text/plain' })
  357. const file = new File([blob], tempPageInfo.title + '.txt', { type: 'text/plain' })
  358. await createFileObj(file)
  359. // await handleUpload(file,tempPageInfo.title + '.txt')
  360. }
  361. function deletePageInfo(i) {
  362. pageInfoList.value.splice(i, 1)
  363. if (pageInfoList.value.length === 0) {
  364. isShowPage.value = false
  365. taklToHtml.value = false
  366. if (type.value === FunctionList.Intelligent_Form_filling) inputMessage.value = ''
  367. type.value = FunctionList.File_Operation
  368. }
  369. }
  370. function addNewDialogue() {
  371. if (messages.value.length === 0) {
  372. ElMessage.warning('已经是新对话')
  373. return
  374. }
  375. isShowPage.value = false
  376. taklToHtml.value = false
  377. messages.value = []
  378. page.value = 1
  379. pageInfoList.value = []
  380. msgUuid.value = uuidv4()
  381. hasNext.value = false
  382. chrome.storage.local.set({ msgUuid: msgUuid.value })
  383. }
  384. const disabledBtn = computed(() => {
  385. return !!(pageInfoList.value.find(_ => _.loading ))
  386. })
  387. async function handleAsk(value) {
  388. const str = value ?? inputMessage.value.trim()
  389. inputMessage.value = ''
  390. if (type.value === FunctionList.Intelligent_Form_filling) {
  391. await addMessage(str)
  392. const res = await fetchRes(str)
  393. if (res.status === 'ok') {
  394. formInfo.value = res.data
  395. } else {
  396. type.value = FunctionList.File_Operation
  397. }
  398. return
  399. }
  400. if (sendLoading.value) return
  401. let msg = null
  402. if (pageInfoList.value.length) {
  403. msg = await addMessage(JSON.stringify([...pageInfoList.value, { type: 'text', value: str }]), str, 'document')
  404. msg.fileId = pageInfoList.value[0].fileId
  405. msg.redisKey = pageInfoList.value[0].redisKey
  406. } else msg = await addMessage(str)
  407. await putChat(msg)
  408. sendLoading.value = true
  409. const obj = reactive({
  410. type: type || '',
  411. rawContent: '',
  412. senderId: -1,
  413. receiverId: userStore.userInfo.id, //大模型
  414. content: '',
  415. sortKey: moment().valueOf(),
  416. role: 'system',
  417. conversationId: msgUuid.value,
  418. })
  419. messages.value.push(obj)
  420. nextTick(() => {
  421. if (scrollbar.value && scrollbar.value.wrapRef) {
  422. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  423. }
  424. })
  425. // request.post('/ai/model/question',{
  426. // conversationId: msgUuid.value,
  427. // modelName: '通义千问-Max',
  428. // question: msg.rawContent,
  429. // id: '699637194561691650',
  430. // // redisKey:msg.redisKey
  431. // }, {
  432. // onDownloadProgress: progressEvent => {
  433. // console.log('progressEvent', progressEvent)
  434. // let { responseText } = progressEvent.event.target
  435. // const newData = responseText.slice(buffer.length)
  436. // buffer += newData
  437. // // const events = buffer.split('\n\n')
  438. // const events = buffer.split('\n\n').map(item => {
  439. // const data = item.replace(/^data: /, '').trim()
  440. // // return data
  441. // try {
  442. // return JSON.parse(data)
  443. // } catch (error) {
  444. // return ''
  445. // }
  446. // })
  447. // debugger
  448. // buffer = events.pop() // 剩余部分保留到下一次
  449. // // events.forEach(event => {
  450. // // if (event.trim()) {
  451. // // const data = event.replace(/^data: /, '').trim();
  452. // // const infoData = JSON.parse(data);
  453. // // console.log('Received event:', infoData.message);
  454. // // }
  455. // // });
  456. // console.log('Received data:', events, 'aaa', buffer)
  457. // }
  458. // })
  459. // 使用 WebSocket 替代 EventSource
  460. try {
  461. // 创建 WebSocket 连接
  462. await askQues({
  463. conversationId: msgUuid.value,
  464. modelName: '通义千问-Max',
  465. question: msg.rawContent,
  466. id: '699637194561691650',
  467. // redisKey:msg.redisKey
  468. })
  469. const wsUrl = `${import.meta.env.VITE_API_WS_URL}/webSocket/${msgUuid.value}`;
  470. console.log(wsUrl,12312);
  471. const socket = new WebSocket(wsUrl);
  472. // 连接建立时发送消息
  473. socket.onopen = () => {
  474. console.log('WebSocket 连接已建立');
  475. // 发送请求数据
  476. socket.send(JSON.stringify({
  477. session:msgUuid.value
  478. }));
  479. };
  480. // 接收消息
  481. socket.onmessage = (event) => {
  482. try {
  483. console.log('收到数据:', event);
  484. // const data = JSON.parse(event.data);
  485. // console.log('收到数据:', data);
  486. return
  487. // 追加内容
  488. if (data.content) {
  489. obj.content += data.content;
  490. obj.rawContent += data.content;
  491. // 滚动到底部
  492. nextTick(() => {
  493. if (scrollbar.value && scrollbar.value.wrapRef) {
  494. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight);
  495. }
  496. });
  497. }
  498. // 如果是最后一条消息,关闭连接
  499. if (data.done) {
  500. socket.close();
  501. sendLoading.value = false;
  502. // 保存到数据库
  503. putChat({
  504. ...obj,
  505. content: obj.content
  506. });
  507. isShowPage.value = false;
  508. pageInfoList.value = [];
  509. }
  510. } catch (error) {
  511. console.error('解析消息出错:', error);
  512. }
  513. };
  514. // 处理错误
  515. socket.onerror = (error) => {
  516. console.error('WebSocket 错误:', error);
  517. socket.close();
  518. sendLoading.value = false;
  519. // 如果没有收到任何内容,显示错误消息
  520. if (!obj.content) {
  521. obj.content = '连接出错,请重试';
  522. // 保存到数据库
  523. putChat({
  524. ...obj,
  525. content: obj.content
  526. });
  527. }
  528. };
  529. // 连接关闭
  530. socket.onclose = () => {
  531. console.log('WebSocket 连接已关闭');
  532. sendLoading.value = false;
  533. };
  534. // 添加到控制器列表,以便可以在需要时中断连接
  535. controllerList.value.push({
  536. abort: () => {
  537. socket.close();
  538. }
  539. });
  540. } catch (error) {
  541. console.error('创建 WebSocket 失败:', error);
  542. sendLoading.value = false;
  543. // 显示错误消息
  544. obj.content = '连接失败,请重试';
  545. // 保存到数据库
  546. putChat({
  547. ...obj,
  548. content: obj.content
  549. });
  550. }
  551. // 移除原有的 EventSource 代码
  552. // const params = new URLSearchParams({...});
  553. // const eventSource = new EventSource(...);
  554. // ...
  555. }
  556. function handleCapture() {
  557. ElMessage({
  558. message: '开发中...',
  559. grouping: true,
  560. showClose: true
  561. })
  562. }
  563. function hisRecords() {
  564. drawerRef.value.drawer = true
  565. }
  566. const createFileObj = async (file) => {
  567. handleUpload(file)
  568. }
  569. const handleUpload = async (file) => {
  570. if (type.value === FunctionList.Intelligent_Form_filling) {
  571. pageInfoList.value = []
  572. }
  573. isShowPage.value = true
  574. const obj = reactive({
  575. title: file.name,
  576. state: '上传中...',
  577. favIconUrl: fileLogo,
  578. loading: true,
  579. url:'',
  580. fileId: '',
  581. redisKey:'',
  582. content: {
  583. mainContent: ''
  584. }
  585. })
  586. pageInfoList.value = [obj]
  587. try {
  588. let formData = new FormData()
  589. formData.append('avatarFile', file)
  590. const res = await uploadFile(formData)
  591. if (type.value === FunctionList.Intelligent_Form_filling) {
  592. obj.state = '解析中...'
  593. obj.redisKey = res.data.redisKey
  594. obj.id = res.data.file.id
  595. obj.url = res.data.file.url
  596. const fileExtension = file.name.split('.').pop().toLowerCase()
  597. if (fileExtension === 'xlsx') {
  598. const readData = await getXlsxValue(file)
  599. readData[0].forEach((header, i) => {
  600. // if (!xlsxData.value[header]) xlsxData.value[header] = []
  601. xlsxData.value[header] = readData[1][i]
  602. })
  603. } else {
  604. const result = await getFormKey({
  605. body: formInfo.value,
  606. input_data:res.data.data
  607. })
  608. xlsxData.value = JSON.parse(result.data)
  609. }
  610. }
  611. nextTick(() => {
  612. if (scrollbar.value && scrollbar.value.wrapRef) {
  613. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  614. }
  615. })
  616. if (type.value === FunctionList.File_Operation) {
  617. obj.loading = false
  618. obj.url = res.data.url
  619. obj.redisKey = res.data.redisKey
  620. obj.fileId = res.data.file.id
  621. obj.url = res.data.file.url
  622. }
  623. obj.loading = false
  624. } catch (error) {
  625. console.log(error);
  626. ElMessage.error('上传出错')
  627. pageInfoList.value = []
  628. isShowPage.value = false
  629. }
  630. }
  631. async function getFileValue(file) {
  632. let formData = new FormData()
  633. formData.append('file', file)
  634. const res = await getFileContent(formData)
  635. return res.data
  636. }
  637. let a = null
  638. // 组件挂载时滚动到底部
  639. onMounted(async () => {
  640. msgStore.updateAIModel(options[0].options[0])
  641. await msgStore.initMsg()
  642. useAutoResizeTextarea(tareRef, inputMessage)
  643. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  644. if (message.type === 'TO_SIDE_PANEL_PAGE_INFO') {
  645. pageInfo.value = message.data
  646. // 转发到 content.js
  647. chrome.tabs.sendMessage(sender.tab.id, {
  648. type: 'TO_CONTENT_SCRIPT',
  649. data: message.data
  650. })
  651. }
  652. if (message.type === 'TO_SIDE_PANEL_PAGE_CHANGE') {
  653. pageInfo.value = message.data
  654. }
  655. })
  656. nextTick(() => {
  657. if (scrollbar.value && scrollbar.value.wrapRef) {
  658. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  659. }
  660. })
  661. })
  662. </script>
  663. <style lang="scss" scoped>
  664. @use '@/entrypoints/sidepanel/css/chat.scss';
  665. @use '@/entrypoints/sidepanel/css/markdown.scss';
  666. .loading-more-indicator {
  667. display: flex;
  668. align-items: center;
  669. justify-content: center;
  670. padding: 10px 0;
  671. color: #909399;
  672. font-size: 14px;
  673. .loading-spinner {
  674. width: 20px;
  675. height: 20px;
  676. margin-right: 8px;
  677. border: 2px solid #e6e6e6;
  678. border-top-color: #4d6bfe;
  679. border-radius: 50%;
  680. animation: spin 1s linear infinite;
  681. }
  682. }
  683. @keyframes spin {
  684. to {
  685. transform: rotate(360deg);
  686. }
  687. }
  688. </style>