浏览代码

自动化对接

zjh 1 月之前
父节点
当前提交
52221220a6

+ 2 - 2
.env

@@ -6,5 +6,5 @@ VITE_APP_BASE_API='http://192.168.1.5:8000'
 # 接口地址 (WebSocket) 
 VITE_API_WS_URL ='ws://192.168.1.166:7777'
 # 终端ID 
-VITE_CLIENT_ID = '7d489df3ed56b51fcbf4a4f7139885c2'
-#VITE_CLIENT_ID = '7a1958fafed3268a7515b083bd6f144f'
+; VITE_CLIENT_ID = 'e062ca8e3d265606847ba1c8d1e415bc'
+VITE_CLIENT_ID = '7a1958fafed3268a7515b083bd6f144f'

+ 3 - 1
src/entrypoints/sidepanel/AdvancedMode.vue

@@ -23,7 +23,7 @@
                 <StepsDisplay 
                 v-if="message.type === 'plan'"
                  :content="message.rawContent" 
-                 :initialStep="0"
+                 :initialStep="initialSteps"
                  @step-change="handleStepChange" @complete="handleComplete"
                  />
                 <span v-else v-html="message.content"></span>
@@ -118,6 +118,7 @@
 </template>
 
 <script setup>
+
 import { ref, onMounted, nextTick, inject, useTemplateRef, reactive,onBeforeMount } from 'vue'
 import { ElScrollbar, ElAvatar, ElInput, ElButton } from 'element-plus'
 import moment from 'moment'
