| import { NextResponse } from "next/server"; |
| import { getPage } from "@/lib/browser"; |
|
|
| export const runtime = "nodejs"; |
| export const dynamic = "force-dynamic"; |
|
|
| type ClickableElement = { |
| tag: string; |
| text: string; |
| x: number; |
| y: number; |
| width: number; |
| height: number; |
| type?: string; |
| href?: string; |
| }; |
|
|
| export async function GET() { |
| try { |
| const page = getPage(); |
|
|
| if (!page) { |
| return NextResponse.json( |
| { error: "No active browser session" }, |
| { status: 400 } |
| ); |
| } |
|
|
| const clickableElements = await page.evaluate(() => { |
| const selectors = [ |
| "a", |
| "button", |
| "input", |
| "select", |
| "textarea", |
| "summary", |
| "label", |
| "[role='button']", |
| "[role='link']", |
| "[role='menuitem']", |
| "[role='tab']", |
| "[tabindex]", |
| "[onclick]", |
| "[contenteditable='true']", |
| ]; |
|
|
| const found: ClickableElement[] = []; |
| const seen = new Set<Element>(); |
|
|
| const isVisible = (el: Element) => { |
| const rect = el.getBoundingClientRect(); |
| if (rect.width < 4 || rect.height < 4) return false; |
| if (rect.bottom < 0 || rect.right < 0) return false; |
| if (rect.top > window.innerHeight || rect.left > window.innerWidth) return false; |
|
|
| const style = window.getComputedStyle(el as HTMLElement); |
| if (style.visibility === "hidden" || style.display === "none") return false; |
| if (style.pointerEvents === "none") return false; |
|
|
| return true; |
| }; |
|
|
| for (const selector of selectors) { |
| document.querySelectorAll(selector).forEach((el) => { |
| if (seen.has(el)) return; |
| seen.add(el); |
|
|
| if (!isVisible(el)) return; |
|
|
| const rect = el.getBoundingClientRect(); |
| const htmlEl = el as HTMLElement; |
|
|
| const text = |
| (htmlEl.innerText || htmlEl.textContent || "").trim().slice(0, 100) || |
| htmlEl.getAttribute("aria-label") || |
| htmlEl.getAttribute("title") || |
| htmlEl.getAttribute("placeholder") || |
| ""; |
|
|
| found.push({ |
| tag: el.tagName.toLowerCase(), |
| text, |
| x: Math.round(rect.left), |
| y: Math.round(rect.top), |
| width: Math.round(rect.width), |
| height: Math.round(rect.height), |
| type: |
| "type" in el && typeof (el as HTMLInputElement).type === "string" |
| ? (el as HTMLInputElement).type |
| : undefined, |
| href: |
| el instanceof HTMLAnchorElement && el.href |
| ? el.href |
| : undefined, |
| }); |
| }); |
| } |
|
|
| return found.slice(0, 200); |
| }); |
|
|
| const viewport = page.viewportSize() || { width: 1280, height: 720 }; |
|
|
| const screenshotBuffer = await page.screenshot({ |
| type: "png", |
| fullPage: false, |
| animations: "disabled", |
| }); |
|
|
| const screenshot = screenshotBuffer.toString("base64"); |
|
|
| return NextResponse.json({ |
| screenshot, |
| clickableElements, |
| width: viewport.width, |
| height: viewport.height, |
| }); |
| } catch (e: unknown) { |
| const message = e instanceof Error ? e.message : "Unknown error"; |
| console.error("[/api/screenshot]", message); |
|
|
| return NextResponse.json({ error: message }, { status: 500 }); |
| } |
| } |