Browse Source

合并 chd 分支

wzg 5 months ago
parent
commit
a693304eef

+ 13 - 3
src/entrypoints/background.js

@@ -12,6 +12,14 @@ export default defineBackground(() => {
         data: message.data
       })
     }
+    if (message.type === 'FROM_CONTENT_TO_SEND_PAGE_FORM') {
+      console.log(message)
+      chrome.runtime.sendMessage({
+        type: 'TO_SIDE_PANEL_FORM_INFO',
+        data: message.data
+      })
+      return true
+    }
 
     if (message.type === 'FROM_SIDE_PANEL_TO_ACTION') {
       console.log(565888)
@@ -29,17 +37,15 @@ export default defineBackground(() => {
             } else {
               console.log('收到 content script 响应:', response)
               console.log(response, 998)
-
               sendResponse(response)
             }
-            return true
           }
         )
       })
       return true
     }
     if (message.type === 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM') {
-      chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+      chrome.tabs.query({ active: true}, (tabs) => {
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
         chrome.tabs.sendMessage(
@@ -52,6 +58,10 @@ export default defineBackground(() => {
               console.log('收到 content script 响应:', response)
               sendResponse(response)
             }
+
+
+
+            return true
           }
         )
       })

+ 64 - 271
src/entrypoints/content.js

@@ -1,6 +1,6 @@
 // entrypoints/content.ts
 import PageAnalyzer, { cleanPage } from '../utils/page-analyzer'
-
+import { formatDate, simulateCompleteUserAction, simulateUserInput, findLabelForInput, findLabelForTag, findLabelForSpan, findLabelForTextarea, getPageInfo } from '../utils/contentUtils'
 export default defineContentScript({
   matches: ['<all_urls>'],
   main(ctx) {
@@ -23,6 +23,7 @@ export default defineContentScript({
         }
         let dom = null
         if (message.type === 'GET_TAG_ACTION') {
+          console.log(message.data);
           const data = message.data
           if (0) {
             dom = document.getElementById(data.id)
@@ -44,27 +45,70 @@ export default defineContentScript({
             dom.click()
           }, 1000)
           sendResponse({ data: '完成' })
-          return true
         }
-
         if (message.type === 'GET_PAGE_FORM') {
-          const len = document.querySelectorAll('form').length
-          if (document.title === '智能招采' && len === 1) {
+          const forms = document.querySelectorAll('form')
+          if (forms.length === 0) {
             sendResponse({
               status: 'error',
               message: '没有找到表单'
             })
             return
           }
-
-          if (len) {
-            const forms = document.querySelectorAll('form')
-            if (len > 1) {
-              form = forms[len - 1]
-            } else form = forms[0]
-            form.querySelectorAll('svg').forEach((el) => el.remove())
+          if (forms.length === 1) {
+            form = forms[0]
+            const cloneForm =  forms[0].cloneNode(true)
+            cloneForm.querySelectorAll('svg').forEach((el) => el.remove())
             formChildren = [...form.elements]
+            sendResponse({
+              status: 'ok',
+              data: cloneForm.outerHTML
+            })
+            return
           }
+          sendResponse({
+            status: 'select',
+          })
+          for (const item of forms) {
+            item.style.border = '2px solid red'
+            function handleClick(e) {
+              e.stopPropagation()
+              console.log(this.outerHTML);
+              for (const form of forms) {
+                form.style.border = 'none'
+                form.removeEventListener('click', handleClick, true)
+              }
+              form = this
+              console.log(form,5855);
+              
+              const cloneForm = this.cloneNode(true)
+              cloneForm.querySelectorAll('svg').forEach((el) => el.remove())
+              formChildren = [...form.elements]
+              // sendResponse({
+              //   type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
+              //   data: this.outerHTML
+              // })
+              chrome.runtime.sendMessage({
+                type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
+                data: cloneForm.outerHTML
+              })
+            }
+            item.addEventListener("click", handleClick, true)
+          }
+        
+        
+ 
+          // if (!form) {
+          //   sendResponse({
+          //     status: 'error',
+          //     message: '没有找到表单'
+          //   })
+          //   return
+          // }
+          // sendResponse({
+          //   status: 'ok',
+          //   data: form.outerHTML
+          // })
           // if (!form && document.querySelector("input")) {
           //     const arr = []
           //     const inputs = document.querySelectorAll("input")
@@ -75,17 +119,6 @@ export default defineContentScript({
           //     }
           //     form = { outerHTML: JSON.stringify(arr) }
           // }
-          if (!form) {
-            sendResponse({
-              status: 'error',
-              message: '没有找到表单'
-            })
-            return
-          }
-          sendResponse({
-            status: 'ok',
-            data: form.outerHTML
-          })
         }
         if (message.type === 'INPUT_FORM') {
           const { formData, excelData } = message.data
@@ -96,45 +129,10 @@ export default defineContentScript({
         return true
       }
     )
-
-    function getPageInfo() {
-      const favIconUrl = getFavicon()
-      return {
-        type: 'PAGE_INFO',
-        data: {
-          favIconUrl,
-          title: document.title,
-          url: window.location.href,
-          content: window.pageAnalyzer.analyzePage()
-        }
-      }
-    }
-
-    function getFavicon() {
-      // 尝试获取动态favicon
-      const iconLinks = Array.from(
-        document.querySelectorAll('link[rel*="icon"]')
-      )
-      const favIconUrl = iconLinks
-        .sort((a, b) => {
-          // 优先使用大尺寸图标
-          const sizeA = parseInt(a.sizes?.value) || 0
-          const sizeB = parseInt(b.sizes?.value) || 0
-          return sizeB - sizeA
-        })
-        .map((link) => link.href)
-        .find(Boolean)
-
-      if (favIconUrl) return favIconUrl
-
-      // 如果没有找到,返回网站根目录的favicon.ico
-      const url = new URL(window.location.href)
-      return `${url.protocol}//${url.hostname}/favicon.ico`
-    }
-
     const handleFillInput = async (data, index) => {
-      console.log(data)
-
+      console.log(data, 85888)
+      console.log(formChildren,form);
+      
       for (let i = 0; i < data.length; i++) {
         const item = data[i]
         if (item.findBy === 'id') {
@@ -147,10 +145,11 @@ export default defineContentScript({
             item.type === 'number'
           ) {
             if (!input) {
-              if (item.label) {
+              if (item.item) {
                 const label = [...form.getElementsByTagName('label')].find(
-                  (label) => label.innerText.includes(item.label)
+                  (label) => label.innerText.includes(item.item)
                 )
+                console.log(item,label)
                 if (label) {
                   const input = findLabelForInput(label)
                   if (input) {
@@ -164,9 +163,9 @@ export default defineContentScript({
             }
           }
           if (item.type === 'radio' || item.type === 'checkbox') {
-            if (item.label) {
+            if (item.item) {
               const label = [...form.getElementsByTagName('label')].find(
-                (label) => label.innerText.includes(item.label)
+                (label) => label.innerText.includes(item.item)
               )
               if (label) {
                 const span = findLabelForSpan(label)
@@ -271,211 +270,5 @@ export default defineContentScript({
         }
       }
     }
-    const simulateUserInput = async (element, value) => {
-      // 设置值
-      if (element.tagName.toLowerCase() === 'textarea') {
-        element.focus()
-        element.value = value
-        element.blur()
-        return
-      }
-      element.value = value
-
-      // 创建并触发 input 事件
-      const inputEvent = new Event('input', { bubbles: true })
-      element.dispatchEvent(inputEvent)
-
-      // 创建并触发 change 事件
-      const changeEvent = new Event('change', { bubbles: true })
-      element.dispatchEvent(changeEvent)
-
-      // 可选:如果表单使用了 React/Vue 等框架,可能还需要触发以下事件
-      element.dispatchEvent(new Event('blur'))
-      element.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }))
-    }
-    const findLabelForInput = (label) => {
-      let p = label.parentElement
-      for (let i = 0; i < 5; i++) {
-        const input = p.getElementsByTagName('input')
-        if (input.length > 0) {
-          return input[0]
-        }
-        p = p.parentElement
-      }
-      return null
-    }
-    const findLabelForTag = (label, tag) => {
-      let p = label.parentElement
-      for (let i = 0; i < 10; i++) {
-        const input = p.getElementsByTagName('input')
-        if (input.length > 0) {
-          return input[0]
-        }
-        p = p.parentElement
-      }
-      return null
-    }
-    const findLabelForSpan = (label) => {
-      let p = label.parentElement
-      for (let i = 0; i < 10; i++) {
-        const span = p.getElementsByTagName('span')
-        if (span.length > 0) {
-          return [...span]
-        }
-        p = p.parentElement
-      }
-      return null
-    }
-    const findLabelForTextarea = (label) => {
-      let p = label.parentElement
-      for (let i = 0; i < 10; i++) {
-        const span = p.getElementsByTagName('textarea')
-        if (span.length > 0) {
-          return [...span]
-        }
-        p = p.parentElement
-      }
-      return null
-    }
-    const simulateCompleteUserAction = async (
-      clickElement,
-      inputElement,
-      inputText,
-      tdTitle
-    ) => {
-      // 1. 模拟鼠标弹起事件
-      const simulateMouseUp = (element) => {
-        const mouseUpEvent = new MouseEvent('mouseup', {
-          bubbles: true,
-          cancelable: true,
-          view: window,
-          button: 0,
-          buttons: 0,
-          clientX: 0,
-          clientY: 0,
-          detail: 1
-        })
-        element.dispatchEvent(mouseUpEvent)
-      }
-      // 123456qq?
-      // 2. 模拟键盘输入
-      const simulateTyping = async (element, text, delay = 50) => {
-        element.focus()
-        for (let char of text) {
-          await new Promise((resolve) => setTimeout(resolve, delay))
-
-          // 按键按下
-          const keydownEvent = new KeyboardEvent('keydown', {
-            key: char,
-            code: `Key${char.toUpperCase()}`,
-            bubbles: true,
-            cancelable: true
-          })
-          element.dispatchEvent(keydownEvent)
-
-          // 更新输入值
-          element.value += char
-
-          // 触发输入事件
-          const inputEvent = new InputEvent('input', {
-            bubbles: true,
-            cancelable: true,
-            data: char,
-            inputType: 'insertText'
-          })
-          element.dispatchEvent(inputEvent)
-
-          // 按键弹起
-          const keyupEvent = new KeyboardEvent('keyup', {
-            key: char,
-            code: `Key${char.toUpperCase()}`,
-            bubbles: true,
-            cancelable: true
-          })
-          element.dispatchEvent(keyupEvent)
-        }
-
-        // 触发change事件
-        element.dispatchEvent(new Event('change', { bubbles: true }))
-      }
-
-      // 3. 查找td元素
-      const findTdByTitle = (title, timeout = 5000) => {
-        return new Promise((resolve, reject) => {
-          const startTime = Date.now()
-
-          const find = () => {
-            const td = document.querySelector(`td[title="${title}"]`)
-            if (td) {
-              resolve(td)
-              return
-            }
-
-            if (Date.now() - startTime > timeout) {
-              reject(new Error(`未找到title为"${title}"的td元素`))
-              return
-            }
-
-            requestAnimationFrame(find)
-          }
-
-          find()
-        })
-      }
-
-      // 4. 模拟点击事件
-      const simulateClick = (element) => {
-        const clickEvent = new MouseEvent('click', {
-          bubbles: true,
-          cancelable: true,
-          view: window,
-          detail: 1
-        })
-        element.dispatchEvent(clickEvent)
-      }
-
-      try {
-        // 执行操作序列
-
-        // 1. 触发鼠标弹起
-        simulateMouseUp(clickElement)
-        await new Promise((resolve) => setTimeout(resolve, 100))
-
-        // 2. 模拟键盘输入
-        await simulateTyping(inputElement, inputText)
-        await new Promise((resolve) => setTimeout(resolve, 200))
-
-        // 3. 查找并点击td元素
-        // const tdElement = await findTdByTitle(tdTitle);
-
-        const enterKeyEvent = new KeyboardEvent('keydown', {
-          key: 'Enter', // 事件键名
-          code: 'Enter', // 物理按键编码
-          keyCode: 13, // 传统键码(Enter键为13)
-          which: 13, // 同keyCode
-          bubbles: true, // 允许事件冒泡
-          cancelable: true // 允许事件被取消
-        })
-        setTimeout(() => {
-          inputElement.dispatchEvent(enterKeyEvent)
-        }, 0)
-        await new Promise((resolve) => setTimeout(resolve, 500))
-        return true
-      } catch (error) {
-        throw error
-      }
-    }
-
-    function formatDate(date) {
-      // 直接创建北京时间的日期对象
-      const d = new Date(date)
-
-      // 获取年、月、日
-      const year = d.getFullYear()
-      const month = String(d.getMonth() + 1).padStart(2, '0') // 月份从0开始,需要加1
-      const day = String(d.getDate()).padStart(2, '0')
-
-      return `${year}-${month}-${day}`
-    }
   }
 })

+ 20 - 33
src/entrypoints/sidepanel/Chat.vue

@@ -68,7 +68,7 @@
             </el-tooltip>
 
             <el-tooltip content="选择后,在输入框描述填表流程" placement="top">
-              <el-button round @click="handelIntelligentFillingClick" :disabled="!taklToHtml">智能填表</el-button>
+              <el-button :class="type === '2' ? 'buttom-clicked' : ''" round @click="handelIntelligentFillingClick" :disabled="!taklToHtml">智能填表</el-button>
             </el-tooltip>
           </div>
         </div>
@@ -125,6 +125,7 @@ const drawerRef = useTemplateRef('historyComponentRef')
 const { registerStore, useStore } = inject('indexedDBHook')
 const msgStore = useMsgStore()
 const { pageInfoList, messages, msgUuid, AIModel } = storeToRefs(useMsgStore())
+const formInfo = ref('')
 
 const {
   taklToHtml,
@@ -132,8 +133,8 @@ const {
   type,
   streamRes,
   getFormKeyAndValue,
-  handleSend,
-  requestFlowFn
+  requestFlowFn,
+  fetchRes
 } = useMsg(scrollbar)
 const inputMessage = ref('')
 const pageInfo = ref('')
@@ -254,7 +255,6 @@ function deletePageInfo(i) {
     isShowPage.value = false
     taklToHtml.value = false
   }
-
 }
 
 function addNewDialogue() {
@@ -268,22 +268,25 @@ function addNewDialogue() {
 
 async function handleAsk() {
   if (sendLoading.value) return
+  // 添加indexDB Store配置
   if (msgUuid.value === '') {
-    // 添加indexDB Store配置
     msgUuid.value = 'D' + Date.now().toString()
     await registerStore({
       name: msgUuid.value,
       keyPath: 'id'
     })
   }
-
   addMessage(inputMessage.value.trim())
-  streamRes(taklToHtml.value)
-  // handleSend(inputMessage.value.trim())
+  if (type.value === '2') {
+    const res = await fetchRes(inputMessage.value.trim())
+    if (res.status === 'ok') {
+      formInfo.value = res.data
+      console.log(res);
+    }
+  }
+  else streamRes(taklToHtml.value)
   inputMessage.value = ''
-
 }
-
 function handleCapture() {
   ElMessage({
     message: '开发中...',
@@ -304,43 +307,27 @@ const handleUpload = async (file) => {
     return
   }
   if (type.value === '2') {
-    chrome.runtime.sendMessage({
-      type: 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM'
-    }, async (response) => {
-      if (chrome.runtime.lastError) {
-        console.error('消息发送错误:', chrome.runtime.lastError)
-      } else {
-        const fileExtension = file.name.split('.').pop().toLowerCase()
-        if (response.status === 'error') return ElMessage({
-          message: response.message,
-          type: 'error',
-          duration: 4 * 1000,
-          grouping: true
-        })
+    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]
           })
-          addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, response.data))
+          addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
           const a = await streamRes()
+          console.log(a);
           handleInput(xlsxData.value, JSON.parse(a.split('json')[1].split('```')[0]))
         } else {
           const { data, msg } = await getFileValue(file)
-          const res2 = await getFormKeyAndValue(data, response.data)
+          const res2 = await getFormKeyAndValue(data, formInfo.value)
           xlsxData.value = res2.data
-          msg.rawContent = buildObjPrompt(res2.data, response.data)
-          console.log()
-
+          msg.rawContent = buildObjPrompt(res2.data, formInfo.value)
           const a = await streamRes()
           handleInput(xlsxData.value, JSON.parse(a.split('json')[1].split('```')[0]))
-          console.log(xlsxData.value)
-          console.log(type.value)
-        }
       }
-      return true
-    })
+          type.value = ''
+        return
   }
   if (type.value === '') {
     const fileVaue = await getFileValue(file)

+ 4 - 0
src/entrypoints/sidepanel/css/chat.scss

@@ -316,3 +316,7 @@
   border-color: #409eff;
   box-shadow: none;
 }
+.buttom-clicked {
+    color: #409eff;
+    border-color: rgb(197.7, 225.9, 255);
+}

+ 137 - 82
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -51,52 +51,81 @@ export function useMsg(scrollbar?: any) {
     }
   }
   const getFormKeyAndValue = async (file: any, form?: any) => {
-    const obj = reactive({
-      id: moment(),
-      username: '用户1',
-      rawContent: '',
-      content: '解析文件中',
-      timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      isSelf: false,
-      avatar: avator,
-      addToHistory: !taklToHtml.value
-    })
+    // const obj = reactive({
+    //   id: moment(),
+    //   username: '用户1',
+    //   rawContent: '',
+    //   content: '解析文件中',
+    //   timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
+    //   isSelf: false,
+    //   avatar: avator,
+    //   addToHistory: !taklToHtml.value
+    // })
 
     try {
       sendLoading.value = true
-      messages.value.push(obj)
+      // messages.value.push(obj)
       const response = await getFormKey({
         body: form,
         input_data: file
       })
       return response
     } catch (error) {
-      obj.content = '解析出错'
+      // obj.content = '解析出错'
     } finally {
       sendLoading.value = false
     }
   }
   // 发送消息
-  const handleSend = async (msg: string, addHtml?: undefined | Boolean) => {
-    if ((type.value === '2' && msg.startsWith('/')) || msg.startsWith('请')) {
-      if (!taklToHtml.value)
-        return messages.value.push({
-          id: messages.value.length + 1,
-          username: '用户1',
-          content: '请打开与页面对话!',
-          rawContent: '请打开与页面对话!',
-          timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-          isSelf: false,
-          avatar: avator,
-          addToHistory: false
-        })
-      indexTemp.value = 0
-      fetchRes(msg)
-    } else {
-      await streamRes(addHtml = false)
-    }
+  // const handleSend = async (msg: any) => {
+  //   if ( msg?.startsWith('/')) {
+  //     if (!taklToHtml.value)
+  //       return messages.value.push({
+  //         id: messages.value.length + 1,
+  //         username: '用户1',
+  //         content: '请打开与页面对话!',
+  //         rawContent: '请打开与页面对话!',
+  //         timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
+  //         isSelf: false,
+  //         avatar: avator,
+  //         addToHistory: false
+  //       })
+  //     indexTemp.value = 0
+  //     fetchRes(msg)
+  //   }
+  // }
+  async function awaitFindForm(obj:any) {
+    return await new Promise((res, rej) => {
+      chrome.runtime.sendMessage({
+        type: 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM'
+      }, ({ status, data }) => {
+        if (status === 'error') {
+          obj.content = '当前页面未找到表单'
+          res({ status })
+        }
+        if (status === 'ok') {
+          obj.content = '请上传数据'
+          res({ status, data })
+        }
+        if (status === 'select') {
+          obj.content = '检测到左侧页面中有多个表单,请选择要填写的表单。'
+          function handle(message, sender, sendResponse)  {
+            if (message.type === 'TO_SIDE_PANEL_FORM_INFO') {
+              console.log('收到一次性消息:', message.data);
+              res({ status: 'ok', data: message.data })
+              obj.content = '请上传数据'
+              console.log(565656);
+              // 销毁监听器(确保只触发一次)
+              chrome.runtime.onMessage.removeListener(handle);
+            }
+          }
+         chrome.runtime.onMessage.addListener(handle);
+        }
+      })
+    })
   }
   const fetchRes = async (msg: any) => {
+    indexTemp.value = 0
     sendLoading.value = true
     const obj: any = reactive({
       id: messages.value.length + 1,
@@ -108,16 +137,28 @@ export function useMsg(scrollbar?: any) {
       addToHistory: !taklToHtml.value
     })
     messages.value.push(obj)
+    msg = msg.split('/智能填表')[1]
     nextTick(() => scrollbar.value?.setScrollTop(99999))
-    await fetchDataAndProcess(msg, obj)
+    if (!msg) {
+      sendLoading.value = false
+      const res = await awaitFindForm(obj)
+      return res
+    }
+    const res = await fetchDataAndProcess(msg, obj)
     sendLoading.value = false
+    if (res.status === 'ok') {
+      await new Promise((res: any) =>
+        setTimeout(() => {
+          res()
+        }, 2000)
+      )
+      const res = await awaitFindForm(obj)
+      return res
+    }
   }
   let str = ''
 
   async function fetchDataAndProcess(input: any, obj: any) {
-    if (input.startsWith('/智能填表')) {
-      input = input.split('/智能填表')[1]
-    }
     str = input
     console.log(str)
     const pageInfo = await getPageInfo()
@@ -130,7 +171,7 @@ export function useMsg(scrollbar?: any) {
     //   input_data: input,
     //   body: pageInfo.content.mainContent
     // })
-    const res = await new Promise((resolve, reject) => {
+    const res: any = await new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve({
           data:
@@ -148,58 +189,55 @@ export function useMsg(scrollbar?: any) {
         grouping: true
       })
       obj.content = '未找到标签,请重试'
-      type.value = ''
-      return
+      return {status: 'error'}
     }
-    await handleClick(res.data, obj)
-  }
-
-  async function handleClick(res: any, msgObj: any) {
     await new Promise((resolve) => setTimeout(resolve, 2000))
-    msgObj.content = `点击${res.tag}元素`
-    chrome.runtime.sendMessage(
-      {
-        type: 'FROM_SIDE_PANEL_TO_ACTION',
-        data: res
-      },
-      async ({ data, status }) => {
-        if (chrome.runtime.lastError) {
-          console.error('消息发送错误:', chrome.runtime.lastError)
-        } else {
-          if (status === 'error') {
-            msgObj.content = data
-            return
-          }
-          if (res.next === '是') {
-            const arr = str.split(',')
-            arr.shift()
-            str = arr.join(',')
-            indexTemp.value++
-            fetchDataAndProcess(str, msgObj)
+    obj.content = `点击${res.data.tag}元素`
+    const res2 = await new Promise((resolve, rej) => {
+      chrome.runtime.sendMessage(
+        {
+          type: 'FROM_SIDE_PANEL_TO_ACTION',
+          data: res.data
+        },
+        async ({ data, status }) => {
+          if (chrome.runtime.lastError) {
+            console.error('消息发送错误:', chrome.runtime.lastError)
           } else {
-            if (type.value === '2') {
-              await new Promise((resolve, reject) => {
-                setTimeout(() => {
-                  resolve(1)
-                }, 2000)
-              })
-              msgObj.content = `请上传数据`
-              ElMessage({
-                message: '请上传数据',
-                type: 'success',
-                duration: 4 * 1000,
-                grouping: true
-              })
-            } else {
-              msgObj.content = `执行完毕`
+            if (status === 'error') {
+              obj.content = data
+              resolve({ data, status })
             }
+            if (res.data.next === '是') {
+              const arr = str.split(',')
+              arr.shift()
+              str = arr.join(',')
+              indexTemp.value++
+              const res = await fetchDataAndProcess(str, obj)
+              resolve(res)
+            } else resolve({ status: 'ok' })
+            // else {
+            //   await new Promise((resolve, reject) => {
+            //     setTimeout(() => {
+            //       resolve(1)
+            //     }, 2000)
+            //   })
+            //   obj.content = `请选择表单`
+            //   ElMessage({
+            //     message: '请选择表单',
+            //     type: 'success',
+            //     duration: 4 * 1000,
+            //     grouping: true
+            //   })
+            //   chrome.runtime.sendMessage({
+            //     type: 'FROM_SIDE_PANEL_TO_GET_PAGE_FORM'
+            //   })
+            // }
           }
         }
-        return true
-      }
-    )
+      )
+    })
+    return res2
   }
-
   /**
    *
    * @param addHtml 是否添加页面信息
@@ -229,7 +267,6 @@ export function useMsg(scrollbar?: any) {
         role: 'user',
         content: messages.value[messages.value.length - 1].rawContent
       })
-      console.log(history)
     } else {
       history = messages.value
         .filter((item: any) => item.addToHistory)
@@ -253,10 +290,14 @@ export function useMsg(scrollbar?: any) {
           obj.rawContent += decodedChunk
           // 实时格式化显示内容
           obj.content = formatMessage(obj.rawContent)
+          // if (type.value === '2') obj.content = obj.content.replace(/item/g, '表单项').replace(/excelColumn/g, '对应数据源')
+
         }
       }
       scrollbar.value?.setScrollTop(99999)
     }
+    console.log(obj.rawContent);
+
     //添加到存储历史
     useStore(msgUuid.value).add({ ...obj })
     // 处理最终内容
@@ -264,6 +305,20 @@ export function useMsg(scrollbar?: any) {
     nextTick(() => {
       scrollbar.value?.setScrollTop(99999)
     })
+    if (type.value === '2') {
+      const arr = JSON.parse(obj.rawContent.split('json')[1].split('```')[0])
+      const newArr = arr.map(obj => {
+        return `| ${[
+          `“findBy”: \` "label",`,
+          `"findByValue" \` “${obj.findByValue}”,`,
+          `"数据来源字段" \` “${obj.excelColumn}”,`,
+          `"表单项" \` “${obj.item}"`
+        ].join(' ')} |`;
+      }).join('\n');
+      obj.content = formatMessage('```json' + JSON.stringify(newArr) + '```')
+      console.log(formatMessage('```json' + JSON.stringify(newArr) + '```'));
+
+    }
     return obj.rawContent
   }
 
@@ -314,7 +369,7 @@ export function useMsg(scrollbar?: any) {
     sendLoading,
     formMap,
     type,
-    handleSend,
+    fetchRes,
     streamRes,
     getFormKeyAndValue,
     getFileSummary,

+ 6 - 4
src/entrypoints/sidepanel/utils/ai-service.js

@@ -232,10 +232,11 @@ ${pageInfo}
 2. 生成表单项与列标题对应的数组,并使用findBy告诉我通过表单项的什么字段信息匹配到的,使用findByValue告诉我匹配到的表单项字段值,使用excelColumn字段告诉我excel文件中列标题的值。
 3. 表单项有id根据id匹配,findBy是id,并通过findByValue告诉我id的值,没有id根据label匹配,findBy是label,并通过findByValue给我label元素的值,没有label根据placeholder匹配,findBy是placeholder,并通过findByValue告诉我placeholder的值,没有placeholder,再根据其他内容匹配
 3. 并去除没有匹配到的表单项和excel文件中没有匹配到的列,
-4. 通过type字段告诉我输入项的类型
-5. 返回数组,不要返回任何其他内容。`
+4. 通过type字段告诉我输入项的类型,表单项有label,通过item字段返回label的值。
+5. 返回json格式数组,不要返回任何其他内容。`
 }
 
+
 // 5. 如果表单项有label标签,同时返回label, 通过label字段告诉我label元素的文本
 export function buildObjPrompt(obj, pageInfo) {
   return `我将向你展示一个对象和一个form表单。请帮我理解这些数据:
@@ -249,8 +250,9 @@ ${pageInfo}
 2. 表单项有label,根据label匹配,没有label根据placeholder匹配,没有placeholder,根据id匹配,再根据其他内容匹配
 3. 并根据实际可操作的表单项的所有信息与上传的对象的key进行匹配,生成表单项与key的数组,并使用findBy告诉我通过表单项的什么字段信息匹配到的,使用findByValue告诉我匹配到的表单项字段值,使用excelColumn字段告诉我对应的key值。在一个字段内返回
 4. 并去除没有匹配到的表单项和对象中没有匹配到的key,
-5. 通过type字段告诉我输入项的类型,如果对象中key对应的是日期,type统一返回date
-6. 仅返回数组,不要返回任何其他内容。`
+5. 通过type字段告诉我输入项的类型,如果对象中key对应的是日期,type统一返回date。
+6. 表单项有label,通过item字段返回label的值。
+7. 返回json格式返回数组,不要返回任何其他内容。`
 }
 
 function escapeHtml(html) {

+ 7 - 4
src/entrypoints/sidepanel/utils/index.js

@@ -1,6 +1,6 @@
 import * as XLSX from 'xlsx'
-
-export const getPageInfo = () => {
+import { ElMessage } from 'element-plus'
+export function getPageInfo (){
   return new Promise((res, rej) => {
     chrome.runtime.sendMessage(
       {
@@ -17,7 +17,7 @@ export const getPageInfo = () => {
     )
   })
 }
-export const handleInput = (xlsxData, formMap) => {
+export function handleInput (xlsxData, formMap) {
   chrome.runtime.sendMessage({
     type: 'FROM_SIDE_PANEL_TO_INPUT_FORM',
     data: {
@@ -26,7 +26,7 @@ export const handleInput = (xlsxData, formMap) => {
     }
   })
 }
-export const getXlsxValue = (file) => {
+export function getXlsxValue (file) {
   return new Promise((res, rej) => {
     try {
       const reader = new FileReader()
@@ -56,3 +56,6 @@ export const getXlsxValue = (file) => {
     }
   })
 }
+
+
+

+ 233 - 0
src/utils/contentUtils.js

@@ -0,0 +1,233 @@
+export function formatDate(date) {
+    // 直接创建北京时间的日期对象
+    const d = new Date(date)
+    // 获取年、月、日
+    const year = d.getFullYear()
+    const month = String(d.getMonth() + 1).padStart(2, '0') // 月份从0开始,需要加1
+    const day = String(d.getDate()).padStart(2, '0')
+    return `${year}-${month}-${day}`
+}
+export async  function simulateCompleteUserAction  (
+    clickElement,
+    inputElement,
+    inputText,
+    tdTitle
+) {
+    // 1. 模拟鼠标弹起事件
+    const simulateMouseUp = (element) => {
+        const mouseUpEvent = new MouseEvent('mouseup', {
+            bubbles: true,
+            cancelable: true,
+            view: window,
+            button: 0,
+            buttons: 0,
+            clientX: 0,
+            clientY: 0,
+            detail: 1
+        })
+        element.dispatchEvent(mouseUpEvent)
+    }
+    // 123456qq?
+    // 2. 模拟键盘输入
+    const simulateTyping = async (element, text, delay = 50) => {
+        element.focus()
+        for (let char of text) {
+            await new Promise((resolve) => setTimeout(resolve, delay))
+
+            // 按键按下
+            const keydownEvent = new KeyboardEvent('keydown', {
+                key: char,
+                code: `Key${char.toUpperCase()}`,
+                bubbles: true,
+                cancelable: true
+            })
+            element.dispatchEvent(keydownEvent)
+
+            // 更新输入值
+            element.value += char
+
+            // 触发输入事件
+            const inputEvent = new InputEvent('input', {
+                bubbles: true,
+                cancelable: true,
+                data: char,
+                inputType: 'insertText'
+            })
+            element.dispatchEvent(inputEvent)
+
+            // 按键弹起
+            const keyupEvent = new KeyboardEvent('keyup', {
+                key: char,
+                code: `Key${char.toUpperCase()}`,
+                bubbles: true,
+                cancelable: true
+            })
+            element.dispatchEvent(keyupEvent)
+        }
+
+        // 触发change事件
+        element.dispatchEvent(new Event('change', { bubbles: true }))
+    }
+
+    // 3. 查找td元素
+    const findTdByTitle = (title, timeout = 5000) => {
+        return new Promise((resolve, reject) => {
+            const startTime = Date.now()
+
+            const find = () => {
+                const td = document.querySelector(`td[title="${title}"]`)
+                if (td) {
+                    resolve(td)
+                    return
+                }
+
+                if (Date.now() - startTime > timeout) {
+                    reject(new Error(`未找到title为"${title}"的td元素`))
+                    return
+                }
+
+                requestAnimationFrame(find)
+            }
+
+            find()
+        })
+    }
+
+    // 4. 模拟点击事件
+    const simulateClick = (element) => {
+        const clickEvent = new MouseEvent('click', {
+            bubbles: true,
+            cancelable: true,
+            view: window,
+            detail: 1
+        })
+        element.dispatchEvent(clickEvent)
+    }
+
+    try {
+        // 执行操作序列
+
+        // 1. 触发鼠标弹起
+        simulateMouseUp(clickElement)
+        await new Promise((resolve) => setTimeout(resolve, 100))
+        // 2. 模拟键盘输入
+        await simulateTyping(inputElement, inputText)
+        await new Promise((resolve) => setTimeout(resolve, 200))
+        // 3. 查找并点击td元素
+        // const tdElement = await findTdByTitle(tdTitle);
+        const enterKeyEvent = new KeyboardEvent('keydown', {
+            key: 'Enter', // 事件键名
+            code: 'Enter', // 物理按键编码
+            keyCode: 13, // 传统键码(Enter键为13)
+            which: 13, // 同keyCode
+            bubbles: true, // 允许事件冒泡
+            cancelable: true // 允许事件被取消
+        })
+        setTimeout(() => {
+            inputElement.dispatchEvent(enterKeyEvent)
+        }, 0)
+        await new Promise((resolve) => setTimeout(resolve, 500))
+        return true
+    } catch (error) {
+        throw error
+    }
+}
+export async function simulateUserInput(element, value) {
+    // 设置值
+    if (element.tagName.toLowerCase() === 'textarea') {
+        element.focus()
+        element.value = value
+        element.blur()
+        return
+    }
+    element.value = value
+
+    // 创建并触发 input 事件
+    const inputEvent = new Event('input', { bubbles: true })
+    element.dispatchEvent(inputEvent)
+
+    // 创建并触发 change 事件
+    const changeEvent = new Event('change', { bubbles: true })
+    element.dispatchEvent(changeEvent)
+
+    // 可选:如果表单使用了 React/Vue 等框架,可能还需要触发以下事件
+    element.dispatchEvent(new Event('blur'))
+    element.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }))
+}
+export function findLabelForInput (label)  {
+    let p = label.parentElement
+    for (let i = 0; i < 5; i++) {
+        const input = p.getElementsByTagName('input')
+        if (input.length > 0) {
+            return input[0]
+        }
+        p = p.parentElement
+    }
+    return null
+}
+export function findLabelForTag(label, tag) {
+    let p = label.parentElement
+    for (let i = 0; i < 10; i++) {
+        const input = p.getElementsByTagName('input')
+        if (input.length > 0) {
+            return input[0]
+        }
+        p = p.parentElement
+    }
+    return null
+}
+export function findLabelForSpan(label) {
+    let p = label.parentElement
+    for (let i = 0; i < 10; i++) {
+        const span = p.getElementsByTagName('span')
+        if (span.length > 0) {
+            return [...span]
+        }
+        p = p.parentElement
+    }
+    return null
+}
+export function findLabelForTextarea(label)  {
+    let p = label.parentElement
+    for (let i = 0; i < 10; i++) {
+        const span = p.getElementsByTagName('textarea')
+        if (span.length > 0) {
+            return [...span]
+        }
+        p = p.parentElement
+    }
+    return null
+}
+export function getPageInfo() {
+    const favIconUrl = getFavicon()
+    return {
+        type: 'PAGE_INFO',
+        data: {
+            favIconUrl,
+            title: document.title,
+            url: window.location.href,
+            content: window.pageAnalyzer.analyzePage()
+        }
+    }
+}
+export function getFavicon() {
+    // 尝试获取动态favicon
+    const iconLinks = Array.from(
+        document.querySelectorAll('link[rel*="icon"]')
+    )
+    const favIconUrl = iconLinks
+        .sort((a, b) => {
+            // 优先使用大尺寸图标
+            const sizeA = parseInt(a.sizes?.value) || 0
+            const sizeB = parseInt(b.sizes?.value) || 0
+            return sizeB - sizeA
+        })
+        .map((link) => link.href)
+        .find(Boolean)
+
+    if (favIconUrl) return favIconUrl
+
+    // 如果没有找到,返回网站根目录的favicon.ico
+    const url = new URL(window.location.href)
+    return `${url.protocol}//${url.hostname}/favicon.ico`
+}