Browse Source

提交合并

wzg 3 months ago
parent
commit
a98837f80c

+ 4 - 1
.env

@@ -1,5 +1,8 @@
 VITE_OPENAI_API_KEY_TONG=sk-e9855234f47346049809ce23ed3ebe3f
 VITE_MAX_FILE_NUMBER=10
-VITE_APP_BASE_API='http://192.168.1.166:7777'
+VITE_APP_BASE_API='http://192.168.1.202:13088'
+
+# 接口地址 (WebSocket)
+VITE_API_WS_URL = 'ws://192.168.1.202:13088'
 # 终端ID
 VITE_CLIENT_ID = '765be25e4e78b101b896cb3ecac39b1b'

+ 3 - 0
package.json

@@ -19,11 +19,14 @@
     "@element-plus/icons-vue": "^2.3.1",
     "axios": "^1.7.9",
     "crypto-js": "^4.2.0",
+    "dompurify": "^3.2.5",
     "element-plus": "^2.9.1",
+    "eventsource-polyfill": "^0.9.6",
     "highlight.js": "^11.11.1",
     "html2canvas": "^1.4.1",
     "jsencrypt": "^3.3.2",
     "lodash": "^4.17.21",
+    "marked": "^15.0.8",
     "moment": "^2.30.1",
     "openai": "^4.85.4",
     "pinia": "^3.0.1",

+ 11 - 0
src/api/index.js

@@ -102,3 +102,14 @@ export function getSmsCaptcha(data) {
         data
     })
 }
+
+export function uploadFile(data) {
+    return request({
+        url: `/messages/file/addFile`,
+        method: 'post',
+        data: data,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        },
+    })
+}

+ 39 - 0
src/api/modal.js

@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+export function askQues(data) {
+    return request({
+        url: '/ai/model/question',
+        method: 'post',
+        data: data
+    })
+    
+}
+
+
+// export async function  askQues(data) {
+//     const { token } = await new Promise((resolve) => {
+//         chrome.storage.local.get(['token'], (result) => {
+//             resolve(result)
+//         })
+//     })
+
+//     // 当token不存在时,执行退出登录
+//     // if (!token && !config.url.includes('/login')) {
+//     //   return Promise.reject('请先登录')
+//     // }
+//     return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/model/question', {
+//         method: 'POST',
+//         headers: {
+//             ['Authorization'] : 'Bearer ' + token
+//         },
+//         body: JSON.stringify(data),
+//     });
+// }
+
+export function getFormKey(data) {
+    return request({
+        url: '/ai/model/callAlgorithm',
+        method: 'post',
+        data: data
+    })
+}

+ 1 - 1
src/entrypoints/background.js