@@ -165,6 +166,7 @@ const {
 const inputMessage = ref('')
 const pageInfo = ref('')
 const summaryHtml = ref(false)
+  const initialSteps=ref(0)
 function handleStopAsk() {
     inputMessage.value = ''
     pageInfoList.value = []

+ 4 - 3
src/entrypoints/sidepanel/App.vue

@@ -1,9 +1,10 @@
 <template>
   <div class="app-container" v-if="userStore.userInfo">
     <UserDropdown />
-    <TabSwitch @tab-change="handleTabChange" />
-    <Chat v-if="currentTab === 'general'" />
-    <AdvancedMode v-else />
+<!--    <TabSwitch @tab-change="handleTabChange" />-->
+<!--    <Chat v-if="currentTab === 'general'" />-->
+<!--    <AdvancedMode v-else />-->
+    <AdvancedMode  />
   </div>
   <Login v-else />
 </template>

+ 14 - 9
src/entrypoints/sidepanel/component/StepsDisplay.vue

@@ -4,7 +4,7 @@
       <h2 class="steps-title">{{ parsedContent.title }}</h2>
       <!-- <p class="steps-id">计划ID: {{ parsedContent.planId }}</p> -->
       <el-steps 
-        :active="activeStep" 
+        :active="-1"
         finish-status="success"
         class="custom-steps"
         direction="vertical"
@@ -15,20 +15,20 @@
           :title="'步骤 ' + (index + 1)"
         >
           <template #icon>
-            <div class="step-icon-container">
+            <div class="step-icon-container"  >
               <div v-if="index < activeStep" class="step-icon completed">
                 <el-icon><Check /></el-icon>
               </div>
-              <div v-else-if="index === activeStep" class="step-icon current">
-                <el-icon><Loading /></el-icon>
-              </div>
+<!--              <div v-else-if="index === activeStep" class="step-icon current">-->
+<!--                <el-icon><Loading /></el-icon>-->
+<!--              </div>-->
               <div v-else style="border-radius: 50%;">
                 {{ index + 1 }}
               </div>
             </div>
           </template>
           <template #title>
-            <div class="step-title-container" @click.stop="toggleCollapse(index)">
+            <div class="step-title-container" @click.stop="toggleCollapse(index)"  >
               <span>步骤 {{ index + 1 }}</span>
               <el-button 
                 type="text" 
@@ -42,8 +42,8 @@
             </div>
           </template>
           <template #description>
-            <div :class="['step-description',{ 'collapsed': collapsedSteps[index] }]">
-              <div class="step-content">
+            <div :class="['step-description',{ 'collapsed': collapsedSteps[index] }]" >
+              <div class="step-content"  >
                 {{ step.description }}
                <div  v-if="response?.agentExecutionSequence[index]?.thinkActSteps" >
                     <div v-for="item,i in response.agentExecutionSequence[index]?.thinkActSteps" >
@@ -104,13 +104,18 @@
 </template>
 
 <script lang="ts" setup>
+chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
+  if (request.type == 'FROM_STEP') {
+    initialSteps.value = request.payload
+  }
+})
 import { ref, computed, onMounted, watch, onBeforeUnmount } from 'vue'
 import { ChatDotRound, Check, Loading, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
 import { deleteExecutor, executePlanByTemplateId, executorDetail } from '@/api/advance'
 import moment from 'moment'
 import { formatMessage } from '../utils/ai-service'
 
-
+const initialSteps=ref(0)
 // 定义props
 const props = defineProps({
   content: {

+ 1207 - 0
src/public/buildDomTree1.js

@@ -0,0 +1,1207 @@
+
+window.buildDomTree = (
+  args = {
+    doHighlightElements: true,
+    focusHighlightIndex: -1,
+    viewportExpansion: 0,
+    debugMode: false,
+  },
+) => {
+  const { doHighlightElements, focusHighlightIndex, viewportExpansion, debugMode } = args;
+  let highlightIndex = 0; // Reset highlight index
+
+  // Add timing stack to handle recursion
+  const TIMING_STACK = {
+    nodeProcessing: [],
+    treeTraversal: [],
+    highlighting: [],
+    current: null,
+  };
+
+  function pushTiming(type) {
+    TIMING_STACK[type] = TIMING_STACK[type] || [];
+    TIMING_STACK[type].push(performance.now());
+  }
+
+  function popTiming(type) {
+    const start = TIMING_STACK[type].pop();
+    const duration = performance.now() - start;
+    return duration;
+  }
+
+  // Only initialize performance tracking if in debug mode
+  const PERF_METRICS = debugMode
+    ? {
+        buildDomTreeCalls: 0,
+        timings: {
+          buildDomTree: 0,
+          highlightElement: 0,
+          isInteractiveElement: 0,
+          isElementVisible: 0,
+          isTopElement: 0,
+          isInExpandedViewport: 0,
+          isTextNodeVisible: 0,
+          getEffectiveScroll: 0,
+        },
+        cacheMetrics: {
+          boundingRectCacheHits: 0,
+          boundingRectCacheMisses: 0,
+          computedStyleCacheHits: 0,
+          computedStyleCacheMisses: 0,
+          getBoundingClientRectTime: 0,
+          getComputedStyleTime: 0,
+          boundingRectHitRate: 0,
+          computedStyleHitRate: 0,
+          overallHitRate: 0,
+        },
+        nodeMetrics: {
+          totalNodes: 0,
+          processedNodes: 0,
+          skippedNodes: 0,
+        },
+        buildDomTreeBreakdown: {
+          totalTime: 0,
+          totalSelfTime: 0,
+          buildDomTreeCalls: 0,
+          domOperations: {
+            getBoundingClientRect: 0,
+            getComputedStyle: 0,
+          },
+          domOperationCounts: {
+            getBoundingClientRect: 0,
+            getComputedStyle: 0,
+          },
+        },
+      }
+    : null;
+
+  // Simple timing helper that only runs in debug mode
+  function measureTime(fn) {
+    if (!debugMode) return fn;
+    return function (...args) {
+      const start = performance.now();
+      const result = fn.apply(this, args);
+      const duration = performance.now() - start;
+      return result;
+    };
+  }
+
+  // Helper to measure DOM operations
+  function measureDomOperation(operation, name) {
+    if (!debugMode) return operation();
+
+    const start = performance.now();
+    const result = operation();
+    const duration = performance.now() - start;
+
+    if (PERF_METRICS && name in PERF_METRICS.buildDomTreeBreakdown.domOperations) {
+      PERF_METRICS.buildDomTreeBreakdown.domOperations[name] += duration;
+      PERF_METRICS.buildDomTreeBreakdown.domOperationCounts[name]++;
+    }
+
+    return result;
+  }
+
+  // Add caching mechanisms at the top level
+  const DOM_CACHE = {
+    boundingRects: new WeakMap(),
+    computedStyles: new WeakMap(),
+    clearCache: () => {
+      DOM_CACHE.boundingRects = new WeakMap();
+      DOM_CACHE.computedStyles = new WeakMap();
+    },
+  };
+
+  // Cache helper functions
+  function getCachedBoundingRect(element) {
+    if (!element) return null;
+
+    if (DOM_CACHE.boundingRects.has(element)) {
+      if (debugMode && PERF_METRICS) {
+        PERF_METRICS.cacheMetrics.boundingRectCacheHits++;
+      }
+      return DOM_CACHE.boundingRects.get(element);
+    }
+
+    if (debugMode && PERF_METRICS) {
+      PERF_METRICS.cacheMetrics.boundingRectCacheMisses++;
+    }
+
+    let rect;
+    if (debugMode) {
+      const start = performance.now();
+      rect = element.getBoundingClientRect();
+      const duration = performance.now() - start;
+      if (PERF_METRICS) {
+        PERF_METRICS.buildDomTreeBreakdown.domOperations.getBoundingClientRect += duration;
+        PERF_METRICS.buildDomTreeBreakdown.domOperationCounts.getBoundingClientRect++;
+      }
+    } else {
+      rect = element.getBoundingClientRect();
+    }
+
+    if (rect) {
+      DOM_CACHE.boundingRects.set(element, rect);
+    }
+    return rect;
+  }
+
+  function getCachedComputedStyle(element) {
+    if (!element) return null;
+
+    if (DOM_CACHE.computedStyles.has(element)) {
+      if (debugMode && PERF_METRICS) {
+        PERF_METRICS.cacheMetrics.computedStyleCacheHits++;
+      }
+      return DOM_CACHE.computedStyles.get(element);
+    }
+
+    if (debugMode && PERF_METRICS) {
+      PERF_METRICS.cacheMetrics.computedStyleCacheMisses++;
+    }
+
+    let style;
+    if (debugMode) {
+      const start = performance.now();
+      style = window.getComputedStyle(element);
+      const duration = performance.now() - start;
+      if (PERF_METRICS) {
+        PERF_METRICS.buildDomTreeBreakdown.domOperations.getComputedStyle += duration;
+        PERF_METRICS.buildDomTreeBreakdown.domOperationCounts.getComputedStyle++;
+      }
+    } else {
+      style = window.getComputedStyle(element);
+    }
+
+    if (style) {
+      DOM_CACHE.computedStyles.set(element, style);
+    }
+    return style;
+  }
+
+  /**
+   * Hash map of DOM nodes indexed by their highlight index.
+   *
+   * @type {Object<string, any>}
+   */
+  const DOM_HASH_MAP = {};
+
+  const ID = { current: 0 };
+
+  const HIGHLIGHT_CONTAINER_ID = 'playwright-highlight-container';
+
+  /**
+   * Highlights an element in the DOM and returns the index of the next element.
+   */
+  function highlightElement(element, index, parentIframe = null) {
+    if (!element) return index;
+
+    // Store overlays and the single label for updating
+    const overlays = [];
+    let label = null;
+    let labelWidth = 20; // Approximate label width
+    let labelHeight = 16; // Approximate label height
+
+    try {
+      // Create or get highlight container
+      let container = document.getElementById(HIGHLIGHT_CONTAINER_ID);
+      if (!container) {
+        container = document.createElement('div');
+        container.id = HIGHLIGHT_CONTAINER_ID;
+        container.style.position = 'fixed';
+        container.style.pointerEvents = 'none';
+        container.style.top = '0';
+        container.style.left = '0';
+        container.style.width = '100%';
+        container.style.height = '100%';
+        container.style.zIndex = '2147483647';
+        document.body.appendChild(container);
+      }
+
+      // Get element client rects
+      const rects = element.getClientRects(); // Use getClientRects()
+
+      if (!rects || rects.length === 0) return index; // Exit if no rects
+
+      // Generate a color based on the index
+      const colors = [
+        '#FF0000',
+        '#00FF00',
+        '#0000FF',
+        '#FFA500',
+        '#800080',
+        '#008080',
+        '#FF69B4',
+        '#4B0082',
+        '#FF4500',
+        '#2E8B57',
+        '#DC143C',
+        '#4682B4',
+      ];
+      const colorIndex = index % colors.length;
+      const baseColor = colors[colorIndex];
+      const backgroundColor = baseColor + '1A'; // 10% opacity version of the color
+
+      // Get iframe offset if necessary
+      let iframeOffset = { x: 0, y: 0 };
+      if (parentIframe) {
+        const iframeRect = parentIframe.getBoundingClientRect(); // Keep getBoundingClientRect for iframe offset
+        iframeOffset.x = iframeRect.left;
+        iframeOffset.y = iframeRect.top;
+      }
+
+      // Create highlight overlays for each client rect
+      for (const rect of rects) {
+        if (rect.width === 0 || rect.height === 0) continue; // Skip empty rects
+
+        const overlay = document.createElement('div');
+        overlay.style.position = 'fixed';
+        overlay.style.border = `2px solid ${baseColor}`;
+        overlay.style.backgroundColor = backgroundColor;
+        overlay.style.pointerEvents = 'none';
+        overlay.style.boxSizing = 'border-box';
+
+        const top = rect.top + iframeOffset.y;
+        const left = rect.left + iframeOffset.x;
+
+        overlay.style.top = `${top}px`;
+        overlay.style.left = `${left}px`;
+        overlay.style.width = `${rect.width}px`;
+        overlay.style.height = `${rect.height}px`;
+
+        container.appendChild(overlay);
+        overlays.push({ element: overlay, initialRect: rect }); // Store overlay and its rect
+      }
+
+      // Create and position a single label relative to the first rect
+      const firstRect = rects[0];
+      label = document.createElement('div');
+      label.className = 'playwright-highlight-label';
+      label.style.position = 'fixed';
+      label.style.background = baseColor;
+      label.style.color = 'white';
+      label.style.padding = '1px 4px';
+      label.style.borderRadius = '4px';
+      label.style.fontSize = `${Math.min(12, Math.max(8, firstRect.height / 2))}px`;
+      label.textContent = index;
+
+      labelWidth = label.offsetWidth > 0 ? label.offsetWidth : labelWidth; // Update actual width if possible
+      labelHeight = label.offsetHeight > 0 ? label.offsetHeight : labelHeight; // Update actual height if possible
+
+      const firstRectTop = firstRect.top + iframeOffset.y;
+      const firstRectLeft = firstRect.left + iframeOffset.x;
+
+      let labelTop = firstRectTop + 2;
+      let labelLeft = firstRectLeft + firstRect.width - labelWidth - 2;
+
+      // Adjust label position if first rect is too small
+      if (firstRect.width < labelWidth + 4 || firstRect.height < labelHeight + 4) {
+        labelTop = firstRectTop - labelHeight - 2;
+        labelLeft = firstRectLeft + firstRect.width - labelWidth; // Align with right edge
+        if (labelLeft < iframeOffset.x) labelLeft = firstRectLeft; // Prevent going off-left
+      }
+
+      // Ensure label stays within viewport bounds slightly better
+      labelTop = Math.max(0, Math.min(labelTop, window.innerHeight - labelHeight));
+      labelLeft = Math.max(0, Math.min(labelLeft, window.innerWidth - labelWidth));
+
+      label.style.top = `${labelTop}px`;
+      label.style.left = `${labelLeft}px`;
+
+      container.appendChild(label);
+
+      // Update positions on scroll/resize
+      const updatePositions = () => {
+        const newRects = element.getClientRects(); // Get fresh rects
+        let newIframeOffset = { x: 0, y: 0 };
+
+        if (parentIframe) {
+          const iframeRect = parentIframe.getBoundingClientRect(); // Keep getBoundingClientRect for iframe
+          newIframeOffset.x = iframeRect.left;
+          newIframeOffset.y = iframeRect.top;
+        }
+
+        // Update each overlay
+        overlays.forEach((overlayData, i) => {
+          if (i < newRects.length) {
+            // Check if rect still exists
+            const newRect = newRects[i];
+            const newTop = newRect.top + newIframeOffset.y;
+            const newLeft = newRect.left + newIframeOffset.x;
+
+            overlayData.element.style.top = `${newTop}px`;
+            overlayData.element.style.left = `${newLeft}px`;
+            overlayData.element.style.width = `${newRect.width}px`;
+            overlayData.element.style.height = `${newRect.height}px`;
+            overlayData.element.style.display = newRect.width === 0 || newRect.height === 0 ? 'none' : 'block';
+          } else {
+            // If fewer rects now, hide extra overlays
+            overlayData.element.style.display = 'none';
+          }
+        });
+
+        // If there are fewer new rects than overlays, hide the extras
+        if (newRects.length < overlays.length) {
+          for (let i = newRects.length; i < overlays.length; i++) {
+            overlays[i].element.style.display = 'none';
+          }
+        }
+
+        // Update label position based on the first new rect
+        if (label && newRects.length > 0) {
+          const firstNewRect = newRects[0];
+          const firstNewRectTop = firstNewRect.top + newIframeOffset.y;
+          const firstNewRectLeft = firstNewRect.left + newIframeOffset.x;
+
+          let newLabelTop = firstNewRectTop + 2;
+          let newLabelLeft = firstNewRectLeft + firstNewRect.width - labelWidth - 2;
+
+          if (firstNewRect.width < labelWidth + 4 || firstNewRect.height < labelHeight + 4) {
+            newLabelTop = firstNewRectTop - labelHeight - 2;
+            newLabelLeft = firstNewRectLeft + firstNewRect.width - labelWidth;
+            if (newLabelLeft < newIframeOffset.x) newLabelLeft = firstNewRectLeft;
+          }
+
+          // Ensure label stays within viewport bounds
+          newLabelTop = Math.max(0, Math.min(newLabelTop, window.innerHeight - labelHeight));
+          newLabelLeft = Math.max(0, Math.min(newLabelLeft, window.innerWidth - labelWidth));
+
+          label.style.top = `${newLabelTop}px`;
+          label.style.left = `${newLabelLeft}px`;
+          label.style.display = 'block';
+        } else if (label) {
+          // Hide label if element has no rects anymore
+          label.style.display = 'none';
+        }
+      };
+
+      window.addEventListener('scroll', updatePositions, true); // Use capture phase
+      window.addEventListener('resize', updatePositions);
+
+      // TODO: Add cleanup logic to remove listeners and elements when done.
+
+      return index + 1;
+    } finally {
+      // popTiming('highlighting'); // Assuming this was a typo and should be removed or corrected
+    }
+  }
+
+  /**
+   * Returns an XPath tree string for an element.
+   */
+  function getXPathTree(element, stopAtBoundary = true) {
+    const segments = [];
+    let currentElement = element;
+
+    while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
+      // Stop if we hit a shadow root or iframe
+      if (
+        stopAtBoundary &&
+        (currentElement.parentNode instanceof ShadowRoot || currentElement.parentNode instanceof HTMLIFrameElement)
+      ) {
+        break;
+      }
+
+      let index = 0;
+      let sibling = currentElement.previousSibling;
+      while (sibling) {
+        if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName === currentElement.nodeName) {
+          index++;
+        }
+        sibling = sibling.previousSibling;
+      }
+
+      const tagName = currentElement.nodeName.toLowerCase();
+      const xpathIndex = index > 0 ? `[${index + 1}]` : '';
+      segments.unshift(`${tagName}${xpathIndex}`);
+
+      currentElement = currentElement.parentNode;
+    }
+
+    return segments.join('/');
+  }
+
+  /**
+   * Checks if a text node is visible.
+   */
+  function isTextNodeVisible(textNode) {
+    try {
+      const range = document.createRange();
+      range.selectNodeContents(textNode);
+      const rects = range.getClientRects();
+
+      if (!rects || rects.length === 0) {
+        return false;
+      }
+
+      let isAnyRectVisible = false;
+      for (const rect of rects) {
+        if (rect.width > 0 && rect.height > 0) {
+          isAnyRectVisible = true;
+          break;
+        }
+      }
+
+      if (!isAnyRectVisible) {
+        return false;
+      }
+
+      const parentElement = textNode.parentElement;
+      if (!parentElement) return false;
+
+      const style = window.getComputedStyle(parentElement);
+      return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
+    } catch (e) {
+      console.warn('Error checking text node visibility:', e);
+      return false;
+    }
+  }
+
+  // Helper function to check if element is accepted
+  function isElementAccepted(element) {
+    if (!element || !element.tagName) return false;
+
+    // Always accept body and common container elements
+    const alwaysAccept = new Set(['body', 'div', 'main', 'article', 'section', 'nav', 'header', 'footer']);
+    const tagName = element.tagName.toLowerCase();
+
+    if (alwaysAccept.has(tagName)) return true;
+
+    const leafElementDenyList = new Set(['svg', 'script', 'style', 'link', 'meta', 'noscript', 'template']);
+
+    return !leafElementDenyList.has(tagName);
+  }
+
+  /**
+   * Checks if an element is visible.
+   */
+  function isElementVisible(element) {
+    const style = getCachedComputedStyle(element);
+    return style.display !== 'none' && style.visibility !== 'hidden';
+  }
+
+  /**
+   * Checks if an element is interactive.
+   *
+   * lots of comments, and uncommented code - to show the logic of what we already tried
+   *
+   * One of the things we tried at the beginning was also to use event listeners, and other fancy class, style stuff -> what actually worked best was just combining most things with computed cursor style :)
+   */
+  function isInteractiveElement(element) {
+    if (!element || element.nodeType !== Node.ELEMENT_NODE) {
+      return false;
+    }
+
+    // Define interactive cursors
+    const interactiveCursors = new Set([
+      'pointer', // Link/clickable elements
+      'move', // Movable elements
+      'text', // Text selection
+      'grab', // Grabbable elements
+      'grabbing', // Currently grabbing
+      'cell', // Table cell selection
+      'copy', // Copy operation
+      'alias', // Alias creation
+      'all-scroll', // Scrollable content
+      'col-resize', // Column resize
+      'context-menu', // Context menu available
+      'crosshair', // Precise selection
+      'e-resize', // East resize
+      'ew-resize', // East-west resize
+      'help', // Help available
+      'n-resize', // North resize
+      'ne-resize', // Northeast resize
+      'nesw-resize', // Northeast-southwest resize
+      'ns-resize', // North-south resize
+      'nw-resize', // Northwest resize
+      'nwse-resize', // Northwest-southeast resize
+      'row-resize', // Row resize
+      's-resize', // South resize
+      'se-resize', // Southeast resize
+      'sw-resize', // Southwest resize
+      'vertical-text', // Vertical text selection
+      'w-resize', // West resize
+      'zoom-in', // Zoom in
+      'zoom-out', // Zoom out
+    ]);
+
+    // Define non-interactive cursors
+    const nonInteractiveCursors = new Set([
+      'not-allowed', // Action not allowed
+      'no-drop', // Drop not allowed
+      'wait', // Processing
+      'progress', // In progress
+      'initial', // Initial value
+      'inherit', // Inherited value
+      //? Let's just include all potentially clickable elements that are not specifically blocked
+      // 'none',        // No cursor
+      // 'default',     // Default cursor
+      // 'auto',        // Browser default
+    ]);
+
+    function doesElementHaveInteractivePointer(element) {
+      if (element.tagName.toLowerCase() === 'html') return false;
+      const style = getCachedComputedStyle(element);
+
+      if (interactiveCursors.has(style.cursor)) return true;
+
+      return false;
+    }
+
+    let isInteractiveCursor = doesElementHaveInteractivePointer(element);
+
+    // Genius fix for almost all interactive elements
+    if (isInteractiveCursor) {
+      return true;
+    }
+
+    const interactiveElements = new Set([
+      'a', // Links
+      'button', // Buttons
+      'input', // All input types (text, checkbox, radio, etc.)
+      'select', // Dropdown menus
+      'textarea', // Text areas
+      'details', // Expandable details
+      'summary', // Summary element (clickable part of details)
+      'label', // Form labels (often clickable)
+      'option', // Select options
+      'optgroup', // Option groups
+      'fieldset', // Form fieldsets (can be interactive with legend)
+      'legend', // Fieldset legends
+    ]);
+
+    // Define explicit disable attributes and properties
+    const explicitDisableTags = new Set([
+      'disabled', // Standard disabled attribute
+      // 'aria-disabled',      // ARIA disabled state
+      'readonly', // Read-only state
+      // 'aria-readonly',     // ARIA read-only state
+      // 'aria-hidden',       // Hidden from accessibility
+      // 'hidden',            // Hidden attribute
+      // 'inert',             // Inert attribute
+      // 'aria-inert',        // ARIA inert state
+      // 'tabindex="-1"',     // Removed from tab order
+      // 'aria-hidden="true"' // Hidden from screen readers
+    ]);
+
+    // handle inputs, select, checkbox, radio, textarea, button and make sure they are not cursor style disabled/not-allowed
+    if (interactiveElements.has(element.tagName.toLowerCase())) {
+      const style = getCachedComputedStyle(element);
+
+      // Check for non-interactive cursor
+      if (nonInteractiveCursors.has(style.cursor)) {
+        return false;
+      }
+
+      // Check for explicit disable attributes
+      for (const disableTag of explicitDisableTags) {
+        if (
+          element.hasAttribute(disableTag) ||
+          element.getAttribute(disableTag) === 'true' ||
+          element.getAttribute(disableTag) === ''
+        ) {
+          return false;
+        }
+      }
+
+      // Check for disabled property on form elements
+      if (element.disabled) {
+        return false;
+      }
+
+      // Check for readonly property on form elements
+      if (element.readOnly) {
+        return false;
+      }
+
+      // Check for inert property
+      if (element.inert) {
+        return false;
+      }
+
+      return true;
+    }
+
+    const tagName = element.tagName.toLowerCase();
+    const role = element.getAttribute('role');
+    const ariaRole = element.getAttribute('aria-role');
+
+    // Added enhancement to capture dropdown interactive elements
+    if (
+      element.classList &&
+      (element.classList.contains('button') ||
+        element.classList.contains('dropdown-toggle') ||
+        element.getAttribute('data-index') ||
+        element.getAttribute('data-toggle') === 'dropdown' ||
+        element.getAttribute('aria-haspopup') === 'true')
+    ) {
+      return true;
+    }
+
+    const interactiveRoles = new Set([
+      'button', // Directly clickable element
+      // 'link',            // Clickable link
+      // 'menuitem',        // Clickable menu item
+      'menuitemradio', // Radio-style menu item (selectable)
+      'menuitemcheckbox', // Checkbox-style menu item (toggleable)
+      'radio', // Radio button (selectable)
+      'checkbox', // Checkbox (toggleable)
+      'tab', // Tab (clickable to switch content)
+      'switch', // Toggle switch (clickable to change state)
+      'slider', // Slider control (draggable)
+      'spinbutton', // Number input with up/down controls
+      'combobox', // Dropdown with text input
+      'searchbox', // Search input field
+      'textbox', // Text input field
+      // 'listbox',         // Selectable list
+      'option', // Selectable option in a list
+      'scrollbar', // Scrollable control
+    ]);
+
+    // Basic role/attribute checks
+    const hasInteractiveRole =
+      interactiveElements.has(tagName) || interactiveRoles.has(role) || interactiveRoles.has(ariaRole);
+
+    if (hasInteractiveRole) return true;
+
+    // check whether element has event listeners
+    try {
+      if (typeof getEventListeners === 'function') {
+        const listeners = getEventListeners(element);
+        const mouseEvents = ['click', 'mousedown', 'mouseup', 'dblclick'];
+        for (const eventType of mouseEvents) {
+          if (listeners[eventType] && listeners[eventType].length > 0) {
+            return true; // Found a mouse interaction listener
+          }
+        }
+      } else {
+        // Fallback: Check common event attributes if getEventListeners is not available
+        const commonMouseAttrs = ['onclick', 'onmousedown', 'onmouseup', 'ondblclick'];
+        if (commonMouseAttrs.some(attr => element.hasAttribute(attr))) {
+          return true;
+        }
+      }
+    } catch (e) {
+      // console.warn(`Could not check event listeners for ${element.tagName}:`, e);
+      // If checking listeners fails, rely on other checks
+    }
+
+    return false;
+  }
+
+  /**
+   * Checks if an element is the topmost element at its position.
+   */
+  function isTopElement(element) {
+    const rects = element.getClientRects();
+    if (!rects || rects.length === 0) {
+      return false;
+    }
+    return true;
+  }
+  /**
+   * Checks if an element is within the expanded viewport.
+   */
+  function isInExpandedViewport(element, viewportExpansion) {
+    return true;
+
+    if (viewportExpansion === -1) {
+      return true;
+    }
+
+    const rects = element.getClientRects(); // Use getClientRects
+
+    if (!rects || rects.length === 0) {
+      // Fallback to getBoundingClientRect if getClientRects is empty,
+      // useful for elements like <svg> that might not have client rects but have a bounding box.
+      const boundingRect = getCachedBoundingRect(element);
+      if (!boundingRect || boundingRect.width === 0 || boundingRect.height === 0) {
+        return false;
+      }
+      return !(
+        boundingRect.bottom < -viewportExpansion ||
+        boundingRect.top > window.innerHeight + viewportExpansion ||
+        boundingRect.right < -viewportExpansion ||
+        boundingRect.left > window.innerWidth + viewportExpansion
+      );
+    }
+
+    // Check if *any* client rect is within the viewport
+    for (const rect of rects) {
+      if (rect.width === 0 || rect.height === 0) continue; // Skip empty rects
+
+      if (
+        !(
+          rect.bottom < -viewportExpansion ||
+          rect.top > window.innerHeight + viewportExpansion ||
+          rect.right < -viewportExpansion ||
+          rect.left > window.innerWidth + viewportExpansion
+        )
+      ) {
+        return true; // Found at least one rect in the viewport
+      }
+    }
+
+    return false; // No rects were found in the viewport
+  }
+
+  // Add this new helper function
+  function getEffectiveScroll(element) {
+    let currentEl = element;
+    let scrollX = 0;
+    let scrollY = 0;
+
+    return measureDomOperation(() => {
+      while (currentEl && currentEl !== document.documentElement) {
+        if (currentEl.scrollLeft || currentEl.scrollTop) {
+          scrollX += currentEl.scrollLeft;
+          scrollY += currentEl.scrollTop;
+        }
+        currentEl = currentEl.parentElement;
+      }
+
+      scrollX += window.scrollX;
+      scrollY += window.scrollY;
+
+      return { scrollX, scrollY };
+    }, 'scrollOperations');
+  }
+
+  // Add these helper functions at the top level
+  function isInteractiveCandidate(element) {
+    if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
+
+    const tagName = element.tagName.toLowerCase();
+
+    // Fast-path for common interactive elements
+    const interactiveElements = new Set(['a', 'button', 'input', 'select', 'textarea', 'details', 'summary']);
+
+    if (interactiveElements.has(tagName)) return true;
+
+    // Quick attribute checks without getting full lists
+    const hasQuickInteractiveAttr =
+      element.hasAttribute('onclick') ||
+      element.hasAttribute('role') ||
+      element.hasAttribute('tabindex') ||
+      element.hasAttribute('aria-') ||
+      element.hasAttribute('data-action') ||
+      element.getAttribute('contenteditable') == 'true';
+
+    return hasQuickInteractiveAttr;
+  }
+
+  // --- Define constants for distinct interaction check ---
+  const DISTINCT_INTERACTIVE_TAGS = new Set([
+    'a',
+    'button',
+    'input',
+    'select',
+    'textarea',
+    'summary',
+    'details',
+    'label',
+    'option',
+  ]);
+  const INTERACTIVE_ROLES = new Set([
+    'button',
+    'link',
+    'menuitem',
+    'menuitemradio',
+    'menuitemcheckbox',
+    'radio',
+    'checkbox',
+    'tab',
+    'switch',
+    'slider',
+    'spinbutton',
+    'combobox',
+    'searchbox',
+    'textbox',
+    'listbox',
+    'option',
+    'scrollbar',
+  ]);
+
+  /**
+   * Checks if an element likely represents a distinct interaction
+   * separate from its parent (if the parent is also interactive).
+   */
+  function isElementDistinctInteraction(element) {
+    if (!element || element.nodeType !== Node.ELEMENT_NODE) {
+      return false;
+    }
+
+    const tagName = element.tagName.toLowerCase();
+    const role = element.getAttribute('role');
+
+    // Check if it's an iframe - always distinct boundary
+    if (tagName === 'iframe') {
+      return true;
+    }
+
+    // Check tag name
+    if (DISTINCT_INTERACTIVE_TAGS.has(tagName)) {
+      return true;
+    }
+    // Check interactive roles
+    if (role && INTERACTIVE_ROLES.has(role)) {
+      return true;
+    }
+    // Check contenteditable
+    if (element.isContentEditable || element.getAttribute('contenteditable') === 'true') {
+      return true;
+    }
+    // Check for common testing/automation attributes
+    if (element.hasAttribute('data-testid') || element.hasAttribute('data-cy') || element.hasAttribute('data-test')) {
+      return true;
+    }
+    // Check for explicit onclick handler (attribute or property)
+    if (element.hasAttribute('onclick') || typeof element.onclick === 'function') {
+      return true;
+    }
+    // Check for other common interaction event listeners
+    try {
+      if (typeof getEventListeners === 'function') {
+        const listeners = getEventListeners(element);
+        const interactionEvents = [
+          'mousedown',
+          'mouseup',
+          'keydown',
+          'keyup',
+          'submit',
+          'change',
+          'input',
+          'focus',
+          'blur',
+        ];
+        for (const eventType of interactionEvents) {
+          if (listeners[eventType] && listeners[eventType].length > 0) {
+            return true; // Found a common interaction listener
+          }
+        }
+      } else {
+        // Fallback: Check common event attributes if getEventListeners is not available
+        const commonEventAttrs = [
+          'onmousedown',
+          'onmouseup',
+          'onkeydown',
+          'onkeyup',
+          'onsubmit',
+          'onchange',
+          'oninput',
+          'onfocus',
+          'onblur',
+        ];
+        if (commonEventAttrs.some(attr => element.hasAttribute(attr))) {
+          return true;
+        }
+      }
+    } catch (e) {
+      // console.warn(`Could not check event listeners for ${element.tagName}:`, e);
+      // If checking listeners fails, rely on other checks
+    }
+
+    // Default to false: if it's interactive but doesn't match above,
+    // assume it triggers the same action as the parent.
+    return false;
+  }
+  // --- End distinct interaction check ---
+
+  /**
+   * Handles the logic for deciding whether to highlight an element and performing the highlight.
+   */
+  function handleHighlighting(nodeData, node, parentIframe, isParentHighlighted) {
+    if (!nodeData.isInteractive) return false;
+
+    let shouldHighlight = false;
+    if (!isParentHighlighted) {
+      shouldHighlight = true;
+    } else {
+      if (isElementDistinctInteraction(node)) {
+        shouldHighlight = true;
+      }
+    }
+
+    if (shouldHighlight) {
+      nodeData.isInViewport = true;
+      nodeData.highlightIndex = highlightIndex++;
+
+      if (doHighlightElements) {
+        if (focusHighlightIndex >= 0) {
+          if (focusHighlightIndex === nodeData.highlightIndex) {
+            highlightElement(node, nodeData.highlightIndex, parentIframe);
+          }
+        } else {
+          highlightElement(node, nodeData.highlightIndex, parentIframe);
+        }
+        return true;
+      }
+    }
+
+    return false;
+  }
+  /**
+   * Creates a node data object for a given node and its descendants.
+   */
+  function buildDomTree(node, parentIframe = null, isParentHighlighted = false) {
+    if (debugMode) PERF_METRICS.nodeMetrics.totalNodes++;
+
+    if (!node || node.id === HIGHLIGHT_CONTAINER_ID) {
+      if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+      return null;
+    }
+
+    // Special handling for root node (body)
+    if (node === document.body) {
+      const nodeData = {
+        tagName: 'body',
+        attributes: {},
+        xpath: '/body',
+        children: [],
+      };
+
+      // Process children of body
+      for (const child of node.childNodes) {
+        const domElement = buildDomTree(child, parentIframe, false); // Body's children have no highlighted parent initially
+        if (domElement) nodeData.children.push(domElement);
+      }
+
+      const id = `${ID.current++}`;
+      DOM_HASH_MAP[id] = nodeData;
+      if (debugMode) PERF_METRICS.nodeMetrics.processedNodes++;
+      return id;
+    }
+
+    // Early bailout for non-element nodes except text
+    if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.TEXT_NODE) {
+      if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+      return null;
+    }
+
+    // Process text nodes
+    if (node.nodeType === Node.TEXT_NODE) {
+      const textContent = node.textContent.trim();
+      if (!textContent) {
+        if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+        return null;
+      }
+
+      // Only check visibility for text nodes that might be visible
+      const parentElement = node.parentElement;
+      if (!parentElement || parentElement.tagName.toLowerCase() === 'script') {
+        if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+        return null;
+      }
+
+      const id = `${ID.current++}`;
+      DOM_HASH_MAP[id] = {
+        type: 'TEXT_NODE',
+        text: textContent,
+        isVisible: isTextNodeVisible(node),
+      };
+      if (debugMode) PERF_METRICS.nodeMetrics.processedNodes++;
+      return id;
+    }
+
+    // Quick checks for element nodes
+    if (node.nodeType === Node.ELEMENT_NODE && !isElementAccepted(node)) {
+      if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+      return null;
+    }
+
+    // Early viewport check - only filter out elements clearly outside viewport
+    // if (viewportExpansion !== -1) {
+    //
+    //   const rect = getCachedBoundingRect(node); // Keep for initial quick check
+    //   const style = getCachedComputedStyle(node);
+    //
+    //   // Skip viewport check for fixed/sticky elements as they may appear anywhere
+    //   const isFixedOrSticky = style && (style.position === 'fixed' || style.position === 'sticky');
+    //
+    //   // Check if element has actual dimensions using offsetWidth/Height (quick check)
+    //   const hasSize = node.offsetWidth > 0 || node.offsetHeight > 0;
+    //
+    //   // Use getBoundingClientRect for the quick OUTSIDE check.
+    //   // isInExpandedViewport will do the more accurate check later if needed.
+    //   if (
+    //     !rect ||
+    //     (!isFixedOrSticky &&
+    //       !hasSize &&
+    //       (rect.bottom < -viewportExpansion ||
+    //         rect.top > window.innerHeight + viewportExpansion ||
+    //         rect.right < -viewportExpansion ||
+    //         rect.left > window.innerWidth + viewportExpansion))
+    //   ) {
+    //     // console.log("Skipping node outside viewport (quick check):", node.tagName, rect);
+    //     if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+    //     return null;
+    //   }
+    // }
+
+    // Process element node
+    const nodeData = {
+      tagName: node.tagName.toLowerCase(),
+      attributes: {},
+      xpath: getXPathTree(node, true),
+      children: [],
+    };
+
+    // Get attributes for interactive elements or potential text containers
+    if (
+      isInteractiveCandidate(node) ||
+      node.tagName.toLowerCase() === 'iframe' ||
+      node.tagName.toLowerCase() === 'body'
+    ) {
+      const attributeNames = node.getAttributeNames?.() || [];
+      for (const name of attributeNames) {
+        nodeData.attributes[name] = node.getAttribute(name);
+      }
+    }
+
+    let nodeWasHighlighted = false;
+    // Perform visibility, interactivity, and highlighting checks
+    if (node.nodeType === Node.ELEMENT_NODE) {
+      nodeData.isVisible = isElementVisible(node); // isElementVisible uses offsetWidth/Height, which is fine
+      if (nodeData.isVisible) {
+        nodeData.isTopElement = isTopElement(node);
+        if (nodeData.isTopElement) {
+          nodeData.isInteractive = isInteractiveElement(node);
+          // Call the dedicated highlighting function
+          nodeWasHighlighted = handleHighlighting(nodeData, node, parentIframe, isParentHighlighted);
+        }
+      }
+    }
+
+    // Process children, with special handling for iframes and rich text editors
+    if (node.tagName) {
+      const tagName = node.tagName.toLowerCase();
+
+      // Handle iframes
+      if (tagName === 'iframe') {
+        try {
+          const iframeDoc = node.contentDocument || node.contentWindow?.document;
+          if (iframeDoc) {
+            for (const child of iframeDoc.childNodes) {
+              const domElement = buildDomTree(child, node, false);
+              if (domElement) nodeData.children.push(domElement);
+            }
+          }
+        } catch (e) {
+          console.warn('Unable to access iframe:', e);
+        }
+      }
+      // Handle rich text editors and contenteditable elements
+      else if (
+        node.isContentEditable ||
+        node.getAttribute('contenteditable') === 'true' ||
+        node.id === 'tinymce' ||
+        node.classList.contains('mce-content-body') ||
+        (tagName === 'body' && node.getAttribute('data-id')?.startsWith('mce_'))
+      ) {
+        // Process all child nodes to capture formatted text
+        for (const child of node.childNodes) {
+          const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted);
+          if (domElement) nodeData.children.push(domElement);
+        }
+      } else {
+        // Handle shadow DOM
+        if (node.shadowRoot) {
+          nodeData.shadowRoot = true;
+          for (const child of node.shadowRoot.childNodes) {
+            const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted);
+            if (domElement) nodeData.children.push(domElement);
+          }
+        }
+        // Handle regular elements
+        for (const child of node.childNodes) {
+          // Pass the highlighted status of the *current* node to its children
+          const passHighlightStatusToChild = nodeWasHighlighted || isParentHighlighted;
+          const domElement = buildDomTree(child, parentIframe, passHighlightStatusToChild);
+          if (domElement) nodeData.children.push(domElement);
+        }
+      }
+    }
+
+    // Skip empty anchor tags
+    if (nodeData.tagName === 'a' && nodeData.children.length === 0 && !nodeData.attributes.href) {
+      if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
+      return null;
+    }
+
+    const id = `${ID.current++}`;
+    DOM_HASH_MAP[id] = nodeData;
+    if (debugMode) PERF_METRICS.nodeMetrics.processedNodes++;
+    return id;
+  }
+
+  // After all functions are defined, wrap them with performance measurement
+  // Remove buildDomTree from here as we measure it separately
+  highlightElement = measureTime(highlightElement);
+  isInteractiveElement = measureTime(isInteractiveElement);
+  isElementVisible = measureTime(isElementVisible);
+  isTopElement = measureTime(isTopElement);
+  isInExpandedViewport = measureTime(isInExpandedViewport);
+  isTextNodeVisible = measureTime(isTextNodeVisible);
+  getEffectiveScroll = measureTime(getEffectiveScroll);
+
+  const rootId = buildDomTree(document.body);
+
+  // Clear the cache before starting
+  DOM_CACHE.clearCache();
+
+  // Only process metrics in debug mode
+  if (debugMode && PERF_METRICS) {
+    // Convert timings to seconds and add useful derived metrics
+    Object.keys(PERF_METRICS.timings).forEach(key => {
+      PERF_METRICS.timings[key] = PERF_METRICS.timings[key] / 1000;
+    });
+
+    Object.keys(PERF_METRICS.buildDomTreeBreakdown).forEach(key => {
+      if (typeof PERF_METRICS.buildDomTreeBreakdown[key] === 'number') {
+        PERF_METRICS.buildDomTreeBreakdown[key] = PERF_METRICS.buildDomTreeBreakdown[key] / 1000;
+      }
+    });
+
+    // Add some useful derived metrics
+    if (PERF_METRICS.buildDomTreeBreakdown.buildDomTreeCalls > 0) {
+      PERF_METRICS.buildDomTreeBreakdown.averageTimePerNode =
+        PERF_METRICS.buildDomTreeBreakdown.totalTime / PERF_METRICS.buildDomTreeBreakdown.buildDomTreeCalls;
+    }
+
+    PERF_METRICS.buildDomTreeBreakdown.timeInChildCalls =
+      PERF_METRICS.buildDomTreeBreakdown.totalTime - PERF_METRICS.buildDomTreeBreakdown.totalSelfTime;
+
+    // Add average time per operation to the metrics
+    Object.keys(PERF_METRICS.buildDomTreeBreakdown.domOperations).forEach(op => {
+      const time = PERF_METRICS.buildDomTreeBreakdown.domOperations[op];
+      const count = PERF_METRICS.buildDomTreeBreakdown.domOperationCounts[op];
+      if (count > 0) {
+        PERF_METRICS.buildDomTreeBreakdown.domOperations[`${op}Average`] = time / count;
+      }
+    });
+
+    // Calculate cache hit rates
+    const boundingRectTotal =
+      PERF_METRICS.cacheMetrics.boundingRectCacheHits + PERF_METRICS.cacheMetrics.boundingRectCacheMisses;
+    const computedStyleTotal =
+      PERF_METRICS.cacheMetrics.computedStyleCacheHits + PERF_METRICS.cacheMetrics.computedStyleCacheMisses;
+
+    if (boundingRectTotal > 0) {
+      PERF_METRICS.cacheMetrics.boundingRectHitRate =
+        PERF_METRICS.cacheMetrics.boundingRectCacheHits / boundingRectTotal;
+    }
+
+    if (computedStyleTotal > 0) {
+      PERF_METRICS.cacheMetrics.computedStyleHitRate =
+        PERF_METRICS.cacheMetrics.computedStyleCacheHits / computedStyleTotal;
+    }
+
+    if (boundingRectTotal + computedStyleTotal > 0) {
+      PERF_METRICS.cacheMetrics.overallHitRate =
+        (PERF_METRICS.cacheMetrics.boundingRectCacheHits + PERF_METRICS.cacheMetrics.computedStyleCacheHits) /
+        (boundingRectTotal + computedStyleTotal);
+    }
+  }
+
+  return debugMode ? { rootId, map: DOM_HASH_MAP, perfMetrics: PERF_METRICS } : { rootId, map: DOM_HASH_MAP };
+};

