diff options
Diffstat (limited to 'src/domains/screenshot.ts')
| -rw-r--r-- | src/domains/screenshot.ts | 79 |
1 files changed, 1 insertions, 78 deletions
diff --git a/src/domains/screenshot.ts b/src/domains/screenshot.ts index 4b5732d..9f2587c 100644 --- a/src/domains/screenshot.ts +++ b/src/domains/screenshot.ts @@ -1,15 +1,10 @@ import { readFileSync } from "node:fs"; -import { writeFile, rename } from "node:fs/promises"; import { Type } from "typebox"; import { Image, type ImageTheme, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; -import type { BrowserClient } from "../client"; import { type Result, ok } from "../util/result"; import { defineBrowserTool, type ToolErr, type ToolOk } from "../util/tool"; -import { screenshotPath } from "../util/paths"; -import { loadSharp } from "../util/sharp-shim"; import { asRecord, asString } from "../util/guards"; -import { safeJs } from "../util/js-template"; -import { cdpCall, evalJs } from "./cdp-call"; +import { captureBase, resizeIfNeeded } from "./screenshot-capture"; const ScreenshotArgs = Type.Object({ fullPage: Type.Optional(Type.Boolean({ default: false, description: "Capture beyond viewport" })), @@ -18,50 +13,6 @@ const ScreenshotArgs = Type.Object({ maxDim: Type.Optional(Type.Integer({ minimum: 100, maximum: 8000, description: "If max(w,h) exceeds this, resize via sharp." })), }); -type CaptureArgs = { - readonly fullPage?: boolean; - readonly format?: "png" | "jpeg"; - readonly quality?: number; -}; - -const captureBase = async ( - client: BrowserClient, - args: CaptureArgs, -): Promise<Result<{ readonly path: string; readonly format: "png" | "jpeg" }, ToolErr>> => { - const format = args.format ?? "png"; - const quality = args.quality ?? 80; - const path = screenshotPath(client.namespace, format); - const r = await cdpCall(client, "Page.captureScreenshot", { - format, - captureBeyondViewport: args.fullPage ?? false, - ...(format === "jpeg" ? { quality } : {}), - }); - if (!r.success) return r; - await writeFile(path, Buffer.from(r.data.data, "base64")); - return ok({ path, format }); -}; - -const resizeIfNeeded = async ( - path: string, - maxDim: number, -): Promise<{ readonly note: string }> => { - const load = await loadSharp(); - if (load.kind === "missing") return { note: " (maxDim ignored: install sharp for auto-resize)" }; - if (load.kind === "error") return { note: ` (maxDim ignored: sharp failed to load: ${load.message})` }; - try { - const meta = await load.sharp(path).metadata(); - const w = meta.width ?? 0; - const h = meta.height ?? 0; - if (Math.max(w, h) <= maxDim) return { note: "" }; - const tmp = `${path}.resized`; - await load.sharp(path).resize(maxDim, maxDim, { fit: "inside" }).toFile(tmp); - await rename(tmp, path); - return { note: ` (resized to fit ${maxDim}px)` }; - } catch (e) { - return { note: ` (maxDim ignored: sharp threw: ${e instanceof Error ? e.message : String(e)})` }; - } -}; - export const screenshotTool = defineBrowserTool({ name: "browser_screenshot", label: "Browser Screenshot", @@ -123,31 +74,3 @@ export const screenshotTool = defineBrowserTool({ } }, }); - -export const captureWithCrosshair = async ( - client: BrowserClient, - args: { readonly x: number; readonly y: number; readonly format?: "png" | "jpeg"; readonly quality?: number }, -): Promise<Result<{ readonly path: string }, ToolErr>> => { - const cap = await captureBase(client, args); - if (!cap.success) return cap; - const load = await loadSharp(); - if (load.kind !== "ok") return ok({ path: cap.data.path }); - try { - const dprR = await evalJs(client, safeJs`window.devicePixelRatio`); - const dpr = dprR.success && typeof dprR.data === "number" ? dprR.data : 1; - const meta = await load.sharp(cap.data.path).metadata(); - const w = meta.width ?? 0; - const h = meta.height ?? 0; - const px = Math.round(args.x * dpr); - const py = Math.round(args.y * dpr); - const r = Math.round(15 * dpr); - const stroke = Math.max(2, Math.round(3 * dpr)); - const svg = `<svg width="${w}" height="${h}"><circle cx="${px}" cy="${py}" r="${r}" fill="none" stroke="red" stroke-width="${stroke}" opacity="0.8"/><line x1="${px - r - 5}" y1="${py}" x2="${px + r + 5}" y2="${py}" stroke="red" stroke-width="${Math.max(1, stroke - 1)}" opacity="0.8"/><line x1="${px}" y1="${py - r - 5}" x2="${px}" y2="${py + r + 5}" stroke="red" stroke-width="${Math.max(1, stroke - 1)}" opacity="0.8"/></svg>`; - const tmp = `${cap.data.path}.debug`; - await load.sharp(cap.data.path).composite([{ input: Buffer.from(svg), top: 0, left: 0 }]).toFile(tmp); - await rename(tmp, cap.data.path); - return ok({ path: cap.data.path }); - } catch { - return ok({ path: cap.data.path }); - } -}; |