@@ -3,7 +3,7 @@ export default defineBackground(() => {
   let arr = []
   let token = ''
   let userId = ''
-  const url = 'http://192.168.1.166:7777/behavior/user/adds'
+  const url = import.meta.env.VITE_APP_BASE_API +  '/behavior/user/adds'
   // Executed when background is loaded
   chrome.sidePanel
     .setPanelBehavior({ openPanelOnActionClick: true })

+ 7 - 7
src/entrypoints/content.js

@@ -20,10 +20,10 @@ export default defineContentScript({
     let page = document.getElementsByTagName('body')[0]
     const src = chrome.runtime.getURL('images/begin.png')
     window.pageAnalyzer = new PageAnalyzer()
-    window.onload = () => {
-      chrome.runtime.sendMessage(getPageInfo())
-      console.log(document.title)
-    }
+    // window.onload = () => {
+    //   chrome.runtime.sendMessage(getPageInfo())
+    //   console.log(document.title)
+    // }
     let form = null
     let formChildren = []
     let excelDataA = {}
@@ -35,7 +35,7 @@ export default defineContentScript({
         if (e.target.outerHTML.length > 1000) return
         tag = e.target.outerHTML
         innerText = e.target.innerText
-        console.log(e.target.outerHTML.length, e.target.outerHTML);
+        // console.log(e.target.outerHTML.length, e.target.outerHTML);
         chrome.runtime.sendMessage({
           type: 'CLICK_EVENT',
           data: {
@@ -57,7 +57,7 @@ export default defineContentScript({
       if (e.target.outerHTML.length > 1000) return
       tag = e.target.outerHTML
       innerText = e.target.innerText
-      console.log(e.target.outerHTML.length, e.target.outerHTML);
+      // console.log(e.target.outerHTML.length, e.target.outerHTML);
       clearTimeout(timer)
       chrome.runtime.sendMessage({
         type: 'CLICK_EVENT',
@@ -132,7 +132,7 @@ export default defineContentScript({
             item.style.border = '2px solid red'
             function handleClick(e) {
               e.stopPropagation()
-              console.log(this.outerHTML)
+              // console.log(this.outerHTML)
               for (const form of forms) {
                 form.style.border = 'none'
                 form.removeEventListener('click', handleClick, true)

+ 365 - 182
src/entrypoints/sidepanel/Chat.vue

@@ -1,21 +1,17 @@
 <!-- Chat.vue -->
 <template>
-  <div class="chat-container items-center">
-    <div v-if="!messages.length" class="message-list">
+  <div class="chat-container">
+    <div v-if="!messages.length && !msgLoading" class="message-list">
       <pageMask />
     </div>
     <!-- 消息列表 -->
-    <div class="message-list w-full" 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>
-        // 添加一个 ref 用于获取消息列表容器
-        const messagesContainer = ref(null)
-
-        // 修改模板中的消息列表 div,添加 ref
         <div class="messages" ref="messagesContainer">
           <div v-for="(message, index) in messages" :key="index"
             :class="['message-item', message.role === 'user' ? 'self' : 'other']">
@@ -42,7 +38,7 @@
                 </span>
               </div>
               <div class="timestamp ">{{ moment(message.sortKey).format('MM-DD HH:mm') }}
-                <span v-if="message.add" style="cursor: pointer;" @click="handleInput">填充</span>
+                <!-- <span v-if="message.add" style="cursor: pointer;" @click="handleInput">填充</span> -->
               </div>
             </div>
           </div>
@@ -51,16 +47,23 @@
       <ScrollToBottom :target="scrollbar" ref="scrollToBottomRef" />
     </div>
 
-    <Tools @read-click="readClick" @upload-file="handleUpload" @handle-capture="handleCapture" @his-records="hisRecords"
+    <Tools :upload="type === FunctionList.File_Operation || !!formInfo" @read-click="readClick"
+      @upload-file="(file) => createFileObj(file)" @handle-capture="handleCapture" @his-records="hisRecords"
       @add-new-dialogue="addNewDialogue" @handle-current-change="handleCurrentChange"
       @handel-intelligent-filling-click="handelIntelligentFillingClick" />
 
     <div class="w-full max-w-[720px] p-[0_12px_12px]">
       <!-- 输入区域 -->
-      <div class="input-area w-full">
+      <div class="input-area">
+        <el-icon class="closeShow" :style="{ display: isShowPage ? 'block' : 'none' }" size="16px" color="#909399"
+          @click="closePageInfo">
+          <CircleClose />
+        </el-icon>
         <div v-show="isShowPage" style="border-bottom: 1px solid #F0F0F0;">
           <div class="card_list">
             <template v-for="(v, i) in pageInfoList" :key="i">
+
+
               <div class="card_image " v-if="v.type === 'image'">
                 <el-image v-loading="v.isUpload" class="w-full h-full p-0.5 object-cover" :src="v.url" :zoom-rate="1.2"
                   :max-scale="7" :min-scale="0.2" :preview-src-list="[v.url]" :initial-index="4" fit="cover" />
@@ -69,10 +72,14 @@
                 </el-icon>
               </div>
               <div v-else :class="`card-content ${pageInfoList.length > 1 ? 'card_width' : ''}`">
-                <img :src="v?.favIconUrl" style="width: 24px;display: block" />
+                <div class="loading-more-indicator" v-if="v.loading">
+                  <div class="loading-spinner"></div>
+                </div>
+                <img v-else :src="v?.favIconUrl" style="width: 24px;display: block" />
+
                 <div class="title-wrapper">
                   <span class="els title-scroller">{{ v?.title }}</span>
-                  <span class="els url-scroller">{{ v?.url }}</span>
+                  <span class="els url-scroller">{{ v.loading ? v?.state : v?.url }}</span>
                 </div>
                 <el-icon class="closeIcon" size="16px" color="#909399" @click="deletePageInfo(i)">
                   <CircleClose />
@@ -80,29 +87,35 @@
               </div>
             </template>
 
+            <div v-show="!pageInfoList.length && type === FunctionList.Intelligent_Form_filling">智能填表</div>
           </div>
 
-          <div v-show="type !== FunctionList.Intelligent_Form_filling" class="card-btn">
-            <el-tooltip content="总结当前页面" placement="top">
-              <el-button round @click="handleSummary">总结</el-button>
+          <div class="card-btn">
+            <el-tooltip content="总结" placement="top">
+              <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 :disabled="disabledBtn"
+                v-if="type === FunctionList.Intelligent_Form_filling && pageInfoList.length" round
+                @click="handleSummaryFile">抽取</el-button>
             </el-tooltip>
           </div>
         </div>
 
         <el-input ref="textareaRef" v-model="inputMessage" type="textarea" :rows="3" placeholder="输入消息..."
-          @keyup.enter="handleAsk" />
+          @keyup.enter="() => handleAsk()" />
         <div class="chat_area_op">
-          <el-button
-            :style="`background-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'};border-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'}`"
-            v-if="inputMessage.trim() || !sendLoading" type="primary" circle @click="handleAsk"
-            :disabled="!inputMessage.trim() || sendLoading">
-            <svg-icon icon-class="send" color="#000000" />
-          </el-button>
-          <el-button style="background-color:#ffffff;border:2px solid rgb(134 143 153);" v-else circle
+
+          <el-button v-if="sendLoading" style="background-color:#ffffff;border:2px solid rgb(134 143 153);" circle
             @click="handleStopAsk">
             <svg-icon icon-class="stop" color="red" />
           </el-button>
-
+          <el-button v-else
+            :style="`background-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'};border-color:${inputMessage.trim() ? '#4d6bfe' : '#d6dee8'}`"
+            type="primary" circle @click="() => handleAsk()" :disabled="disSend">
+            <svg-icon icon-class="send" color="#000000" />
+          </el-button>
         </div>
       </div>
 
@@ -120,12 +133,10 @@ import { ElScrollbar, ElAvatar, ElInput, ElButton } from 'element-plus'
 import moment from 'moment'
 import fileLogo from '@/assets/svg/file.svg'
 import {
-  buildExcelUnderstandingPrompt,
-  getSummaryPrompt,
   getFileContent,
   buildObjPrompt,
-  modelFileUpload,
-  controllerList
+  controllerList,
+  formatMessage
 } from '@/entrypoints/sidepanel/utils/ai-service.js'
 import { storeToRefs } from 'pinia'
 import { ElMessage } from 'element-plus'
@@ -138,15 +149,15 @@ import ScrollToBottom from '@/entrypoints/sidepanel/component/ScrollToBottom.vue
 import formTable from '@/entrypoints/sidepanel/component/formTable.vue'
 import userAvatar from '@/assets/images/user.png'
 import avatar from '@/public/icon/32.png'
-import { mockData, startMsg, mockData2, options, FunctionList } from '@/entrypoints/sidepanel/mock'
+import { options, FunctionList } from '@/entrypoints/sidepanel/mock'
 import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResizeTextarea.ts'
-import { getPageInfo, getXlsxValue, handleInput } from './utils/index.js'
+import { getPageInfo, getPageInfoClean, handleInput, downloadFile } from './utils/index.js'
 import { useMsgStore } from '@/store/modules/msg.ts'
 import { useUserStore } from '@/store/modules/user'
-import { putChat } from "@/api/index.js";
+import { putChat, uploadFile } from "@/api/index.js";
+import { askQues, getFormKey } from '@/api/modal.js'
 import { debounce } from 'lodash'
 const userStore = useUserStore()
-import { getChatDetail } from '@/api/index.js'
 // 在其他状态变量附近添加
 const isLoadingMore = ref(false)
 import { v4 as uuidv4 } from 'uuid';
@@ -157,30 +168,32 @@ const xlsxData = ref({})
 const isShowPage = ref(false)
 const tareRef = useTemplateRef('textareaRef')
 const drawerRef = useTemplateRef('historyComponentRef')
+const disSend = computed(() => {
+  if (pageInfoList.value.length && type.value === FunctionList.Intelligent_Form_filling) return true
+  if (sendLoading.value) return true
+  if (inputMessage.value.trim()) return false
+  return true
+})
 // 获取父组件提供的 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 {
   taklToHtml,
   sendLoading,
   type,
-  streamRes,
-  getFormKeyAndValue,
-  requestFlowFn,
   fetchRes
+
 } = useMsg(scrollbar)
 const inputMessage = ref('')
 const pageInfo = ref('')
+const summaryHtml = ref(false)
 function handleStopAsk() {
-  if (type.value === FunctionList.Intelligent_Form_filling) {
-    inputMessage.value = ''
-    pageInfoList.value = []
-    isShowPage.value = false
-    taklToHtml.value = false
-    type.value = FunctionList.File_Operation
-  }
+  inputMessage.value = ''
+  pageInfoList.value = []
+  isShowPage.value = false
+  taklToHtml.value = false
+  type.value = FunctionList.File_Operation
   controllerList.value[0].abort()
   controllerList.value = []
   sendLoading.value = false
@@ -230,7 +243,6 @@ const handleScrollToTop = debounce(async function () {
  * @param {string} type 发送的类型 document 、text
  * **/
 async function addMessage(msg, raw, type) {
-  console.log(msg);
   const newMessage = reactive({
     id: +new Date(),
     type: type || '',
@@ -241,7 +253,9 @@ async function addMessage(msg, raw, type) {
     sortKey: moment().valueOf(),
     role: 'user',
     conversationId: msgUuid.value,
-    addToHistory: `${!taklToHtml.value}`
+    addToHistory: `${!taklToHtml.value}`,
+    redisKey: undefined,
+    fileId: undefined
   })
   if (type === 'document') {
     newMessage.content = msg
@@ -249,60 +263,66 @@ async function addMessage(msg, raw, type) {
   }
   if (!msg) return
   messages.value.push(newMessage)
-  await nextTick(() => {
-    scrollbar.value?.setScrollTop(99999)
+  nextTick(() => {
+    if (scrollbar.value && scrollbar.value.wrapRef) {
+      scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
+    }
   })
   return newMessage
 }
 
 const handleSummary = async () => {
-  let params = []
-  if (AIModel.value.file === true) {
-    pageInfoList.value.forEach((pageInfo) => {
-      params.push({ role: 'system', content: `fileid://${pageInfo.fileId}` })
-    })
-    params.push({
-      role: 'user', content: `
-    请根据这几个文件的文本内容综合分析总结
-    要求:
-    1.抽取文件的文本内容
-    2.对每个文件中的文本内容进行分别总结
-    3.对步骤二总结的内容进行综合总结`
-    })
-  } else {
-    let tempStr = ''
-    pageInfoList.value.forEach((pageInfo, index) => {
-      pageInfo.handleText = getSummaryPrompt(pageInfo.content)
-      tempStr += `第${index + 1}段:${pageInfo.handleText}\n`
-    })
-    params = [{
-      role: 'user',
-      content: `请根据以下这几个内容综合总结出结果:
-      ${tempStr}要求:
-      1. 用简洁清晰的语言提取所有的核心要点
-      2. 保持客观中立的语气
-      3. 按重要性排序
-      4. 返回内容做好换行,以及展示样式
-      5. 请以"以下是对该文件内容的总结:"开头,然后用要点的形式列出主要内容。
-      6. 对这几个内容进行综合分析及联想`
-    }]
-  }
-  const msg = await addMessage(JSON.stringify(pageInfoList.value), JSON.stringify(params.map(_ => _.content)), 'document')
-  putChat(msg)
-  if (requestFlowFn) {
-    isShowPage.value = false
-    taklToHtml.value = false
+  if (sendLoading.value) return
+  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)
+    createWS(userMsg)
+    formInfo.value = ''
+    //   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 res = await requestFlowFn(params)
+    //   const resForm = await askQues({
+    //   conversationId: msgUuid.value,
+    //   modelName: '通义千问-Max',
+    //   question: buildObjPrompt(xlsxData.value,formInfo.value),
+    //   id: '699637194561691650',
+    //   // redisKey:msg.redisKey
+    //   })
+    // return console.log(resForm);
+
+
+  } catch (error) {
+    console.log(error);
+
+  } finally {
+    //  sendLoading.value = false
+    // type.value = FunctionList.File_Operation
   }
 }
-
 async function handelIntelligentFillingClick() {
   isShowPage.value = true
-  taklToHtml.value = true
-  const tempPageInfo = await getPageInfo()
-  pageInfo.value = tempPageInfo
-  pageInfoList.value = [tempPageInfo]
+  // taklToHtml.value = true
+  // const tempPageInfo = await getPageInfo()
+  // pageInfo.value = tempPageInfo
+  // pageInfoList.value = [tempPageInfo]
   inputMessage.value = '/智能填表 '
   type.value = FunctionList.Intelligent_Form_filling
 }
@@ -324,6 +344,7 @@ function handleCurrentChange(e) {
 
 async function handleCurrentData(e) {
   drawerRef.value.drawer = false
+  isShowPage.value = false
   if (!e) {
     addNewDialogue()
     return
@@ -333,14 +354,29 @@ async function handleCurrentData(e) {
   msgUuid.value = e
   chrome.storage.local.set({ msgUuid: msgUuid.value })
   await msgStore.initMsg()
-  scrollbar.value?.setScrollTop(99999)
+  nextTick(() => {
+    if (scrollbar.value && scrollbar.value.wrapRef) {
+      scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
+    }
+  })
 }
+let SystemMsg = null
 
+watchEffect(() => {
+  if (!isShowPage.value) {
+    summaryHtml.value = false
+    type.value = FunctionList.File_Operation
+    SystemMsg = null
+    inputMessage.value = ''
+  }
+})
+const htmlIcon = ref('')
 async function readClick() {
   if (type.value === FunctionList.Intelligent_Form_filling) {
     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)) {
@@ -348,14 +384,20 @@ async function readClick() {
     return
   }
   const tempPageInfo = await getPageInfo()
-  if (AIModel.value.file === true) {
-    const blob = new Blob([tempPageInfo.content.mainContent], { type: 'text/plain' })
-    const file = new File([blob], tempPageInfo.title + '.txt', { type: 'text/plain' })
-    tempPageInfo.fileId = await modelFileUpload(file)
-  }
-  pageInfoList.value.unshift(tempPageInfo)
+  htmlIcon.value = tempPageInfo.favIconUrl
+  const blob = new Blob([tempPageInfo.content.mainContent], { type: 'text/plain' })
+  const file = new File([blob], '页面' + '.txt', { type: 'text/plain' })
+  // console.log(file);
+  // 下载文件到本地
+  // downloadFile(file);
+  console.log(tempPageInfo);
+
+  await createFileObj(file)
+  // await handleUpload(file,tempPageInfo.title + '.txt')
 }
 
+// 添加下载文件的函数
+
 function deletePageInfo(i) {
   pageInfoList.value.splice(i, 1)
   if (pageInfoList.value.length === 0) {
@@ -371,35 +413,157 @@ function addNewDialogue() {
     ElMessage.warning('已经是新对话')
     return
   }
+  summaryHtml.value = false
+  type.value = FunctionList.File_Operation
   isShowPage.value = false
   taklToHtml.value = false
   messages.value = []
   page.value = 1
   pageInfoList.value = []
   msgUuid.value = uuidv4()
+  hasNext.value = false
   chrome.storage.local.set({ msgUuid: msgUuid.value })
 }
-
-async function handleAsk() {
-  const str = inputMessage.value.trim()
-  inputMessage.value = ''
+const disabledBtn = computed(() => {
+  return !!(pageInfoList.value.find(_ => _.loading))
+})
+function closePageInfo() {
+  pageInfoList.value = []
+  type.value = FunctionList.File_Operation
+  isShowPage.value = false
+  formInfo.value = null
+}
+async function handleAsk(value) {
   if (sendLoading.value) return
-  const msg = await addMessage(str)
-  putChat(msg)
+  const str = value ?? inputMessage.value.trim()
+  inputMessage.value = ''
   if (type.value === FunctionList.Intelligent_Form_filling) {
-    const res = await fetchRes(str)
+    const msg = await addMessage(str)
+    await putChat(msg)
+    const [res, obj] = await fetchRes(str)
     if (res.status === 'ok') {
+      console.log(obj);
+
       formInfo.value = res.data
-      console.log(res, 55558)
+      SystemMsg = obj
     } else {
       type.value = FunctionList.File_Operation
-      isShowPage.value = false
-      taklToHtml.value = false
-      pageInfoList.value = []
     }
-  } else streamRes(taklToHtml.value)
+    return
+  }
+  if (sendLoading.value) return
+  let msg = null
+  if (pageInfoList.value.length) {
+    msg = await addMessage(JSON.stringify([...pageInfoList.value, { type: 'text', value: str }]), str, 'document')
+    msg.fileId = pageInfoList.value[0].fileId
+    msg.redisKey = pageInfoList.value[0].redisKey
+  } else msg = await addMessage(str)
+  pageInfoList.value = []
+  isShowPage.value = false
+  await putChat(msg)
+  createWS(msg)
 }
+/**
+ * 
+ * @param msg 用户消息对象,调用askQues时需要
+ */
+async function createWS(msg) {
+  sendLoading.value = true
+  const obj = reactive({
+    type: type.value === FunctionList.Intelligent_Form_filling ? 'form' : '',
+    rawContent: '',
+    senderId: -1,
+    receiverId: userStore.userInfo.id, //大模型
+    content: '',
+    sortKey: moment().valueOf(),
+    role: 'system',
+    conversationId: msgUuid.value,
+  })
+  try {
+    messages.value.push(obj)
+    nextTick(() => {
+      if (scrollbar.value && scrollbar.value.wrapRef) {
+        scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
+      }
+    })
+    const wsUrl = `${import.meta.env.VITE_API_WS_URL}/webSocket/clue/${msgUuid.value}`;
+    const socket = new WebSocket(wsUrl);
+    askQues({
+      conversationId: msgUuid.value,
+      modelName: '通义千问-Max',
+      question: type.value === FunctionList.File_Operation ? msg.rawContent : buildObjPrompt(xlsxData.value, formInfo.value),
+      id: '699637194561691650',
+      redisKey: msg.redisKey
+    }).catch(res => {
+      obj.rawContent = '接口出错,请重试。'
+      obj.content = '接口出错,请重试。'
+      socket.close()
+    })
+    socket.onmessage = (event) => {
+      try {
+        if (event.data === '') {
+          console.log(23223);
 
+          socket.close()
+          if (type.value === FunctionList.Intelligent_Form_filling) {
+            const formResult = obj.rawContent
+            let form = null
+            if (formResult.includes('json')) form = JSON.parse(formResult.split('json')[1].split('```')[0])
+            else form = JSON.parse(formResult)
+            sendLoading.value = false
+            console.log(xlsxData.value, form);
+
+            handleInput(xlsxData.value, form)
+          }
+          isShowPage.value = false
+        } else {
+          obj.rawContent += event.data;
+          obj.content = obj.type === 'form' ? obj.rawContent : formatMessage(obj.rawContent);
+          // 滚动到底部
+          nextTick(() => {
+            if (scrollbar.value && scrollbar.value.wrapRef) {
+              scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight);
+            }
+          });
+        }
+      } catch (error) {
+        console.error('解析消息出错:', error);
+      }
+    };
+    socket.onerror = (error) => {
+      console.error('WebSocket 错误:', error);
+      socket.close();
+    };
+    socket.onclose = () => {
+      console.log('WebSocket 连接已关闭');
+      sendLoading.value = false;
+      putChat({
+        ...obj,
+        content: ''
+      });
+      sendLoading.value = false
+      controllerList.value = []
+      type.value = FunctionList.File_Operation
+    };
+    // 添加到控制器列表,以便可以在需要时中断连接
+    controllerList.value.push({
+      abort: () => {
+        socket.close();
+      }
+    });
+  } catch (error) {
+    console.error(error);
+    sendLoading.value = false;
+    // 显示错误消息
+    obj.content = '连接失败,请重试';
+    putChat({
+      ...obj,
+      content: ''
+    });
+  } finally {
+
+  }
+}
 function handleCapture() {
   chrome.runtime.sendMessage({
     type: 'SCREENSHOT',
@@ -417,87 +581,103 @@ function handleCapture() {
     }
   })
 }
-
 function hisRecords() {
   drawerRef.value.drawer = true
 }
+const createFileObj = async (file) => {
+  handleUpload(file)
+}
 const handleUpload = async (file) => {
-  if (AIModel.value.file === true) {
-    const id = await modelFileUpload(file)
-    pageInfoList.value.unshift({
-      fileId: id,
-      title: file.name,
-      url: 'File',
-      favIconUrl: fileLogo
-    })
-    isShowPage.value = true
-    taklToHtml.value = true
-    return
-  }
-  if (type.value === FunctionList.Intelligent_Form_filling) {
-    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]
-      })
-      const msg = addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
-      putChat(msg)
-      const { rawContent, status } = await streamRes()
-      if (status === 'ok') {
-        let form = []
-        if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
-        else form = JSON.parse(rawContent)
-        handleInput(xlsxData.value, form)
-      }
-    } else {
-      const msg = await addMessage(`文件上传中`)
-      try {
-        sendLoading.value = true
-        putChat(msg)
-        const data = await getFileValue(file)
-        msg.content = `已上传文件:${file.name}`
-        msg.rawContent = data
-        const res2 = await getFormKeyAndValue(data, formInfo.value)
-        xlsxData.value = res2.data
-        msg.rawContent = buildObjPrompt(res2.data, formInfo.value)
-        const { rawContent, status } = await streamRes()
-        if (status === 'ok') {
-          let form = []
-          if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
-          else form = JSON.parse(rawContent)
-          handleInput(xlsxData.value, form)
-        }
-      } catch (error) {
-        msg.content = '文件解析出错'
-      } finally {
-        sendLoading.value = false
-        // putChat({
-        //   id: msgUuid.value,
-        //   msg: msg
-        // })
-      }
+  const obj = reactive({
+    title: file.name,
+    state: '上传中...',
+    favIconUrl: fileLogo,
+    loading: true,
+    url: '',
+    fileId: '',
+    redisKey: '',
+    content: {
+      mainContent: ''
     }
-    type.value = FunctionList.File_Operation
-    isShowPage.value = false
-    taklToHtml.value = false
+  })
+  if (type.value === FunctionList.Intelligent_Form_filling) {
     pageInfoList.value = []
-    return
   }
-  if (type.value === FunctionList.File_Operation) {
+  if (summaryHtml.value) obj.favIconUrl = htmlIcon.value
+  isShowPage.value = true
+  // const obj = reactive({
+  //   title: file.name,
+  //   state: '上传中...',
+  //   favIconUrl: fileLogo,
+  //   loading: true,
+  //   url:'',
+  //   fileId: '',
+  //   redisKey:'',
+  //   content: {
+  //     mainContent: ''
+  //   }
+  // })
+  pageInfoList.value = [obj]
+  try {
     let formData = new FormData()
-    formData.append('file', file)
-    const res = await getFileContent(formData)
-    pageInfoList.value.unshift({
-      title: file.name,
-      url: 'File',
-      favIconUrl: fileLogo,
-      content: {
-        mainContent: res.data
+    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
+      // const fileExtension = file.name.split('.').pop().toLowerCase()
+      const result = await getFormKey({
+        body: formInfo.value,
+        input_data: res.data.data
+      })
+      xlsxData.value = JSON.parse(result.data)
+      // 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,
+      //   input_data:res.data.data
+      // })
+      // xlsxData.value = JSON.parse(result.data)
+      //   }
+    }
+
+    nextTick(() => {
+      if (scrollbar.value && scrollbar.value.wrapRef) {
+        scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
       }
     })
-    isShowPage.value = true
+    if (type.value === FunctionList.File_Operation) {
+      obj.loading = false
+      obj.url = res.data.url
+      obj.redisKey = res.data.redisKey
+      obj.fileId = res.data.file.id
+      obj.url = res.data.file.url
+    }
+    obj.loading = false
+    console.log(SystemMsg);
+
+  } catch (error) {
+    if (SystemMsg) {
+      SystemMsg.content = '数据上传出错,请重试'
+      SystemMsg.rawContent = '数据上传出错,请重试'
+    }
+    console.log(error);
+    ElMessage.error('上传出错')
+    pageInfoList.value = []
+    isShowPage.value = false
+  } finally {
+    SystemMsg && putChat({
+      ...SystemMsg,
+      content: '',
+    })
+    SystemMsg = null
   }
 }
 async function getFileValue(file) {
@@ -526,13 +706,16 @@ onMounted(async () => {
     }
   })
   nextTick(() => {
-    scrollbar.value?.setScrollTop(99999)
+    if (scrollbar.value && scrollbar.value.wrapRef) {
+      scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
+    }
   })
 })
 </script>
 
 <style lang="scss" scoped>
 @use '@/entrypoints/sidepanel/css/chat.scss';
+@use '@/entrypoints/sidepanel/css/markdown.scss';
 
 .loading-more-indicator {
   display: flex;

+ 2 - 2
src/entrypoints/sidepanel/component/ScrollToBottom.vue

@@ -19,7 +19,7 @@ defineExpose({
 
 <template>
   <el-button v-show="showButton" :icon="CaretBottom" circle
-             @click="props.target.setScrollTop(99999)"
+             @click="props.target.setScrollTop(props.target.wrapRef.scrollHeight)"
              class="back_bottom" />
 </template>
 
@@ -30,4 +30,4 @@ defineExpose({
   right: 12px;
   font-size: 20px
 }
-</style>
+</style>

+ 13 - 4
src/entrypoints/sidepanel/component/document.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script setup lang="js">
 import { onMounted, computed } from 'vue'
 
 const props = defineProps({
@@ -12,20 +12,29 @@ const props = defineProps({
   }
 })
 const docZil = computed(() => {
-  return JSON.parse(props.content)
+  const arr = JSON.parse(props.content)
+  arr.pop()
+  return arr
+})
+const value = computed(() => {
+   const arr = JSON.parse(props.content)
+ return  arr[arr.length - 1].value
 })
 </script>
 
 <template>
   <div class="document_r">
-    <div v-for="(item,i) in docZil" :key="i" class="document_content">
+    <div v-for="(item,i) in docZil" :key="i" class="document_content" @click="() => { 
+      console.log(item);
+      
+    }">
       <img class="document_img" :src="item.favIconUrl" alt="">
       <div class="document_text">
         <p class="els" :title="item.title" style="font-weight: 900">{{ item.title }}</p>
         <p class="els" :title="item.url" style="color: rgba(96,98,102,0.77)">{{ item.url }}</p>
       </div>
     </div>
-    <p class="document_content1">总结</p>
+    <p class="document_content1">{{value}}</p>
   </div>
 
 </template>

+ 13 - 9
src/entrypoints/sidepanel/component/formTable.vue

@@ -10,14 +10,16 @@ const props = defineProps({
 })
 const show = ref(false)
 const tableData = computed(() => {
-  let arr = []
-  if (props.content.includes('json')) {
-    arr = JSON.parse(props.content.split('json')[1].split('```')[0])
-  }
-  else arr = JSON.parse(props.content)
-  if (arr[0].data) show.value = true
+  const startIndex = props.content.indexOf('{');
+const endIndex = props.content.lastIndexOf('}');
+
+// 2. 截取有效内容
+const jsonSlice = props.content.slice(startIndex, endIndex + 1);
+  let str = '[' + jsonSlice + ']'
+  const arr = JSON.parse(str)
+  if (arr[0]?.data) show.value = true
   return arr.map((_:any) => ({
-    label: _.label ?? _.findByValue,
+    label: !!_.label ?_.label : _.findByValue,
     source: _.excelColumn,
     value:_.data
   }))
@@ -25,12 +27,14 @@ const tableData = computed(() => {
 </script>
 
 <template>
-  <div class="mb-2" >抽取对应关系</div>
-  <el-table :data="tableData" style="width: 100%">
+  <div>
+    <div class="mb-2" >抽取对应关系</div>
+  <el-table v-if="tableData.length" :data="tableData" style="width: 100%">
     <el-table-column show-overflow-tooltip prop="label" label="表单项" width="100" />
     <el-table-column show-overflow-tooltip prop="source" label="数据源" width="100" />
     <el-table-column v-if="show" show-overflow-tooltip prop="value" label="数据值" width="100" />
   </el-table>
+  </div>
 
 </template>
 

+ 10 - 7
src/entrypoints/sidepanel/component/historyComponent.vue

@@ -5,7 +5,9 @@ import { ElMessageBox, ElMessage } from 'element-plus'
 import { getChatList,deleteChat } from '@/api/index.js'
 import { storeToRefs } from 'pinia';
 import { useMsgStore } from '@/store/modules/msg.ts'
+import { useUserStore } from '@/store/modules/user'
 
+const userStore = useUserStore()
 const drawer = ref(false)
 const count = ref(0)
 const input = ref('')
@@ -26,10 +28,10 @@ watch(drawer, (newVal) => {
     loading.value = true
     getChatList({
       page:1,
-      size:50
+      size: 50,
+      senderId:userStore.userInfo.id
     }).then(res => {
-      dataList.value = res.list
-      console.log(res.list);
+      dataList.value = res.data.list
     }).finally(res => loading.value = false)
   }
 })
@@ -63,9 +65,10 @@ function handleDeleteStore(e: any, item: any,name:string) {
       }
       getChatList({
         page: 1,
-        size: 50
+        size: 50,
+        senderId:userStore.userInfo.id
       }).then(res => {
-        dataList.value = res.list
+        dataList.value = res.data.list
       }).finally(res => loading.value = false)
     }).catch(() => {
     })
@@ -88,8 +91,8 @@ defineExpose({
     </template>
     <div style="height: 100%;overflow: hidden;" v-loading="loading">
       <div class="his_delete">
-        <el-input style="margin-right: 12px" v-model="input" placeholder="搜索" clearable :prefix-icon="Search"
-          :disabled="true" />
+        <!-- <el-input style="margin-right: 12px" v-model="input" placeholder="搜索" clearable :prefix-icon="Search"
+          :disabled="true" /> -->
         <!-- <el-tooltip effect="dark" content="删除全部" placement="top">
           <el-button :icon="Delete" circle @click="handleDeleteDB" />
         </el-tooltip> -->

+ 15 - 2
src/entrypoints/sidepanel/component/tools.vue

@@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia'
 import { options } from '@/entrypoints/sidepanel/mock'
 import { Reading, Upload, Paperclip, Scissor, AlarmClock, CirclePlus, Edit } from '@element-plus/icons-vue'
 import { useMsgStore } from '@/store/modules/msg'
+import { ElMessage } from 'element-plus'
 
 const { AIModel } = storeToRefs(useMsgStore())
 const selectInput = ref(null)
@@ -12,7 +13,19 @@ const emit = defineEmits(['readClick', 'uploadFile', 'handleCapture', 'addNewDia
 watchEffect(() => {
   selectInput.value = AIModel.value?.value
 })
+const handleUpload = (e) => {
+  if (!props.upload) {
+   e.stopPropagation()
+    ElMessage.warning('智能填表需要先发送指令,再上传文件')
+  }
+  
+}
+const props = defineProps({
+  upload: {
+    type: Boolean,
+  },
 
+})
 </script>
 
 <template>
@@ -25,12 +38,12 @@ watchEffect(() => {
         </el-option-group>
       </el-select>
       <el-tooltip effect="dark" content="阅读此页,开启后将会根据左侧网页中的内容做出回答" placement="top">
-        <el-button v-permission="['agent:summary']" class="tools_btn" link :icon="Reading" @click="emit('readClick')" />
+        <el-button class="tools_btn" link :icon="Reading" @click="emit('readClick')" />
       </el-tooltip>
       <el-upload style="display:inline-block" :before-upload="(file: any) => emit('uploadFile', file)" :multiple="false"
         name="file" :show-file-list="false" :accept="'.xlsx,.pdf,.doc,.docx'">
         <el-tooltip effect="dark" content="文件上传" placement="top">
-          <el-button v-permission="['agent:upload']" class="tools_btn" link :icon="Paperclip" />
+          <el-button class="tools_btn" link :icon="Paperclip" @click="handleUpload"/>
         </el-tooltip>
       </el-upload>
       <!-- <el-tooltip effect="dark" content="截屏" placement="top">

+ 34 - 5
src/entrypoints/sidepanel/css/chat.scss

@@ -1,7 +1,10 @@
 .els {
-  white-space: nowrap; /* 强制文本不换行 */
-  overflow: hidden; /* 隐藏溢出内容 */
-  text-overflow: ellipsis; /* 显示省略号 */
+  white-space: nowrap;
+  /* 强制文本不换行 */
+  overflow: hidden;
+  /* 隐藏溢出内容 */
+  text-overflow: ellipsis;
+  /* 显示省略号 */
 }
 
 .chat-container {
@@ -195,6 +198,7 @@
   }
 
   @keyframes pulse {
+
     0%,
     100% {
       transform: scale(0.8);
@@ -214,6 +218,8 @@
   background-color: #fff;
   border: 1px solid rgba(102, 102, 102, 0.3);
   border-radius: 16px;
+  margin: 0 12px 12px;
+  position: relative;
 
   .card_list {
     display: flex;
@@ -291,9 +297,32 @@
     .title-scroller.scroll {
       animation: scrollTitle 10s linear infinite;
     }
+
+    .closeIcon {
+      display: none;
+      cursor: pointer;
+      position: absolute;
+      right: -5px;
+      top: -5px;
+      background-color: #ffffff;
+      border-radius: 50%;
+      z-index: 1;
+    }
   }
 
-  .card_image:hover, .card-content:hover {
+  .closeShow {
+    display: none;
+    position: absolute;
+    right: -5px;
+    top: -5px;
+    background-color: #ffffff;
+    border-radius: 50%;
+    z-index: 1;
+    cursor: pointer;
+  }
+
+  .card_image:hover,
+  .card-content:hover {
     .closeIcon {
       display: block;
     }
@@ -339,4 +368,4 @@
 .buttom-clicked {
   color: #409eff;
   border-color: rgb(197.7, 225.9, 255);
-}
+}

+ 152 - 0
src/entrypoints/sidepanel/css/markdown.scss

@@ -0,0 +1,152 @@
+/* Markdown 样式 */
+.content {
+  font-size: 14px;
+  line-height: 1.6;
+  word-break: break-word;
+  
+  h1, h2, h3, h4, h5, h6 {
+    margin-top: 24px;
+    margin-bottom: 16px;
+    font-weight: 600;
+    line-height: 1.25;
+  }
+  
+  h1 { font-size: 2em; }
+  h2 { font-size: 1.5em; }
+  h3 { font-size: 1.25em; }
+  
+  p {
+    margin-top: 0;
+    margin-bottom: 16px;
+  }
+  
+  a {
+    color: #0366d6;
+    text-decoration: none;
+    
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+  
+  img {
+    max-width: 100%;
+    box-sizing: border-box;
+  }
+  
+  blockquote {
+    padding: 0 1em;
+    color: #6a737d;
+    border-left: 0.25em solid #dfe2e5;
+    margin: 0 0 16px 0;
+  }
+  
+  ul, ol {
+    padding-left: 2em;
+    margin-top: 0;
+    margin-bottom: 16px;
+  }
+  
+  code {
+    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+    padding: 0.2em 0.4em;
+    margin: 0;
+    font-size: 85%;
+    background-color: rgba(27, 31, 35, 0.05);
+    border-radius: 3px;
+  }
+  
+  pre {
+    margin-top: 0;
+    margin-bottom: 16px;
+    padding: 16px;
+    overflow: auto;
+    font-size: 85%;
+    line-height: 1.45;
+    background-color: #f6f8fa;
+    border-radius: 3px;
+    
+    code {
+      padding: 0;
+      margin: 0;
+      font-size: 100%;
+      word-break: normal;
+      white-space: pre;
+      background: transparent;
+      border: 0;
+    }
+  }
+  
+  .code-block {
+    position: relative;
+    margin: 16px 0;
+    border-radius: 6px;
+    overflow: hidden;
+    
+    .code-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 8px 16px;
+      background-color: #f1f1f1;
+      border-bottom: 1px solid #ddd;
+      
+      .code-language {
+        font-size: 12px;
+        font-weight: 600;
+        color: #333;
+      }
+      
+      .copy-button {
+        padding: 2px 8px;
+        font-size: 12px;
+        background-color: #fff;
+        border: 1px solid #ddd;
+        border-radius: 4px;
+        cursor: pointer;
+        
+        &:hover {
+          background-color: #f9f9f9;
+        }
+      }
+    }
+  }
+  
+  .table-container {
+    overflow-x: auto;
+    margin-bottom: 16px;
+    
+    .responsive-table {
+      border-collapse: collapse;
+      width: 100%;
+      
+      th, td {
+        padding: 8px 13px;
+        border: 1px solid #dfe2e5;
+      }
+      
+      th {
+        background-color: #f6f8fa;
+        font-weight: 600;
+      }
+      
+      tr:nth-child(even) {
+        background-color: #f8f8f8;
+      }
+    }
+  }
+  
+  .math-inline {
+    font-style: italic;
+    padding: 0 3px;
+  }
+  
+  .math-block {
+    display: block;
+    margin: 16px 0;
+    padding: 16px;
+    text-align: center;
+    background-color: #f9f9f9;
+    border-radius: 4px;
+  }
+}

+ 24 - 10
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -79,10 +79,12 @@ export function useMsg(scrollbar?: any) {
         ({ status, data }) => {
           if (status === 'error') {
             obj.content = '当前页面未找到表单'
+            obj.rawContent = '当前页面未找到表单'
             res({ status })
           }
           if (status === 'ok') {
             obj.content = '请上传数据'
+            obj.rawContent = '请上传数据'
             res({ status, data })
           }
           if (status === 'select') {
@@ -93,7 +95,7 @@ export function useMsg(scrollbar?: any) {
                 console.log('收到一次性消息:', message.data)
                 res({ status: 'ok', data: message.data })
                 obj.content = '请上传数据'
-                console.log(565656)
+                obj.rawContent = '请上传数据'
                 // 销毁监听器(确保只触发一次)
                 chrome.runtime.onMessage.removeListener(handle)
               }
@@ -121,11 +123,15 @@ export function useMsg(scrollbar?: any) {
     })
     messages.value.push(obj)
     msg = msg.split('/智能填表')[1]
-    nextTick(() => scrollbar.value?.setScrollTop(99999))
+    nextTick(() => {
+      if (scrollbar.value && scrollbar.value.wrapRef) {
+        scrollbar.value.setScrollTop(scrollbar.value.wrapRef.scrollHeight)
+      }
+    })
     if (!msg) {
       sendLoading.value = false
       const res = await awaitFindForm(obj)
-      return res
+      return [res, obj]
     }
     try {
       const res = await fetchDataAndProcess(msg, obj)
@@ -137,19 +143,27 @@ export function useMsg(scrollbar?: any) {
           }, 2000)
         )
         const res = await awaitFindForm(obj)
-        console.log(res, 34444)
-        return res
+        return [res, obj]
+      } else {
+        putChat({
+          ...msg,
+          content: '',
+        })
       }
     } catch (error) {
-      obj.content = '流程链执行出错'
-      return { status: 'error' }
-    } finally {
-      sendLoading.value = false
-      console.log(messages.value);
+      obj.content = '流程链执行出错,请重试'
       putChat({
         ...msg,
         content: '',
       })
+      return [{ status: 'error' }]
+    } finally {
+      sendLoading.value = false
+      console.log(messages.value);
+      // putChat({
+      //   ...msg,
+      //   content:'',
+      // })
     }
   }
   let str = ''

+ 56 - 2
src/entrypoints/sidepanel/utils/index.js

@@ -6,7 +6,41 @@ export function getPageInfo() {
   return new Promise((res, rej) => {
     chrome.runtime.sendMessage(
       {
-        type: 'FROM_SIDE_PANEL_TO_GET_PAGE_INFO'
+        type: 'FROM_SIDE_PANEL_TO_GET_PAGE_INFO',
+      },
+      (response) => {
+        if (chrome.runtime.lastError) {
+          console.error('消息发送错误:', chrome.runtime.lastError, 555)
+          rej(chrome.runtime.lastError)
+        } else {
+          res(response.data)
+        }
+      }
+    )
+  })
+}
+export function getPageInfoClean() {
+  return new Promise((res, rej) => {
+    chrome.runtime.sendMessage(
+      {
+        type: 'FROM_SIDE_PANEL_TO_GET_PAGE_INFO_CLEAN',
+      },
+      (response) => {
+        if (chrome.runtime.lastError) {
+          console.error('消息发送错误:', chrome.runtime.lastError, 555)
+          rej(chrome.runtime.lastError)
+        } else {
+          res(response.data)
+        }
+      }
+    )
+  })
+}
+export function getPageInfoClean() {
+  return new Promise((res, rej) => {
+    chrome.runtime.sendMessage(
+      {
+        type: 'FROM_SIDE_PANEL_TO_GET_PAGE_INFO_CLEAN',
       },
       (response) => {
         if (chrome.runtime.lastError) {
@@ -19,7 +53,6 @@ export function getPageInfo() {
     )
   })
 }
-
 export function handleInput(xlsxData, formMap) {
   chrome.runtime.sendMessage({
     type: 'FROM_SIDE_PANEL_TO_INPUT_FORM',
@@ -60,6 +93,27 @@ export function getXlsxValue(file) {
     }
   })
 }
+export function downloadFile(file) {
+  try {
+    // 创建一个下载链接
+    const downloadUrl = URL.createObjectURL(file);
+    // 创建一个a标签用于下载
+    const downloadLink = document.createElement('a');
+    downloadLink.href = downloadUrl;
+    downloadLink.download = file.name;
+    // 添加到文档中并触发点击
+    document.body.appendChild(downloadLink);
+    downloadLink.click();
+    // 清理
+    document.body.removeChild(downloadLink);
+    URL.revokeObjectURL(downloadUrl);
+
+    ElMessage.success(`文件 ${file.name} 已下载到本地`);
+  } catch (error) {
+    console.error('下载文件失败:', error);
+    ElMessage.error('下载文件失败');
+  }
+}
 
 export function domToCanvas() {
   return new Promise((resolve, reject) => {

+ 40 - 30
src/store/modules/msg.ts

@@ -4,12 +4,14 @@ import { getChatDetail } from '@/api/index'
 import {
   formatMessage,
 } from '@/entrypoints/sidepanel/utils/ai-service.js'
+import { v4 as uuidv4 } from 'uuid';
 export const useMsgStore = defineStore('msg', {
   state: () => ({
     msgUuid: <string>'',
     messages: <any[]>[],
     hasNext: true,
     page: 1,
+    msgLoading: false,
     pageInfoList: [],
     AIModel: <any>{},
     openai: <any>null,
@@ -17,38 +19,46 @@ export const useMsgStore = defineStore('msg', {
   }),
   actions: {
     async initMsg() {
-      console.log(this.page, this.hasNext, this.messages);
-      const { msgUuid } = await new Promise<any>((resolve) => {
-        chrome.storage.local.get(['msgUuid'], (result) => {
-          resolve(result)
+      try {
+        const { msgUuid } = await new Promise<any>((resolve) => {
+          chrome.storage.local.get(['msgUuid'], (result) => {
+            resolve(result)
+          })
         })
-      })
-      if (msgUuid) {
-        console.log(85556);
-        this.msgUuid = msgUuid
-        const res = await getChatDetail({
-          page: this.page,
-          size: 10,
-          sort: 'sortKey,desc',
-          conversationId: this.msgUuid
-        })
-        this.messages = res.list.reverse().map(_ => ({
-          id: _.id,
-          type: _.type,
-          rawContent: _.rawContent,
-          senderId: _.senderId,
-          receiverId: _.receiverId,
-          sortKey: _.sortKey,
-          role: _.role,
-          conversationId: _.conversationId,
-          addToHistory: _.addToHistory,
-          content: _.role === 'system' ? formatMessage(_.rawContent) : _.content
-        })).concat(this.messages)
-        if (this.messages.length < res.total) {
-          this.hasNext = true
-          this.page++
+        if (msgUuid) {
+          this.msgLoading = true
+          this.msgUuid = msgUuid
+          const res = await getChatDetail({
+            page: this.page,
+            size: 100,
+            sort: 'sortKey,desc',
+            conversationId: this.msgUuid
+          })
+          this.messages = res.data.list.reverse().map(_ => ({
+            id: _.id,
+            type: _.type,
+            rawContent: _.rawContent,
+            senderId: _.senderId,
+            receiverId: _.receiverId,
+            sortKey: _.sortKey,
+            role: _.role,
+            conversationId: _.conversationId,
+            addToHistory: _.addToHistory,
+            content: _.role === 'system' ? formatMessage(_.rawContent) : _.content
+          })).concat(this.messages)
+          if (this.messages.length < res.data.total) {
+            this.hasNext = true
+            this.page++
+          }
+          else this.hasNext = false
+        } else {
+          this.msgUuid = uuidv4()
+          chrome.storage.local.set({ msgUuid: this.msgUuid })
         }
-        else this.hasNext = false
+      } catch (error) {
+
+      } finally {
+        this.msgLoading = false
       }
     },
 

+ 1 - 1
src/store/modules/user.ts

@@ -125,7 +125,7 @@ export const useUserStore = defineStore('user', {
           ElMessage.success('登陆成功')
           return true
         } else {
-          ElMessage.error(res.message || '登录失败')
+          ElMessage.error(res.msg || '登录失败')
           return false
         }
       } catch (error: any) {

+ 20 - 5
src/utils/contentUtils.js

@@ -349,10 +349,24 @@ export function getPageInfo() {
       favIconUrl,
       title: document.title,
       url: window.location.href,
-      content: window.pageAnalyzer.analyzePage()
+      content: fallbackAnalysis()
     }
   }
 }
+function fallbackAnalysis() {
+  return {
+    title: document.title,
+    url: window.location.href,
+    mainContent: extractMainContent(),
+    excerpt: '',
+    siteName: new URL(window.location.href).hostname,
+    wordCount: 0
+  }
+}
+function extractMainContent() {
+  const content = document.body.cloneNode(true)
+  return cleanPage(content)
+}
 export function getFavicon() {
   // 尝试获取动态favicon
   const iconLinks = Array.from(document.querySelectorAll('link[rel*="icon"]'))
@@ -372,8 +386,8 @@ export function getFavicon() {
   const url = new URL(window.location.href)
   return `${url.protocol}//${url.hostname}/favicon.ico`
 }
-export function cleanPage2(body) {
-  const cloneBody = body.cloneNode(true)
+export function cleanPage2(element) {
+  const cloneBody = element.cloneNode(true)
   // 移除所有行内样式
   const elementsWithInlineStyle = cloneBody.querySelectorAll('[style]')
   elementsWithInlineStyle.forEach((element) => {
@@ -417,7 +431,7 @@ export function cleanPage2(body) {
   const regex = /\s+/g
 
   // 定义标准 HTML 属性集合 排除id class style href src target
-  const standardAttributes = new Set([
+  let standardAttributes = new Set([
     'id',
     'class',
     'alt',
@@ -444,7 +458,6 @@ export function cleanPage2(body) {
     'rel',
     'aria-*'
   ])
-
   // 创建一个临时容器
   const temp = document.createElement('div')
   temp.innerHTML = cloneBody.outerHTML
@@ -477,6 +490,8 @@ export function cleanPage2(body) {
 
   // 销毁临时容器
   temp.remove()
+  // console.log(cleanedHtml);
+  
   // content.outerHTML.trim().replace(/\s+/g, " ");
   return cleanedHtml
 }

+ 10 - 29
src/utils/page-analyzer.js

@@ -11,11 +11,7 @@ export default class PageAnalyzer {
   analyzePage(form = false) {
     try {
       // 检查Readability是否可用
-      if (typeof Readability === 'undefined') {
-        console.warn('Readability not loaded, falling back to basic extraction')
-        return this.fallbackAnalysis(form)
-      }
-
+      return this.fallbackAnalysis()
       // 创建文档副本以避免修改原始DOM
       const documentClone = document.cloneNode(true)
       // 初始化 Readability
@@ -40,38 +36,21 @@ export default class PageAnalyzer {
       return this.fallbackAnalysis()
     }
   }
-
   // 基础提取方法作为后备
-  fallbackAnalysis(iframe) {
+  fallbackAnalysis() {
     return {
       title: document.title,
       url: window.location.href,
-      mainContent: this.extractMainContent(iframe),
+      mainContent: this.extractMainContent(),
       excerpt: '',
       siteName: new URL(window.location.href).hostname,
       wordCount: 0
     }
   }
-
   // 基础的内容提取方法
-  extractMainContent(iframe) {
-    // 移除脚本、样式等
-
-    //提取页面的表单
-    if (iframe) {
-      const form = document.querySelector('form')
-      if (form) {
-        content.textContent = form.outerHTML
-      }
-    } else {
-      const content = document.body.cloneNode(true)
-      return cleanPage(content)
-      // return document.documentElement.outerHTML
-      // .trim().replace(/\s+/g, " ")
-      // content
-      //     .querySelectorAll("script, style, iframe, nav, header, footer,svg")
-      //     .forEach((el) => el.remove());
-    }
+  extractMainContent() {
+    const content = document.body.cloneNode(true)
+    return cleanPage(content)
   }
 }
 
@@ -107,8 +86,8 @@ export function cleanPage(body) {
   })
 
   // 可选择移除其他无关内容,比如脚本和广告等
-  // const scripts = body.querySelectorAll('script');
-  // scripts.forEach(script => script.remove());
+  const scripts = body.querySelectorAll('script');
+  scripts.forEach(script => script.remove());
 
   // const iframes = body.querySelectorAll('iframe');
   // iframes.forEach(iframe => iframe.remove());
@@ -193,6 +172,8 @@ export function cleanPage(body) {
   // 销毁临时容器
   temp.remove()
   // content.outerHTML.trim().replace(/\s+/g, " ");
+  console.log(cleanedHtml, cleanedHtml.length);
+  
   return cleanedHtml
 }
 function cleanPage2(body) {

+ 1 - 3
src/utils/request.js

@@ -8,9 +8,7 @@ axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
 // 创建axios实例
 const service = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API,
-  timeout: 60000
 })
-console.log(import.meta.env.VITE_APP_BASE_API);
 
 let cancel
 // request拦截器
@@ -92,7 +90,7 @@ service.interceptors.response.use(
       // }
       ElMessage.error('您的登录状态已过期,请重新登录。')
       const userStore = useUserStore()
-      userStore.logout()
+      userStore.logout(false)
       return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
     } else if (code === 500) {
       ElMessage({ message: msg, type: 'error', grouping: true })