Chat.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  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 :disHistory="sendLoading" :upload="type === FunctionList.File_Operation || !!formInfo" @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. <el-icon class="closeShow" :style="{ display: isShowPage ? 'block' : 'none'}" size="16px" color="#909399" @click="closePageInfo">
  56. <CircleClose />
  57. </el-icon>
  58. <div v-show="isShowPage" style="border-bottom: 1px solid #F0F0F0;">
  59. <div class="card_list">
  60. <div v-for="(v, i) in pageInfoList" :key="i"
  61. :class="`card-content ${pageInfoList.length > 1 ? 'card_width' : ''}`">
  62. <div class="loading-more-indicator" v-if="v.loading">
  63. <div class="loading-spinner"></div>
  64. </div>
  65. <img v-else :src="v?.favIconUrl" style="width: 24px;display: block" />
  66. <div class="title-wrapper">
  67. <span class="els title-scroller">{{ v?.title }}</span>
  68. <span class="els url-scroller">{{ v.loading ? v?.state : v?.url }}</span>
  69. </div>
  70. <el-icon class="closeIcon" size="16px" color="#909399" @click="deletePageInfo(i)">
  71. <CircleClose />
  72. </el-icon>
  73. </div>
  74. <div v-show="!pageInfoList.length && type === FunctionList.Intelligent_Form_filling">智能填表</div>
  75. </div>
  76. <div class="card-btn">
  77. <el-tooltip content="总结" placement="top">
  78. <el-button :disabled="disabledBtn" v-if="type !== FunctionList.Intelligent_Form_filling" round @click="handleSummary">总结</el-button>
  79. </el-tooltip>
  80. <el-tooltip content="抽取表单信息" placement="top">
  81. <el-button :disabled="disabledBtn" v-if="type === FunctionList.Intelligent_Form_filling && pageInfoList.length" round @click="handleSummaryFile">抽取</el-button>
  82. </el-tooltip>
  83. </div>
  84. </div>
  85. <el-input ref="textareaRef" v-model="inputMessage" type="textarea" :rows="3" placeholder="输入消息..."
  86. @keyup.enter="() => handleAsk()" />
  87. <div class="chat_area_op">
  88. <el-button v-if="sendLoading" style="background-color:#ffffff;border:2px solid rgb(134 143 153);" circle
  89. @click="handleStopAsk">
  90. <svg-icon icon-class="stop" color="red" />
  91. </el-button>
  92. <el-button
  93. v-else
  94. :style="`background-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'};border-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'}`"
  95. type="primary" circle @click="() => handleAsk()"
  96. :disabled="disSend">
  97. <svg-icon icon-class="send" color="#000000" />
  98. </el-button>
  99. </div>
  100. </div>
  101. </div>
  102. <!-- 历史记录 -->
  103. <historyComponent :msgUuid="msgUuid" ref="historyComponentRef" @currentData="(e) => handleCurrentData(e)" />
  104. </div>
  105. </template>
  106. <script setup>
  107. import { ref, onMounted, nextTick, inject, useTemplateRef, reactive } from 'vue'
  108. import { ElScrollbar, ElAvatar, ElInput, ElButton } from 'element-plus'
  109. import moment from 'moment'
  110. import fileLogo from '@/assets/svg/file.svg'
  111. import {
  112. getFileContent,
  113. buildObjPrompt,
  114. controllerList,
  115. formatMessage
  116. } from '@/entrypoints/sidepanel/utils/ai-service.js'
  117. import { storeToRefs } from 'pinia'
  118. import { ElMessage } from 'element-plus'
  119. import { useMsg } from '@/entrypoints/sidepanel/hook/useMsg.ts'
  120. import Tools from '@/entrypoints/sidepanel/component/tools.vue'
  121. import historyComponent from '@/entrypoints/sidepanel/component/historyComponent.vue'
  122. import document from '@/entrypoints/sidepanel/component/document.vue'
  123. import pageMask from '@/entrypoints/sidepanel/component/pageMask.vue'
  124. import ScrollToBottom from '@/entrypoints/sidepanel/component/ScrollToBottom.vue'
  125. import formTable from '@/entrypoints/sidepanel/component/formTable.vue'
  126. import userAvatar from '@/assets/images/user.png'
  127. import avatar from '@/public/icon/32.png'
  128. import { options, FunctionList } from '@/entrypoints/sidepanel/mock'
  129. import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResizeTextarea.ts'
  130. import { getPageInfo, getPageInfoClean, handleInput,downloadFile } from './utils/index.js'
  131. import { useMsgStore } from '@/store/modules/msg.ts'
  132. import { useUserStore } from '@/store/modules/user'
  133. import { putChat, uploadFile } from "@/api/index.js";
  134. import {askQues,getFormKey} from '@/api/modal.js'
  135. import { debounce } from 'lodash'
  136. const userStore = useUserStore()
  137. // 在其他状态变量附近添加
  138. const isLoadingMore = ref(false)
  139. import { v4 as uuidv4 } from 'uuid';
  140. // 滚动条引用
  141. const scrollbar = ref(null)
  142. const scrollToBottomRef = ref(null)
  143. const xlsxData = ref({})
  144. const isShowPage = ref(false)
  145. const tareRef = useTemplateRef('textareaRef')
  146. const drawerRef = useTemplateRef('historyComponentRef')
  147. const disSend = computed(() => {
  148. if (pageInfoList.value.length && type.value === FunctionList.Intelligent_Form_filling) return true
  149. if (sendLoading.value) return true
  150. if (inputMessage.value.trim()) return false
  151. return true
  152. })
  153. // 获取父组件提供的 Hook 实例
  154. const msgStore = useMsgStore()
  155. const { pageInfoList, messages, msgUuid, AIModel,page,hasNext,msgLoading } = storeToRefs(useMsgStore())
  156. const formInfo = ref('')
  157. const {
  158. taklToHtml,
  159. sendLoading,
  160. type,
  161. fetchRes
  162. } = useMsg(scrollbar)
  163. const inputMessage = ref('')
  164. const pageInfo = ref('')
  165. const summaryHtml = ref(false)
  166. function handleStopAsk() {
  167. inputMessage.value = ''
  168. pageInfoList.value = []
  169. isShowPage.value = false
  170. taklToHtml.value = false
  171. type.value = FunctionList.File_Operation
  172. controllerList.value[0]?.abort()
  173. controllerList.value = []
  174. sendLoading.value = false
  175. formInfo.value = null
  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. createWS(userMsg)
  260. formInfo.value = ''
  261. // const msg = reactive({
  262. // type: '',
  263. // rawContent: '',
  264. // senderId: -1,
  265. // receiverId: userStore.userInfo.id, //大模型
  266. // content: '',
  267. // sortKey: moment().valueOf(),
  268. // role: 'system',
  269. // conversationId: msgUuid.value,
  270. // })
  271. // messages.value.push(msg)
  272. // isShowPage.value = false
  273. pageInfoList.value = []
  274. // const resForm = await askQues({
  275. // conversationId: msgUuid.value,
  276. // modelName: '通义千问-Max',
  277. // question: buildObjPrompt(xlsxData.value,formInfo.value),
  278. // id: '699637194561691650',
  279. // // redisKey:msg.redisKey
  280. // })
  281. // return console.log(resForm);
  282. } catch (error) {
  283. console.log(error);
  284. } finally {
  285. // sendLoading.value = false
  286. // type.value = FunctionList.File_Operation
  287. }
  288. }
  289. async function handelIntelligentFillingClick() {
  290. isShowPage.value = true
  291. // taklToHtml.value = true
  292. // const tempPageInfo = await getPageInfo()
  293. // pageInfo.value = tempPageInfo
  294. // pageInfoList.value = [tempPageInfo]
  295. inputMessage.value = '/智能填表 '
  296. type.value = FunctionList.Intelligent_Form_filling
  297. }
  298. function handleCurrentChange(e) {
  299. options.forEach((item) => {
  300. item.options.forEach((item2) => {
  301. if (item2.value === e) {
  302. msgStore.updateAIModel(item2)
  303. }
  304. })
  305. })
  306. if (AIModel.value.file === true) {
  307. isShowPage.value = false
  308. taklToHtml.value = false
  309. pageInfoList.value = []
  310. }
  311. }
  312. async function handleCurrentData(e) {
  313. drawerRef.value.drawer = false
  314. isShowPage.value = false
  315. if (!e) {
  316. addNewDialogue()
  317. return
  318. }
  319. messages.value = []
  320. page.value = 1
  321. msgUuid.value = e
  322. chrome.storage.local.set({ msgUuid: msgUuid.value })
  323. await msgStore.initMsg()
  324. nextTick(() => {
  325. if (scrollbar.value && scrollbar.value.wrapRef) {
  326. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  327. }
  328. })
  329. }
  330. let SystemMsg = null
  331. watchEffect(() => {
  332. console.log(isShowPage.value);
  333. if (!isShowPage.value) {
  334. summaryHtml.value = false
  335. type.value = FunctionList.File_Operation
  336. SystemMsg = null
  337. inputMessage.value = ''
  338. }
  339. })
  340. const htmlIcon = ref('')
  341. async function readClick() {
  342. if (type.value === FunctionList.Intelligent_Form_filling) {
  343. pageInfoList.value = []
  344. type.value = FunctionList.File_Operation
  345. }
  346. summaryHtml.value = true
  347. isShowPage.value = true
  348. taklToHtml.value = true
  349. if (pageInfoList.value.length >= Number(import.meta.env.VITE_MAX_FILE_NUMBER)) {
  350. ElMessage.warning(`最多添加${import.meta.env.VITE_MAX_FILE_NUMBER}个文件`)
  351. return
  352. }
  353. const tempPageInfo = await getPageInfo()
  354. htmlIcon.value = tempPageInfo.favIconUrl
  355. const blob = new Blob([tempPageInfo.content.mainContent], { type: 'text/plain' })
  356. const file = new File([blob], '页面' + '.txt', { type: 'text/plain' })
  357. // console.log(file);
  358. // 下载文件到本地
  359. // downloadFile(file);
  360. console.log(tempPageInfo);
  361. await createFileObj(file)
  362. // await handleUpload(file,tempPageInfo.title + '.txt')
  363. }
  364. // 添加下载文件的函数
  365. function deletePageInfo(i) {
  366. pageInfoList.value.splice(i, 1)
  367. if (pageInfoList.value.length === 0) {
  368. if (type.value === FunctionList.Intelligent_Form_filling) {
  369. inputMessage.value = ''
  370. SystemMsg.content = '智能填表流程中断'
  371. SystemMsg.rawContent = '智能填表流程中断'
  372. putChat({
  373. ...SystemMsg,
  374. content: '',
  375. })
  376. }
  377. handleStopAsk()
  378. }
  379. }
  380. function addNewDialogue() {
  381. if (messages.value.length === 0) {
  382. ElMessage.warning('已经是新对话')
  383. return
  384. }
  385. summaryHtml.value = false
  386. type.value = FunctionList.File_Operation
  387. isShowPage.value = false
  388. taklToHtml.value = false
  389. messages.value = []
  390. page.value = 1
  391. pageInfoList.value = []
  392. msgUuid.value = uuidv4()
  393. hasNext.value = false
  394. chrome.storage.local.set({ msgUuid: msgUuid.value })
  395. }
  396. const disabledBtn = computed(() => {
  397. return !!(pageInfoList.value.find(_ => _.loading ))
  398. })
  399. function closePageInfo() {
  400. pageInfoList.value = []
  401. if (type.value === FunctionList.Intelligent_Form_filling && SystemMsg ) {
  402. SystemMsg.content = '智能填表流程中断'
  403. SystemMsg.rawContent = '智能填表流程中断'
  404. putChat({
  405. ...SystemMsg,
  406. content: '',
  407. })
  408. }
  409. handleStopAsk()
  410. }
  411. async function handleAsk(value) {
  412. if (sendLoading.value) return
  413. const str = value ?? inputMessage.value.trim()
  414. inputMessage.value = ''
  415. if (type.value === FunctionList.Intelligent_Form_filling) {
  416. const msg = await addMessage(str)
  417. await putChat(msg)
  418. const [res,obj] = await fetchRes(str)
  419. if (res.status === 'ok') {
  420. console.log(obj);
  421. formInfo.value = res.data
  422. SystemMsg = obj
  423. } else {
  424. type.value = FunctionList.File_Operation
  425. isShowPage.value = false
  426. }
  427. return
  428. }
  429. if (sendLoading.value) return
  430. let msg = null
  431. if (pageInfoList.value.length) {
  432. msg = await addMessage(JSON.stringify([...pageInfoList.value, { type: 'text', value: str }]), str, 'document')
  433. msg.fileId = pageInfoList.value[0].fileId
  434. msg.redisKey = pageInfoList.value[0].redisKey
  435. } else msg = await addMessage(str)
  436. pageInfoList.value = []
  437. isShowPage.value = false
  438. await putChat(msg)
  439. createWS(msg)
  440. }
  441. /**
  442. *
  443. * @param msg 用户消息对象,调用askQues时需要
  444. */
  445. async function createWS(msg) {
  446. sendLoading.value = true
  447. const obj = reactive({
  448. type: type.value === FunctionList.Intelligent_Form_filling ? 'form' : '',
  449. rawContent: '',
  450. senderId: -1,
  451. receiverId: userStore.userInfo.id, //大模型
  452. content: '',
  453. sortKey: moment().valueOf(),
  454. role: 'system',
  455. conversationId: msgUuid.value,
  456. })
  457. try {
  458. messages.value.push(obj)
  459. nextTick(() => {
  460. if (scrollbar.value && scrollbar.value.wrapRef) {
  461. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  462. }
  463. })
  464. const websocketId= uuidv4()
  465. const wsUrl = `${import.meta.env.VITE_API_WS_URL}/webSocket/clue/${websocketId}`;
  466. const socket = new WebSocket(wsUrl);
  467. askQues({
  468. conversationId: msgUuid.value,
  469. websocketId,
  470. modelName: '通义千问-Max',
  471. question: type.value === FunctionList.File_Operation ? msg.rawContent : buildObjPrompt(xlsxData.value,formInfo.value),
  472. id: '699637194561691650',
  473. redisKey:msg.redisKey
  474. }).catch(res => {
  475. obj.rawContent = '接口出错,请重试。'
  476. obj.content = '接口出错,请重试。'
  477. socket.close()
  478. })
  479. socket.onmessage = (event) => {
  480. try {
  481. if (event.data === '') {
  482. console.log(23223);
  483. socket.close()
  484. if (type.value === FunctionList.Intelligent_Form_filling) {
  485. const formResult = obj.rawContent
  486. let form = null
  487. if (formResult.includes('json')) form = JSON.parse(formResult.split('json')[1].split('```')[0])
  488. else form = JSON.parse(formResult)
  489. sendLoading.value = false
  490. console.log(xlsxData.value, form);
  491. handleInput(xlsxData.value, form)
  492. }
  493. isShowPage.value = false
  494. } else {
  495. obj.rawContent += event.data;
  496. obj.content = obj.type === 'form' ? obj.rawContent : formatMessage(obj.rawContent);
  497. // 滚动到底部
  498. nextTick(() => {
  499. if (scrollbar.value && scrollbar.value.wrapRef) {
  500. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight);
  501. }
  502. });
  503. }
  504. } catch (error) {
  505. console.error('解析消息出错:', error);
  506. }
  507. };
  508. socket.onerror = (error) => {
  509. console.error('WebSocket 错误:', error);
  510. socket.close();
  511. };
  512. socket.onclose = () => {
  513. console.log('WebSocket 连接已关闭');
  514. sendLoading.value = false;
  515. putChat({
  516. ...obj,
  517. content:''
  518. });
  519. sendLoading.value = false
  520. controllerList.value = []
  521. type.value = FunctionList.File_Operation
  522. };
  523. // 添加到控制器列表,以便可以在需要时中断连接
  524. controllerList.value.push({
  525. abort: () => {
  526. socket.close();
  527. }
  528. });
  529. } catch (error) {
  530. console.error( error);
  531. sendLoading.value = false;
  532. // 显示错误消息
  533. obj.content = '连接失败,请重试';
  534. putChat({
  535. ...obj,
  536. content:''
  537. });
  538. } finally {
  539. }
  540. }
  541. function handleCapture() {
  542. ElMessage({
  543. message: '开发中...',
  544. grouping: true,
  545. showClose: true
  546. })
  547. }
  548. function hisRecords() {
  549. drawerRef.value.drawer = true
  550. }
  551. const createFileObj = async (file) => {
  552. handleUpload(file)
  553. }
  554. const handleUpload = async (file) => {
  555. const obj = reactive({
  556. title: summaryHtml.value ? file.name.split('.')[0] : file.name,
  557. state: '上传中...',
  558. favIconUrl: fileLogo,
  559. loading: true,
  560. url:'',
  561. fileId: '',
  562. redisKey:'',
  563. content: {
  564. mainContent: ''
  565. }
  566. })
  567. if (type.value === FunctionList.Intelligent_Form_filling) {
  568. pageInfoList.value = []
  569. }
  570. if (summaryHtml.value) obj.favIconUrl = htmlIcon.value
  571. isShowPage.value = true
  572. // const obj = reactive({
  573. // title: file.name,
  574. // state: '上传中...',
  575. // favIconUrl: fileLogo,
  576. // loading: true,
  577. // url:'',
  578. // fileId: '',
  579. // redisKey:'',
  580. // content: {
  581. // mainContent: ''
  582. // }
  583. // })
  584. pageInfoList.value = [obj]
  585. try {
  586. let formData = new FormData()
  587. formData.append('avatarFile', file)
  588. const res = await uploadFile(formData)
  589. if (type.value === FunctionList.Intelligent_Form_filling) {
  590. obj.state = '解析中...'
  591. obj.redisKey = res.data.redisKey
  592. obj.id = res.data.file.id
  593. obj.url = res.data.file.url
  594. // const fileExtension = file.name.split('.').pop().toLowerCase()
  595. const result = await getFormKey({
  596. body: formInfo.value,
  597. input_data:res.data.data
  598. })
  599. xlsxData.value = JSON.parse(result.data)
  600. // if (fileExtension === 'xlsx') {
  601. // const readData = await getXlsxValue(file)
  602. // readData[0].forEach((header, i) => {
  603. // // if (!xlsxData.value[header]) xlsxData.value[header] = []
  604. // xlsxData.value[header] = readData[1][i]
  605. // })
  606. // } else {
  607. // const result = await getFormKey({
  608. // body: formInfo.value,
  609. // input_data:res.data.data
  610. // })
  611. // xlsxData.value = JSON.parse(result.data)
  612. // }
  613. }
  614. nextTick(() => {
  615. if (scrollbar.value && scrollbar.value.wrapRef) {
  616. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  617. }
  618. })
  619. if (type.value === FunctionList.File_Operation) {
  620. obj.loading = false
  621. obj.url = res.data.url
  622. obj.redisKey = res.data.redisKey
  623. obj.fileId = res.data.file.id
  624. obj.url = res.data.file.url
  625. }
  626. obj.loading = false
  627. console.log(SystemMsg);
  628. } catch (error) {
  629. if (SystemMsg) {
  630. SystemMsg.content = '数据上传出错,请重试'
  631. SystemMsg.rawContent = '数据上传出错,请重试'
  632. }
  633. console.log(error);
  634. ElMessage.error('上传出错')
  635. pageInfoList.value = []
  636. isShowPage.value = false
  637. } finally {
  638. SystemMsg && putChat({
  639. ...SystemMsg,
  640. content: '',
  641. })
  642. SystemMsg = null
  643. }
  644. }
  645. async function getFileValue(file) {
  646. let formData = new FormData()
  647. formData.append('file', file)
  648. const res = await getFileContent(formData)
  649. return res.data
  650. }
  651. let a = null
  652. // 组件挂载时滚动到底部
  653. onMounted(async () => {
  654. msgStore.updateAIModel(options[0].options[0])
  655. await msgStore.initMsg()
  656. useAutoResizeTextarea(tareRef, inputMessage)
  657. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  658. if (message.type === 'TO_SIDE_PANEL_PAGE_INFO') {
  659. pageInfo.value = message.data
  660. // 转发到 content.js
  661. chrome.tabs.sendMessage(sender.tab.id, {
  662. type: 'TO_CONTENT_SCRIPT',
  663. data: message.data
  664. })
  665. }
  666. if (message.type === 'TO_SIDE_PANEL_PAGE_CHANGE') {
  667. pageInfo.value = message.data
  668. }
  669. })
  670. nextTick(() => {
  671. if (scrollbar.value && scrollbar.value.wrapRef) {
  672. scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
  673. }
  674. })
  675. })
  676. </script>
  677. <style lang="scss" scoped>
  678. @use '@/entrypoints/sidepanel/css/chat.scss';
  679. @use '@/entrypoints/sidepanel/css/markdown.scss';
  680. .loading-more-indicator {
  681. display: flex;
  682. align-items: center;
  683. justify-content: center;
  684. padding: 10px 0;
  685. color: #909399;
  686. font-size: 14px;
  687. .loading-spinner {
  688. width: 20px;
  689. height: 20px;
  690. margin-right: 8px;
  691. border: 2px solid #e6e6e6;
  692. border-top-color: #4d6bfe;
  693. border-radius: 50%;
  694. animation: spin 1s linear infinite;
  695. }
  696. }
  697. @keyframes spin {
  698. to {
  699. transform: rotate(360deg);
  700. }
  701. }
  702. </style>