+ 66 - 14
src/utils/navigator.js

@@ -17,7 +17,7 @@ export const navigator=()=> {
             protocol: 'cdp'
         };
         console.log(connectOptions)
-        let browser;
+        // let browser;
 
         try {
             // 连接到浏览器
@@ -25,7 +25,7 @@ export const navigator=()=> {
             const [page] = await browser.pages();
             await chrome.scripting.executeScript({
                 target: {tabId: tabId},
-                files: ['buildDomTree.js'], // 注入外部文件
+                files: ['buildDomTree1.js'], // 注入外部文件
                 world: "MAIN"
             });
             // await page.waitForFunction(() => typeof window.yourFunc === 'function');
@@ -45,12 +45,29 @@ export const navigator=()=> {
                         return null;
                     }
                 }
+                function cleanHtmlTags(htmlStr, attrs = ['name', 'href']) {
+                    const doc = new DOMParser().parseFromString(htmlStr.replace(/[\r\n]+/g, ''), 'text/html');
+                    // 遍历所有元素
+                    const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT);
+                    while (walker.nextNode()) {
+                        const node = walker.currentNode;
+                        // 移除指定属性
+                        attrs.forEach(attr => node.removeAttribute(attr));
+                        // 只保留当前节点和其文本内容
+                        node.innerHTML = node.textContent;
+                    }
 
+                    return doc.body.innerHTML;
+                }
                 const domList = Object.values(buildDomTree.map)
                     .filter(n => typeof n === "object" && n.highlightIndex !== undefined)
                     .map(n => findDomByXpath(n.xpath))
                     .filter(x => !!x)
                     .map((item, index) => `[${index}] ${item.outerHTML}`)
