feat(browser): auto-commit con 60 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,12 +17,57 @@ type CdpScreenshotOpts struct {
|
||||
Format string
|
||||
}
|
||||
|
||||
// CdpScreenshot captura un screenshot de la pagina actual y lo guarda en outputPath.
|
||||
// fullPageClip es el rectangulo de recorte (en CSS pixels) que cubre la pagina
|
||||
// completa. scale=1 mantiene la resolucion nativa.
|
||||
type fullPageClip struct {
|
||||
X, Y, Width, Height, Scale float64
|
||||
}
|
||||
|
||||
// buildFullPageClip construye el clip de pagina completa a partir de la respuesta
|
||||
// de Page.getLayoutMetrics. Es una funcion pura: no toca red, recibe el mapa ya
|
||||
// deserializado por CDP y decide el rectangulo.
|
||||
//
|
||||
// Prefiere cssContentSize (dimensiones en CSS pixels, ya divididas por el DPR),
|
||||
// que es lo que espera el campo "clip" de Page.captureScreenshot. Cae a
|
||||
// contentSize (device pixels, protocolo antiguo) si cssContentSize no esta
|
||||
// presente. Devuelve ok=false cuando no hay un tamano valido (>0 en ambos ejes),
|
||||
// para que el caller capture solo el viewport en vez de un clip degenerado.
|
||||
func buildFullPageClip(metrics map[string]any) (fullPageClip, bool) {
|
||||
asFloat := func(v any) float64 {
|
||||
f, _ := v.(float64)
|
||||
return f
|
||||
}
|
||||
for _, key := range []string{"cssContentSize", "contentSize"} {
|
||||
size, ok := metrics[key].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
w := asFloat(size["width"])
|
||||
h := asFloat(size["height"])
|
||||
if w > 0 && h > 0 {
|
||||
return fullPageClip{X: 0, Y: 0, Width: w, Height: h, Scale: 1}, true
|
||||
}
|
||||
}
|
||||
return fullPageClip{}, false
|
||||
}
|
||||
|
||||
// CdpScreenshotBytes captura un screenshot de la pagina actual y devuelve los
|
||||
// bytes de imagen ya decodificados junto con su mimeType, sin tocar el disco.
|
||||
// Usa Page.captureScreenshot del protocolo CDP.
|
||||
// outputPath debe tener extension .png o .jpg/.jpeg segun el formato elegido.
|
||||
func CdpScreenshot(c *CDPConn, outputPath string, opts CdpScreenshotOpts) error {
|
||||
//
|
||||
// El mimeType es "image/jpeg" cuando opts pide JPEG y "image/png" en cualquier
|
||||
// otro caso (incluido el default cuando opts.Format esta vacio).
|
||||
//
|
||||
// Si opts.FullPage es true, consulta Page.getLayoutMetrics para construir un clip
|
||||
// que cubra la altura completa del documento (no solo el viewport) y mantiene
|
||||
// captureBeyondViewport=true para que Chrome renderice mas alla del area visible.
|
||||
//
|
||||
// Es la primitiva reutilizable de captura: util para devolver la imagen al LLM
|
||||
// como image content (bytes) sin pasar por archivo. CdpScreenshot compone sobre
|
||||
// ella para persistir a disco.
|
||||
func CdpScreenshotBytes(c *CDPConn, opts CdpScreenshotOpts) ([]byte, string, error) {
|
||||
if c == nil {
|
||||
return fmt.Errorf("cdp screenshot: conexion nula")
|
||||
return nil, "", fmt.Errorf("cdp screenshot: conexion nula")
|
||||
}
|
||||
|
||||
if opts.Format == "" {
|
||||
@@ -32,8 +77,13 @@ func CdpScreenshot(c *CDPConn, outputPath string, opts CdpScreenshotOpts) error
|
||||
opts.Quality = 80
|
||||
}
|
||||
|
||||
mimeType := "image/png"
|
||||
if opts.Format == "jpeg" {
|
||||
mimeType = "image/jpeg"
|
||||
}
|
||||
|
||||
params := map[string]any{
|
||||
"format": opts.Format,
|
||||
"format": opts.Format,
|
||||
"captureBeyondViewport": opts.FullPage,
|
||||
}
|
||||
if opts.Format == "jpeg" {
|
||||
@@ -41,27 +91,52 @@ func CdpScreenshot(c *CDPConn, outputPath string, opts CdpScreenshotOpts) error
|
||||
}
|
||||
|
||||
if opts.FullPage {
|
||||
// Expandir clip para capturar toda la pagina
|
||||
scrollHeight, err := CdpEvaluate(c, "document.documentElement.scrollHeight")
|
||||
if err == nil {
|
||||
params["clip"] = nil // dejar que Chrome capture todo
|
||||
_ = scrollHeight
|
||||
// Page.getLayoutMetrics da el tamano real del documento. Construimos el
|
||||
// clip con la funcion pura buildFullPageClip. Si la consulta falla o no
|
||||
// hay dimensiones validas, omitimos el clip y caemos a captura normal
|
||||
// (con captureBeyondViewport=true Chrome aun captura algo razonable).
|
||||
if metrics, err := c.sendCDP("Page.getLayoutMetrics", nil); err == nil {
|
||||
if clip, ok := buildFullPageClip(metrics); ok {
|
||||
params["clip"] = map[string]any{
|
||||
"x": clip.X,
|
||||
"y": clip.Y,
|
||||
"width": clip.Width,
|
||||
"height": clip.Height,
|
||||
"scale": clip.Scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result, err := c.sendCDP("Page.captureScreenshot", params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cdp screenshot: %w", err)
|
||||
return nil, "", fmt.Errorf("cdp screenshot: %w", err)
|
||||
}
|
||||
|
||||
dataStr, ok := result["data"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("cdp screenshot: campo data ausente en respuesta")
|
||||
return nil, "", fmt.Errorf("cdp screenshot: campo data ausente en respuesta")
|
||||
}
|
||||
|
||||
imgData, err := base64.StdEncoding.DecodeString(dataStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cdp screenshot: decodificar base64: %w", err)
|
||||
return nil, "", fmt.Errorf("cdp screenshot: decodificar base64: %w", err)
|
||||
}
|
||||
|
||||
return imgData, mimeType, nil
|
||||
}
|
||||
|
||||
// CdpScreenshot captura un screenshot de la pagina actual y lo guarda en outputPath.
|
||||
// outputPath debe tener extension .png o .jpg/.jpeg segun el formato elegido.
|
||||
//
|
||||
// Compone sobre CdpScreenshotBytes para obtener los bytes de imagen y luego crea
|
||||
// el directorio destino si no existe y escribe el archivo. Mismo comportamiento
|
||||
// observable que antes: mismos parametros, mismos efectos en disco, mismos
|
||||
// errores de captura.
|
||||
func CdpScreenshot(c *CDPConn, outputPath string, opts CdpScreenshotOpts) error {
|
||||
imgData, _, err := CdpScreenshotBytes(c, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Crear directorio si no existe
|
||||
|
||||
Reference in New Issue
Block a user