Cristobal299 commited on
Commit
9b2bd6c
·
verified ·
1 Parent(s): 849b339

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +12 -0
  2. README.md +10 -4
  3. app.py +126 -0
  4. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+ RUN playwright install --with-deps chromium
8
+
9
+ COPY . .
10
+
11
+ EXPOSE 7860
12
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,16 @@
1
  ---
2
- title: Teste
3
- emoji: 🐠
4
  colorFrom: purple
5
- colorTo: green
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
1
  ---
2
+ title: Forja Tester
3
+ emoji: 🧪
4
  colorFrom: purple
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ # Forja Tester
12
+
13
+ Abre una app en un navegador real (Chromium headless con Playwright), captura
14
+ lo que se ve y un modelo decide si la pagina cumple su objetivo o esta rota.
15
+
16
+ Requiere el secret `GROQ_KEY`.
app.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import json
4
+ import base64
5
+ import tempfile
6
+ from concurrent.futures import ThreadPoolExecutor
7
+
8
+ import gradio as gr
9
+ from openai import OpenAI
10
+ from playwright.sync_api import sync_playwright
11
+
12
+ GROQ_KEY = os.environ.get("GROQ_KEY", "")
13
+
14
+ # Modelo de vision de Groq (si cambia el nombre, ajustalo aqui).
15
+ VISION_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct"
16
+ # Respaldo solo-texto si la vision falla.
17
+ TEXT_MODEL = "llama-3.3-70b-versatile"
18
+
19
+ _groq = OpenAI(api_key=GROQ_KEY, base_url="https://api.groq.com/openai/v1") if GROQ_KEY else None
20
+
21
+ # Playwright (API sync) debe correr en un hilo SIN loop asyncio. Usamos un
22
+ # executor propio para garantizarlo.
23
+ _executor = ThreadPoolExecutor(max_workers=1)
24
+
25
+
26
+ def _abrir_y_capturar(url):
27
+ """Abre la URL en Chromium headless. Devuelve (texto_visible, ruta_captura)."""
28
+ with sync_playwright() as p:
29
+ navegador = p.chromium.launch(args=["--no-sandbox", "--disable-dev-shm-usage"])
30
+ pagina = navegador.new_page(viewport={"width": 1280, "height": 1600})
31
+ pagina.goto(url, wait_until="networkidle", timeout=45000)
32
+ pagina.wait_for_timeout(2500) # dar tiempo a apps JS (Gradio tarda en pintar)
33
+ texto = pagina.inner_text("body")[:6000]
34
+ ruta = os.path.join(tempfile.gettempdir(), "captura.png")
35
+ pagina.screenshot(path=ruta, full_page=True)
36
+ navegador.close()
37
+ return texto, ruta
38
+
39
+
40
+ def _abrir_seguro(url):
41
+ return _executor.submit(_abrir_y_capturar, url).result()
42
+
43
+
44
+ def _img_b64(ruta):
45
+ with open(ruta, "rb") as f:
46
+ return base64.b64encode(f.read()).decode("utf-8")
47
+
48
+
49
+ SYSTEM = (
50
+ "Eres un tester de control de calidad de webs y apps. Te dan el OBJETIVO de una "
51
+ "pagina y lo que se ve en ella (texto y, si hay, captura). Decide si cumple el "
52
+ "objetivo y si parece funcional. Marca ok=false si la pagina esta en blanco, "
53
+ "muestra errores, le faltan secciones del objetivo o se ve rota. "
54
+ 'Responde SOLO JSON: {"ok": true/false, "problemas": ["..."], "resumen": "..."}'
55
+ )
56
+
57
+
58
+ def _juzgar(objetivo, texto, ruta_img):
59
+ if _groq is None:
60
+ return '{"ok": false, "problemas": ["Falta GROQ_KEY en los secrets"], "resumen": "Sin clave"}'
61
+ base = f"OBJETIVO:\n{objetivo}\n\nTEXTO VISIBLE EN LA PAGINA:\n{texto}"
62
+ # 1) Intento con vision (incluye la captura)
63
+ try:
64
+ b64 = _img_b64(ruta_img)
65
+ resp = _groq.chat.completions.create(
66
+ model=VISION_MODEL,
67
+ messages=[
68
+ {"role": "system", "content": SYSTEM},
69
+ {"role": "user", "content": [
70
+ {"type": "text", "text": base},
71
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
72
+ ]},
73
+ ],
74
+ temperature=0,
75
+ )
76
+ return resp.choices[0].message.content
77
+ except Exception:
78
+ pass
79
+ # 2) Respaldo solo-texto
80
+ try:
81
+ resp = _groq.chat.completions.create(
82
+ model=TEXT_MODEL,
83
+ messages=[
84
+ {"role": "system", "content": SYSTEM},
85
+ {"role": "user", "content": base},
86
+ ],
87
+ temperature=0,
88
+ )
89
+ return resp.choices[0].message.content
90
+ except Exception as e:
91
+ return '{"ok": false, "problemas": ["Error del modelo: ' + str(e)[:120] + '"], "resumen": "Fallo al juzgar"}'
92
+
93
+
94
+ def probar(url, objetivo):
95
+ url = (url or "").strip()
96
+ if not url:
97
+ return "Pon una URL.", None
98
+ if not url.startswith("http"):
99
+ url = "https://" + url
100
+ try:
101
+ texto, ruta = _abrir_seguro(url)
102
+ except Exception as e:
103
+ return f"No se pudo abrir la pagina: {e}", None
104
+ veredicto = _juzgar(objetivo or "La pagina debe cargar sin errores y verse completa.", texto, ruta)
105
+ salida = veredicto
106
+ try:
107
+ limpio = veredicto.replace("```json", "").replace("```", "").strip()
108
+ d = json.loads(limpio)
109
+ estado = "PASA" if d.get("ok") else "NO PASA"
110
+ problemas = "\n".join(f"- {x}" for x in d.get("problemas", []))
111
+ salida = f"### {estado}\n\n{d.get('resumen','')}\n\n{problemas}".strip()
112
+ except Exception:
113
+ pass
114
+ return salida, ruta
115
+
116
+
117
+ with gr.Blocks(title="Forja Tester") as demo:
118
+ gr.Markdown("# Forja Tester\nAbre una app en un navegador real, la mira y dice si funciona.")
119
+ url_in = gr.Textbox(label="URL de la app a probar")
120
+ obj_in = gr.Textbox(label="Que deberia hacer / contener", lines=3)
121
+ btn = gr.Button("Probar", variant="primary")
122
+ out = gr.Markdown()
123
+ img = gr.Image(label="Lo que vio el navegador")
124
+ btn.click(probar, [url_in, obj_in], [out, img])
125
+
126
+ demo.launch(server_name="0.0.0.0", server_port=7860)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio
2
+ openai
3
+ playwright
4
+ requests