UNPKG

ai-debug-local-mcp

Version:

šŸŽÆ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

130 lines • 5.45 kB
/** * Flutter Semantic Filter * Intelligent filtering of semantic nodes to find the right elements */ export class FlutterSemanticFilter { /** * Filter semantic nodes to find the most likely target */ static filterNodes(nodes, searchText, options = {}) { const { maxWidth = 500, maxHeight = 200, requireClickable = true, excludeFullPage = true, preferExactMatch = true } = options; // First, find all nodes that contain the search text let matchingNodes = nodes.filter(node => { const label = node.label.toLowerCase(); const search = searchText.toLowerCase(); return label.includes(search); }); // Filter out nodes that are too large (likely containers) if (excludeFullPage) { matchingNodes = matchingNodes.filter(node => { // Exclude nodes that are likely full-page containers return node.bounds.width < 1200 && node.bounds.height < 800; }); } // Apply size constraints matchingNodes = matchingNodes.filter(node => { return node.bounds.width <= maxWidth && node.bounds.height <= maxHeight; }); // Filter by clickability if required if (requireClickable) { matchingNodes = matchingNodes.filter(node => node.isClickable); } // Sort by relevance matchingNodes.sort((a, b) => { // Prefer exact matches if (preferExactMatch) { const aExact = a.label.toLowerCase() === searchText.toLowerCase(); const bExact = b.label.toLowerCase() === searchText.toLowerCase(); if (aExact && !bExact) return -1; if (!aExact && bExact) return 1; } // Prefer smaller elements (more specific) const aArea = a.bounds.width * a.bounds.height; const bArea = b.bounds.width * b.bounds.height; // Prefer nodes with reasonable button-like dimensions const aIsButtonLike = this.isButtonLike(a); const bIsButtonLike = this.isButtonLike(b); if (aIsButtonLike && !bIsButtonLike) return -1; if (!aIsButtonLike && bIsButtonLike) return 1; // If both are button-like or neither is, prefer smaller area return aArea - bArea; }); return matchingNodes; } /** * Check if a node has button-like dimensions */ static isButtonLike(node) { const { width, height } = node.bounds; // Typical button dimensions const minWidth = 60; const maxWidth = 400; const minHeight = 30; const maxHeight = 100; // Aspect ratio check (buttons are usually wider than tall) const aspectRatio = width / height; const minAspectRatio = 1.5; const maxAspectRatio = 10; return (width >= minWidth && width <= maxWidth && height >= minHeight && height <= maxHeight && aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio); } /** * Find the best match for a button */ static findButton(nodes, buttonText) { // Use strict filtering for buttons const filtered = this.filterNodes(nodes, buttonText, { maxWidth: 400, maxHeight: 100, requireClickable: true, excludeFullPage: true, preferExactMatch: true }); // Return the best match or null return filtered.length > 0 ? filtered[0] : null; } /** * Find form fields */ static findFormField(nodes, fieldLabel) { // Form fields might not be directly clickable but their containers are const filtered = this.filterNodes(nodes, fieldLabel, { maxWidth: 600, maxHeight: 150, requireClickable: false, // Fields might not be marked clickable excludeFullPage: true, preferExactMatch: false // Field labels might be partial matches }); // Look for the most likely input field for (const node of filtered) { // Check if it's likely an input field based on properties if (node.label.includes(fieldLabel) && node.bounds.height < 80) { return node; } } return filtered.length > 0 ? filtered[0] : null; } /** * Debug helper to log filtered results */ static debugFilter(nodes, searchText, options) { // console.log(`\nšŸ” Filtering for: "${searchText}"`); const allMatches = nodes.filter(node => node.label.toLowerCase().includes(searchText.toLowerCase())); // console.log(`Total matches: ${allMatches.length}`); const filtered = this.filterNodes(nodes, searchText, options); // console.log(`After filtering: ${filtered.length}`); filtered.forEach((node, index) => { // console.log(`\n[${index + 1}] ${node.label}`); // console.log(` Position: (${node.bounds.x}, ${node.bounds.y})`); // console.log(` Size: ${node.bounds.width}x${node.bounds.height}`); // console.log(` Clickable: ${node.isClickable}`); // console.log(` Button-like: ${this.isButtonLike(node)}`); }); } } //# sourceMappingURL=flutter-semantic-filter.js.map