Browse Source

refactor(content): 重构内容脚本并优化页面分析功能

- 改进了页面内容提取方法,移除了无关元素和属性
- 调整了消息发送和加载状态的管理方式
- 优化了 DOM 操作和事件触发的相关代码
wzg 5 months ago
parent
commit
d43dbf3281

+ 323 - 323
src/entrypoints/content.js

@@ -5,344 +5,344 @@ import PageAnalyzer from '../utils/page-analyzer'
 import { log } from 'console';
 
 export default defineContentScript({
-    matches: ["<all_urls>"],
-    main(ctx) {
-        let page = document.getElementsByTagName('body')[0];
-        const src = chrome.runtime.getURL('images/begin.png')
-        window.pageAnalyzer = new PageAnalyzer();
-        window.onload = () => {
-            chrome.runtime.sendMessage(getPageInfo())
+  matches: ["<all_urls>"],
+  main(ctx) {
+    let page = document.getElementsByTagName('body')[0];
+    const src = chrome.runtime.getURL('images/begin.png')
+    window.pageAnalyzer = new PageAnalyzer();
+    window.onload = () => {
+      // chrome.runtime.sendMessage(getPageInfo())
+    }
+    let form = null
+    let formChildren = []
+    let excelDataA = {}
+    chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
+      if (message.type === 'GET_PAGE_INFO') {
+        sendResponse({
+          data: getPageInfo()
+        })
+      }
+      let dom = null
+      if (message.type === "GET_TAG_ACTION") {
+        const data = message.data
+        if (data.id) {
+          dom = document.getElementById(data.id)
         }
-        let form = null
-        let formChildren = []
-        let excelDataA = {}
-        chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
-            if (message.type === 'GET_PAGE_INFO') {
-                sendResponse({
-                    data: getPageInfo()
-                })
-            }
-            let dom = null
-            if (message.type === "GET_TAG_ACTION") {
-                const data = message.data
-                if (data.id) {
-                    dom = document.getElementById(data.id)
-                }
-                if (data.class) {
-                    dom = [...document.getElementsByTagName(data.tag)]
-                        // .filter(_ => _.className.includes(data.class))
-                        .filter(_ => _.innerText.includes(data.text))
-                }
-                console.log(dom);
-                dom[0].click()
-                sendResponse({ data: '完成' })
-                return true
-            }
-
-            if (message.type === 'GET_PAGE_FORM') {
-
-                const len = document.querySelectorAll("form").length
-                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());
-                    formChildren = [...form.elements]
-                }
-                // if (!form && document.querySelector("input")) {
-                //     const arr = []
-                //     const inputs = document.querySelectorAll("input")
-                //     formChildren = [...inputs]
-
-                //     for (const element of inputs) {
-                //         arr.push(element.outerHTML)
-                //     }
-                //     form = { outerHTML: JSON.stringify(arr) }
-                // }
-                if (!form) {
-                    sendResponse({
-                        status: 'error',
-                        message: '没有找到表单'
-                    })
-                    return
+        if (data.class) {
+          dom = [...document.getElementsByTagName(data.tag)]
+            // .filter(_ => _.className.includes(data.class))
+            .filter(_ => _.innerText.includes(data.text))
+        }
+        console.log(dom);
+        dom[0].click()
+        sendResponse({ data: '完成' })
+        return true
+      }
+
+      if (message.type === 'GET_PAGE_FORM') {
+
+        const len = document.querySelectorAll("form").length
+        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());
+          formChildren = [...form.elements]
+        }
+        // if (!form && document.querySelector("input")) {
+        //     const arr = []
+        //     const inputs = document.querySelectorAll("input")
+        //     formChildren = [...inputs]
+
+        //     for (const element of inputs) {
+        //         arr.push(element.outerHTML)
+        //     }
+        //     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
+        excelDataA = excelData
+        console.log(formData, excelData);
+        await handleFillInput(formData, 0)
+      }
+      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);
+
+      for (let i = 0; i < data.length; i++) {
+        const item = data[i]
+        if (item.findBy === 'id') {
+          const input = formChildren.find(child => child.id === item.findByValue)
+          if (item.type === 'text' || item.type === 'textarea' || item.type === 'number') {
+            if (!input) {
+              if (item.label) {
+                const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.label))
+                if (label) {
+                  const input = findLabelForInput(label)
+                  if (input) {
+                    await simulateUserInput(input, excelDataA[item.excelColumn][index])
+                  }
                 }
-                sendResponse({
-                    status: 'ok',
-                    data: form.outerHTML
-                })
+              }
             }
-            if (message.type === "INPUT_FORM") {
-                const { formData, excelData } = message.data
-                excelDataA = excelData
-                console.log(formData, excelData);
-                await handleFillInput(formData, 0)
+            if (input) {
+              await simulateUserInput(input, excelDataA[item.excelColumn][index])
             }
-            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;
+          }
+          if (item.type === 'radio' || item.type === 'checkbox') {
+            if (item.label) {
+
+              const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.label))
+              if (label) {
+                const span = findLabelForSpan(label)
+                span.forEach(span => {
+                  span.innerText === excelDataA[item.excelColumn][index] && span.click()
                 })
-                .map((link) => link.href)
-                .find(Boolean);
+              }
+            }
+          }
 
-            if (favIconUrl) return favIconUrl;
+          if (item.type === 'date') {
+            const input = formChildren.find(child => child.id === item.findByValue)
+            if (excelDataA[item.excelColumn][index]) {
 
-            // 如果没有找到,返回网站根目录的favicon.ico
-            const url = new URL(window.location.href);
-            return `${url.protocol}//${url.hostname}/favicon.ico`;
-        }
-        const handleFillInput = async (data, index) => {
-            console.log(data);
-
-            for (let i = 0; i < data.length; i++) {
-                const item = data[i]
-                if (item.findBy === 'id') {
-                    const input = formChildren.find(child => child.id === item.findByValue)
-                    if (item.type === 'text' || item.type === 'textarea' || item.type === 'number') {
-                        if (!input) {
-                            if (item.label) {
-                                const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.label))
-                                if (label) {
-                                    const input = findLabelForInput(label)
-                                    if (input) {
-                                        await simulateUserInput(input, excelDataA[item.excelColumn][index])
-                                    }
-                                }
-                            }
-                        }
-                        if (input) {
-                            await simulateUserInput(input, excelDataA[item.excelColumn][index])
-                        }
-                    }
-                    if (item.type === 'radio' || item.type === 'checkbox') {
-                        if (item.label) {
-
-                            const label = [...form.getElementsByTagName('label')].find(label => label.innerText.includes(item.label))
-                            if (label) {
-                                const span = findLabelForSpan(label)
-                                span.forEach(span => {
-                                    span.innerText === excelDataA[item.excelColumn][index] && span.click()
-                                })
-                            }
-                        }
-                    }
-
-                    if (item.type === 'date') {
-                        const input = formChildren.find(child => child.id === item.findByValue)
-                        if (excelDataA[item.excelColumn][index]) {
-
-                            await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn][index]), formatDate(excelDataA[item.excelColumn][index]))
-                        }
-                    }
-                } else if (item.findBy === 'placeholder') {
-                    const input = formChildren.find(child => child.placeholder === item.findByValue)
-                    if (input) {
-                        simulateUserInput(input, excelDataA[item.excelColumn][index])
-                    }
-                }
+              await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn][index]), formatDate(excelDataA[item.excelColumn][index]))
             }
