Files
langgraph/examples/web-navigation/mark_page.js
T

158 lines
4.7 KiB
JavaScript

const customCSS = `
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #27272a;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 0.375rem;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
`;
const styleTag = document.createElement("style");
styleTag.textContent = customCSS;
document.head.append(styleTag);
let labels = [];
function unmarkPage() {
// Unmark page logic
for (const label of labels) {
document.body.removeChild(label);
}
labels = [];
}
function markPage() {
unmarkPage();
var bodyRect = document.body.getBoundingClientRect();
var items = Array.prototype.slice
.call(document.querySelectorAll("*"))
.map(function (element) {
var vw = Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0
);
var vh = Math.max(
document.documentElement.clientHeight || 0,
window.innerHeight || 0
);
var textualContent = element.textContent.trim().replace(/\s{2,}/g, " ");
var elementType = element.tagName.toLowerCase();
var ariaLabel = element.getAttribute("aria-label") || "";
var rects = [...element.getClientRects()]
.filter((bb) => {
var center_x = bb.left + bb.width / 2;
var center_y = bb.top + bb.height / 2;
var elAtCenter = document.elementFromPoint(center_x, center_y);
return elAtCenter === element || element.contains(elAtCenter);
})
.map((bb) => {
const rect = {
left: Math.max(0, bb.left),
top: Math.max(0, bb.top),
right: Math.min(vw, bb.right),
bottom: Math.min(vh, bb.bottom),
};
return {
...rect,
width: rect.right - rect.left,
height: rect.bottom - rect.top,
};
});
var area = rects.reduce((acc, rect) => acc + rect.width * rect.height, 0);
return {
element: element,
include:
element.tagName === "INPUT" ||
element.tagName === "TEXTAREA" ||
element.tagName === "SELECT" ||
element.tagName === "BUTTON" ||
element.tagName === "A" ||
element.onclick != null ||
window.getComputedStyle(element).cursor == "pointer" ||
element.tagName === "IFRAME" ||
element.tagName === "VIDEO",
area,
rects,
text: textualContent,
type: elementType,
ariaLabel: ariaLabel,
};
})
.filter((item) => item.include && item.area >= 20);
// Only keep inner clickable items
items = items.filter(
(x) => !items.some((y) => x.element.contains(y.element) && !(x == y))
);
// Function to generate random colors
function getRandomColor() {
var letters = "0123456789ABCDEF";
var color = "#";
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// Lets create a floating border on top of these elements that will always be visible
items.forEach(function (item, index) {
item.rects.forEach((bbox) => {
newElement = document.createElement("div");
var borderColor = getRandomColor();
newElement.style.outline = `2px dashed ${borderColor}`;
newElement.style.position = "fixed";
newElement.style.left = bbox.left + "px";
newElement.style.top = bbox.top + "px";
newElement.style.width = bbox.width + "px";
newElement.style.height = bbox.height + "px";
newElement.style.pointerEvents = "none";
newElement.style.boxSizing = "border-box";
newElement.style.zIndex = 2147483647;
// newElement.style.background = `${borderColor}80`;
// Add floating label at the corner
var label = document.createElement("span");
label.textContent = index;
label.style.position = "absolute";
// These we can tweak if we want
label.style.top = "-19px";
label.style.left = "0px";
label.style.background = borderColor;
// label.style.background = "black";
label.style.color = "white";
label.style.padding = "2px 4px";
label.style.fontSize = "12px";
label.style.borderRadius = "2px";
newElement.appendChild(label);
document.body.appendChild(newElement);
labels.push(newElement);
// item.element.setAttribute("-ai-label", label.textContent);
});
});
const coordinates = items.flatMap((item) =>
item.rects.map(({ left, top, width, height }) => ({
x: (left + left + width) / 2,
y: (top + top + height) / 2,
type: item.type,
text: item.text,
ariaLabel: item.ariaLabel,
}))
);
return coordinates;
}