+                    .map((item)=>{
+                        const attrs = ['name', 'href','src','class','id','target',]
+                        return cleanHtmlTags(item, attrs);
+                    })
                     .join("\n")
                 console.log(domList)
                 return {buildDomTree:buildDomTree,domList:domList}
@@ -58,9 +75,10 @@ export const navigator=()=> {
             return {domState:domState, page:page,tabitem:tabitem};
         } finally {
             // if (browser) await browser.disconnect();
+
         }
     }
-    async function browserautomate(domState,page,type,targetIndex) {
+    async function browserautomate(domState,page,type,targetIndex,text) {
 console.log(domState,page,type,targetIndex,browser)
         // 查找目标元素
         const targetElement = findElementByIndex(domState.map, targetIndex);
@@ -76,12 +94,18 @@ console.log(domState,page,type,targetIndex,browser)
                 case 'click':
                     await page.click(selector, {delay: 100});
                     break;
+                case 'key_enter':
+                    await page.click(selector, {delay: 100});
+                    break;
                 case 'navigate':
                     await page.goto('https://example.com', {waitUntil: 'networkidle2'});
                     break;
                 case 'getcontent':
                     console.log(await page.content());
                     break;
+                case 'input_text':
+                    await page.type(selector, text, {delay: 600});
+                    break;
                 // 添加更多命令支持,如:
                 case 'form':
                     const formData = [{
@@ -119,10 +143,24 @@ console.log(domState,page,type,targetIndex,browser)
             // 刷新 DOM 状态
             await page.evaluate(() => {
                 document.getElementById('playwright-highlight-container')?.remove();
-                window.buildDomTree({doHighlightElements: true});
             });
+            if (browser) await browser.disconnect();
+        }finally {
+            console.log(123456789)
+            await page.evaluate(() => {
+                document.getElementById('playwright-highlight-container')?.remove();
+            });
+            if (browser) await browser.disconnect();
         }
     }
+    function allTabs(){
+        return new Promise( (resolve, reject) => {
+            chrome.tabs.query({}, (tabs) => {
+                console.log('tabs', tabs)
+                resolve(tabs);
+            });
+        })
+    }
 
     function getActiveTabId() {
         return new Promise((resolve) => {
@@ -163,8 +201,9 @@ console.log(domState,page,type,targetIndex,browser)
     function switchTabOrOpenNew(url) {
         return new Promise(resolve => {
             // 查询是否存在指定的标签页
-            chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, async (tabs) => {
-                let tabId = tabs.find(tabs => tabs.url === url);
+            // chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, async (tabs) => {
+                chrome.tabs.query({}, async (tabs) => {
+                let tabId = tabs.find(tabs => tabs.url.replace(/\/$/, '') === url);
                 console.log(tabs, tabId,url)
                 if (tabId) {
                     // 如果存在,则激活该标签页
@@ -186,6 +225,7 @@ console.log(domState,page,type,targetIndex,browser)
     }
 
     async function agent(payload) {
+
         for(let item of payload.steps){
             console.log(item)
             if(item.stepIndex === 0){
@@ -210,15 +250,16 @@ console.log(domState,page,type,targetIndex,browser)
                 }
                 let add = await getexecuteapi(params)
                 console.log(add)
-                let toolParameters=add.data.plan.steps[0].toolParameters
-                console.log(JSON.parse(toolParameters).action)
-                if (JSON.parse(toolParameters).action == 'navigate') {
-                    await switchTabOrOpenNew(JSON.parse(toolParameters).url)
+
+                let toolExecution=JSON.parse(add.data.plan.steps[0].toolExecution)[0].toolParameters
+                console.log(JSON.parse(toolExecution).action)
+                if (JSON.parse(toolExecution).action == 'navigate') {
+                    await switchTabOrOpenNew(JSON.parse(toolExecution).url)
                 }
             }else {
-
-                let browdata =  await  browsercomm()
+                    let browdata=  await  browsercomm()
         console.log(browdata)
+               const tabs = await allTabs()
         let params = {
             conversationId: payload.conversationId,
             messageId: payload.messageId,
@@ -231,8 +272,9 @@ console.log(domState,page,type,targetIndex,browser)
                 osArch: "arm64",
                 url: browdata.tabitem.url,
                 title: browdata.tabitem.title,
-                tabs: [],
+                tabs: tabs,
                 interactiveElements: browdata.domState.domList
+                // interactiveElements: "[0] <a>新闻</a>\n        [1] <a>hao123</a>\n        [2] <a>地图</a>\n        [3] <a>贴吧</a>\n        [4] <a>视频</a>\n        [5] <a>图片</a>\n        [6] <a>网盘</a>\n        [7] <a>文库</a>\n        [8] <a id=\"csaitab\"></a>\n        [9] <a name=\"tj_briicon\">更多</a>\n        [10] <a></a>\n        [11] <a name=\"tj_login\" id=\"s-top-loginbtn\">登录</a>\n        [12] <input placeholder=\"梁靖崑称优势是有王楚钦\" name=\"wd\" id=\"kw\" value=\"\"></input>\n        [13] <input type=\"submit\" id=\"su\" value=\"百度一下\"></input>\n        [14] <a>AI搜索已支持DeepSeek R1最新版立即体验</a>\n        [16] <a id=\"hotsearch-refresh-btn\">换一换</a>\n        [17] <a>0让“干坡坡”变“金窝窝”</a>\n        [18] <a>5福建一楼房发生爆炸 有人员"
             },
             resultSummary: null,
             needSummary: true,
@@ -240,8 +282,18 @@ console.log(domState,page,type,targetIndex,browser)
         }
         let add = await getexecuteapi(params)
                 console.log(add)
-                await  browserautomate(browdata.domState.buildDomTree,browdata.page,'click',1)
+                let toolExecution=add.data.plan.steps[item.stepIndex].toolExecution
+                if(toolExecution) {
+                    let toolExecution=JSON.parse(add.data.plan.steps[item.stepIndex].toolExecution)[0].toolParameters
+                    await browserautomate(browdata.domState.buildDomTree, browdata.page, JSON.parse(toolExecution).action, JSON.parse(toolExecution).index,JSON.parse(toolExecution).text)
+
+                }else {
+                    return
+                }
             }
+            chrome.runtime.sendMessage({
+                type: "FROM_STEP",
+                payload: item.stepIndex});
         }
     }