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
JavaScript
/**
* 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