+          }
+        } else if (item.findBy === 'placeholder') {
+          const input = formChildren.find(child => child.placeholder === item.findByValue)
+          if (input) {
+            simulateUserInput(input, excelDataA[item.excelColumn][index])
+          }
         }
-        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 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 < 10; i++) {
+        const input = p.getElementsByTagName('input')
+        if (input.length > 0) {
+          return input[0]
         }
-        const findLabelForInput = (label) => {
-            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
+        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]
         }
-        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
+        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);
         }
-        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;
+        // 触发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;
             }
-        };
-        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');
+            if (Date.now() - startTime > timeout) {
+              reject(new Error(`未找到title为"${title}"的td元素`));
+              return;
+            }
 
-            return `${year}-${month}-${day}`;
-        }
+            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}`;
+    }
+
+  },
 });

+ 52 - 38
src/entrypoints/sidepanel/Chat.vue

@@ -9,12 +9,12 @@
           <el-avatar :size="32" :src="message.avatar" />
           <div class="message-content">
             <div class="content" v-if="!message.isSelf" :class="{ 'loading-content': message.content === '' }">
-              <div v-html="message.content"></div>
-              <div class="loading-indicator" v-if="sendLoading && index === messages.length - 1">
+              <span v-html="message.content"></span>
+              <span class="loading-indicator" v-if="sendLoading && index === messages.length - 1">
                 <span class="dot"></span>
                 <span class="dot"></span>
                 <span class="dot"></span>
-              </div>
+              </span>
             </div>
             <div v-else class="content">{{ message.content }}</div>
             <div class="timestamp ">{{ message.timestamp }}
@@ -25,7 +25,7 @@
       </div>
     </el-scrollbar>
 
-    <Tools @read-click="isShowPage = true, taklToHtml = true" @upload-file="handleUpload" />
+    <Tools @read-click="readClick" @upload-file="handleUpload" @handle-capture="handleCapture" />
 
     <div>
       <!-- 输入区域 -->
@@ -73,8 +73,6 @@
 <script setup>
 import { ref, onMounted, nextTick, inject, useTemplateRef } from 'vue'
 import { ElScrollbar, ElAvatar, ElInput, ElButton } from 'element-plus'
-import avator from '@/public/icon/32.png'
-import moment from 'moment'
 import { buildExcelUnderstandingPrompt } from '@/utils/ai-service.js'
 import * as XLSX from "xlsx";
 import { ElMessage } from 'element-plus';
@@ -86,7 +84,6 @@ import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResiz
 
 // 滚动条引用
 const scrollbar = ref(null);
-const type = ref('');
 const xlsxData = ref({});
 const isShowPage = ref(false);
 const tareRef = useTemplateRef("textareaRef");
@@ -97,12 +94,13 @@ const {
   taklToHtml,
   pageInfo,
   sendLoading,
+  type,
   addMessage,
   sendRequese,
   getPageInfo,
   streamRes,
   handleInput
-} = useMsg(scrollbar, type, xlsxData, fetchDataAndProcess);
+} = useMsg(scrollbar, xlsxData, fetchDataAndProcess);
 const { handleCardButtonClick } = useSummary(addMessage, sendRequese);
 
 function handelIntelligentFillingClick() {
@@ -114,11 +112,25 @@ function handelIntelligentFillingClick() {
   }
 }
 
+async function readClick() {
+  isShowPage.value = true;
+  taklToHtml.value = true;
+  await getPageInfo()
+}
+
 function handleAsk() {
   addMessage(inputMessage.value.trim(), true);
   inputMessage.value = '';
 }
 
+function handleCapture() {
+  ElMessage({
+    message: '开发中...',
+    grouping: true,
+    showClose: true
+  });
+}
+
 // 计算标题是否需要滚动
 const titleScroll = computed(() => {
   return pageInfo.value?.title?.length > 20 // 当标题超过20个字符时触发滚动
@@ -180,8 +192,7 @@ const handleUpload = (file) => {
 async function fetchDataAndProcess(input, obj) {
   console.log(input);
 
-  const pageInfo = await getPageInfo()
-  console.log(pageInfo);
+  const pageInfo = await getPageInfo();
 
   // 发起请求获取数据
   // const res = await hepl({
@@ -267,7 +278,6 @@ onMounted(async () => {
       pageInfo.value = message.data
     }
   });
-  await getPageInfo()
 
   nextTick(() => {
     scrollbar.value?.setScrollTop(99999)
@@ -477,43 +487,47 @@ onMounted(async () => {
 }
 
 .loading-indicator {
-  display: flex;
+  display: inline-block;
   align-items: center;
   gap: 4px;
-}
-
-.dot {
-  width: 8px;
-  height: 8px;
-  background-color: #409eff;
-  border-radius: 50%;
-  display: inline-block;
-  animation: pulse 1.5s infinite ease-in-out;
-}
+  margin-left: 2px;
+
+  .dot {
+    width: 4px;
+    height: 4px;
+    margin: 0 2px;
+    background-color: gray;
+    border-radius: 50%;
+    display: inline-block;
+    animation: pulse 1.5s infinite ease-in-out;
+  }
 
-.dot:nth-child(2) {
-  animation-delay: 0.3s;
-}
+  .dot:nth-child(2) {
+    animation-delay: 0.3s;
+  }
 
-.dot:nth-child(3) {
-  animation-delay: 0.6s;
-}
+  .dot:nth-child(3) {
+    animation-delay: 0.6s;
+  }
 
-@keyframes pulse {
+  @keyframes pulse {
 
-  0%,
-  100% {
-    transform: scale(0.8);
-    opacity: 0.6;
-  }
+    0%,
+    100% {
+      transform: scale(0.8);
+      opacity: 0.6;
+    }
 
-  50% {
-    transform: scale(1.2);
-    opacity: 1;
+    50% {
+      transform: scale(1.2);
+      opacity: 1;
+    }
   }
 }
 
 
+
+
 .input-area {
   padding: 8px 10px;
   color: black;
@@ -559,7 +573,7 @@ onMounted(async () => {
   border-radius: 8px;
   transition: border-color 0.3s;
   resize: none;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+  box-shadow: none;
 }
 
 .input-area :deep(.el-textarea__inner) {

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

@@ -5,7 +5,7 @@ import { Reading, Upload, Paperclip, Scissor } from "@element-plus/icons-vue";
 
 const value = ref(options[0].value);
 
-const emit = defineEmits(['readClick', 'uploadFile'])
+const emit = defineEmits(['readClick', 'uploadFile','handleCapture'])
 </script>
 
 <template>
@@ -26,7 +26,7 @@ const emit = defineEmits(['readClick', 'uploadFile'])
     </el-upload>
     <span class="separator"></span>
     <el-tooltip effect="dark" content="截屏" placement="top">
-      <el-button :icon="Scissor" circle />
+      <el-button :icon="Scissor" circle  @click="emit('handleCapture')"/>
     </el-tooltip>
   </div>
 </template>

+ 3 - 1
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -3,12 +3,13 @@ import { ref, reactive } from 'vue';
 import avator from '@/public/icon/32.png';
 import moment from 'moment'
 // import { sendMessage } from '@/utils/ai-service';
-export function useMsg(scrollbar: any, type: any, xlsxData: any, fetchDataAndProcess: Function) {
+export function useMsg(scrollbar: any, xlsxData: any, fetchDataAndProcess: Function) {
   const inputMessage = ref('');
   const indexTemp = ref(0);
   const taklToHtml = ref<any>(false);
   const sendLoading = ref(false);
   const pageInfo = ref<any>({});
+  const type = ref('');
   const formMap = ref([]);
   const messages = ref([{
     username: '用户1',
@@ -176,6 +177,7 @@ export function useMsg(scrollbar: any, type: any, xlsxData: any, fetchDataAndPro
     pageInfo,
     sendLoading,
     formMap,
+    type,
     addMessage,
     sendRequese,
     getPageInfo,

+ 153 - 103
src/utils/page-analyzer.js

@@ -1,123 +1,173 @@
 // 确保类定义在全局作用域
 export default class PageAnalyzer {
-    constructor() {
-        this.readability = null;
+  constructor() {
+    this.readability = null;
+  }
+
+  /**
+   * 分析页面内容
+   * @returns {Object} 页面分析结果
+   */
+  analyzePage(iframe = false) {
+    try {
+      // 检查Readability是否可用
+      if (typeof Readability === "undefined") {
+        console.warn(
+          "Readability not loaded, falling back to basic extraction"
+        );
+        return this.fallbackAnalysis(iframe);
+      }
+
+      // 创建文档副本以避免修改原始DOM
+      const documentClone = document.cloneNode(true);
+
+      // 初始化 Readability
+      this.readability = new Readability(documentClone, {
+        debug: false,
+        charThreshold: 20,
+      });
+
+      // 解析页面
+      const article = this.readability.parse();
+
+      return {
+        title: article.title || document.title,
+        url: window.location.href,
+        mainContent: article.textContent || article.excerpt || "",
+        excerpt: article.excerpt || "",
+        siteName: article.siteName || new URL(window.location.href).hostname,
+        wordCount: article.length || 0,
+      };
+    } catch (error) {
+      console.warn("Readability failed, using fallback:", error);
+      return this.fallbackAnalysis();
     }
-
-    /**
-     * 分析页面内容
-     * @returns {Object} 页面分析结果
-     */
-    analyzePage(iframe = false) {
-        try {
-            // 检查Readability是否可用
-            if (typeof Readability === "undefined") {
-                console.warn(
-                    "Readability not loaded, falling back to basic extraction"
-                );
-                return this.fallbackAnalysis(iframe);
-            }
-
-            // 创建文档副本以避免修改原始DOM
-            const documentClone = document.cloneNode(true);
-
-            // 初始化 Readability
-            this.readability = new Readability(documentClone, {
-                debug: false,
-                charThreshold: 20,
-            });
-
-            // 解析页面
-            const article = this.readability.parse();
-
-            return {
-                title: article.title || document.title,
-                url: window.location.href,
-                mainContent: article.textContent || article.excerpt || "",
-                excerpt: article.excerpt || "",
-                siteName: article.siteName || new URL(window.location.href).hostname,
-                wordCount: article.length || 0,
-            };
-        } catch (error) {
-            console.warn("Readability failed, using fallback:", error);
-            return this.fallbackAnalysis();
+  }
+
+  // 基础提取方法作为后备
+  fallbackAnalysis(iframe) {
+    return {
+      title: document.title,
+      url: window.location.href,
+      mainContent: this.extractMainContent(iframe),
+      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);
+      cleanPage(content)
+      // .trim().replace(/\s+/g, " ")
+      // content
+      //     .querySelectorAll("script, style, iframe, nav, header, footer,svg")
+      //     .forEach((el) => el.remove());
+
+      const regex = /\s+/g;
+
+      // 定义标准 HTML 属性集合 排除id class style href src target
+      const standardAttributes = new Set(['alt', 'title', 'type', 'value', 'name', 'placeholder', 'disabled', 'checked', 'selected', 'readonly', 'required', 'maxlength', 'min', 'max', 'step', 'pattern', 'autocomplete', 'autofocus', 'multiple', 'rows', 'cols', 'rel', 'aria-*']);
+
+      // 创建一个临时容器
+      const temp = document.createElement('div');
+      temp.innerHTML = content.outerHTML;
+      // 递归删除空标签
+      function removeEmpty(element) {
+        const children = Array.from(element.children);
+        children.forEach(child => {
+          removeEmpty(child); // 递归处理子元素
+        });
+
+        // 检查标签是否为空
+        if (!element.innerHTML.trim()) {
+          element.remove(); // 删除空标签
         }
+      }
+
+      // 从临时容器的子元素开始处理
+      removeEmpty(temp);
+
+      // 删除 <link> 标签
+      const linkTags = temp.querySelectorAll('link');
+      linkTags.forEach(link => {
+        link.remove();
+      });
+
+      // 遍历所有元素
+      const elements = temp.querySelectorAll('*');
+      elements.forEach(element => {
+        // 获取所有属性
+        const attributes = Array.from(element.attributes);
+        attributes.forEach(attr => {
+          // 如果属性不是标准属性,则移除
+          if (!standardAttributes.has(attr.name) && !attr.name.startsWith('aria-')) {
+            element.removeAttribute(attr.name);
+          }
+        });
+      });
+
+      // 获取处理后的 HTML 字符串
+      const cleanedHtml = temp.innerHTML.trim().replace(regex, " ");
+
+      // 销毁临时容器
+      temp.remove();
+
+      console.log(cleanedHtml)
+      // content.outerHTML.trim().replace(/\s+/g, " ");
+      return cleanedHtml;
     }
 
-    // 基础提取方法作为后备
-    fallbackAnalysis(iframe) {
-        return {
-            title: document.title,
-            url: window.location.href,
-            mainContent: this.extractMainContent(iframe),
-            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);
-            cleanPage(content)
-// .trim().replace(/\s+/g, " ")
-            // content
-            //     .querySelectorAll("script, style, iframe, nav, header, footer,svg")
-            //     .forEach((el) => el.remove());
-            console.log(content.outerHTML.trim().replace(/\s+/g, " "),56565);
-            
-            return content.outerHTML.trim().replace(/\s+/g, " ");
-        }
-
 
-    }
+  }
 };
 
 function cleanPage(body) {
 
-    // 移除所有行内样式
-    const elementsWithInlineStyle = body.querySelectorAll('[style]');
-    elementsWithInlineStyle.forEach(element => {
-        element.removeAttribute('style');
-    });
-
-    // 移除所有注释节点
-    const comments = [];
-    const walk = document.createTreeWalker(body, NodeFilter.SHOW_COMMENT, null, false);
-    while (walk.nextNode()) {
-        comments.push(walk.currentNode);
-    }
-    comments.forEach(comment => comment.remove());
+  // 移除所有行内样式
+  const elementsWithInlineStyle = body.querySelectorAll('[style]');
+  elementsWithInlineStyle.forEach(element => {
+    element.removeAttribute('style');
+  });
+
+  // 移除所有注释节点
+  const comments = [];
+  const walk = document.createTreeWalker(body, NodeFilter.SHOW_COMMENT, null, false);
+  while (walk.nextNode()) {
+    comments.push(walk.currentNode);
+  }
+  comments.forEach(comment => comment.remove());
 
-    // 移除所有SVG图标
-    body.querySelectorAll("script, style, iframe, nav, header, footer,svg").forEach(svg => svg.remove());
+  // 移除所有SVG图标
+  body.querySelectorAll("script, style, iframe, nav, header, footer,svg").forEach(svg => svg.remove());
 
-    // 移除所有图片的src属性
-    const images = body.querySelectorAll('img');
-    images.forEach(img => {
-        img.removeAttribute('src');
-    });
+  // 移除所有图片的src属性
+  const images = body.querySelectorAll('img');
+  images.forEach(img => {
+    img.removeAttribute('src');
+  });
 
-    // 可选择移除其他无关内容,比如脚本和广告等
-    // 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());
+  // const iframes = body.querySelectorAll('iframe');
+  // iframes.forEach(iframe => iframe.remove());
 
-    const ads = body.querySelectorAll('.ad, .advertisement, .ads');
-    ads.forEach(ad => ad.remove());
+  const ads = body.querySelectorAll('.ad, .advertisement, .ads');
+  ads.forEach(ad => ad.remove());
 
-    console.log('页面清理完成!');
+  console.log('页面清理完成!');
 }