Spaces:
Paused
Paused
Update main.py
Browse files
main.py
CHANGED
|
@@ -130,11 +130,13 @@ CHAT_ROLES = {
|
|
| 130 |
"Habla de forma fluida y creativa. Si el usuario ya compartió algo antes, úsalo como base."
|
| 131 |
),
|
| 132 |
"image_agent": (
|
| 133 |
-
"Eres ImageAgent,
|
| 134 |
-
"
|
| 135 |
-
"
|
| 136 |
-
'{"action":"generate_image","queries":["english
|
| 137 |
-
"
|
|
|
|
|
|
|
| 138 |
),
|
| 139 |
}
|
| 140 |
|
|
@@ -314,15 +316,37 @@ async def fetch_pexels(q):
|
|
| 314 |
except: pass
|
| 315 |
return None
|
| 316 |
|
| 317 |
-
async def gen_hf_image(
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
return None
|
| 327 |
|
| 328 |
CREATIVE_KEYWORDS = [
|
|
@@ -338,9 +362,11 @@ CREATIVE_KEYWORDS = [
|
|
| 338 |
def is_creative(q: str) -> bool:
|
| 339 |
return any(k in q.lower() for k in CREATIVE_KEYWORDS)
|
| 340 |
|
| 341 |
-
async def get_image(q: str):
|
| 342 |
-
|
| 343 |
-
|
|
|
|
|
|
|
| 344 |
result = await gen_hf_image(q)
|
| 345 |
if result: return result
|
| 346 |
# Real-world → Pexels first, AI fallback
|
|
@@ -693,19 +719,20 @@ async def chat_with_agent(request: Request):
|
|
| 693 |
parsed = _json2.loads(response.strip())
|
| 694 |
if isinstance(parsed, dict) and parsed.get("action") == "generate_image":
|
| 695 |
queries = parsed.get("queries", [message])[:3]
|
| 696 |
-
_safe = re.sub(r'[^\w]', '_', message[:
|
| 697 |
-
_ts = datetime.now().strftime('%
|
| 698 |
-
|
|
|
|
| 699 |
for i, img in enumerate(imgs):
|
| 700 |
if img:
|
| 701 |
-
fname = f"{
|
| 702 |
(DOCS_DIR / fname).write_bytes(img)
|
| 703 |
img_files.append(fname)
|
| 704 |
if img_files:
|
| 705 |
-
response = f"
|
| 706 |
-
img_result = {"img_base":
|
| 707 |
else:
|
| 708 |
-
response = "
|
| 709 |
except (ValueError, TypeError, KeyError):
|
| 710 |
pass # not JSON — normal text response
|
| 711 |
|
|
|
|
| 130 |
"Habla de forma fluida y creativa. Si el usuario ya compartió algo antes, úsalo como base."
|
| 131 |
),
|
| 132 |
"image_agent": (
|
| 133 |
+
"Eres ImageAgent, especialista en imágenes y arte visual con IA. "
|
| 134 |
+
"REGLA CRÍTICA: Si el usuario pide generar, crear, buscar, dibujar o mostrar UNA imagen o foto de CUALQUIER cosa, "
|
| 135 |
+
"responde ÚNICAMENTE con este JSON sin ningún texto adicional: "
|
| 136 |
+
'{"action":"generate_image","queries":["detailed english prompt 1","detailed english prompt 2","english prompt 3"]} '
|
| 137 |
+
"Los queries deben ser DESCRIPTIVOS en inglés (colores, estilo, composición). Ej: "
|
| 138 |
+
'{"action":"generate_image","queries":["flying cat with angel wings pastel colors","cute cat flying through clouds digital art","cat with wings fantasy illustration"]} '
|
| 139 |
+
"Para preguntas sobre fotografía, diseño o arte SIN pedir imágenes, conversa normalmente."
|
| 140 |
),
|
| 141 |
}
|
| 142 |
|
|
|
|
| 316 |
except: pass
|
| 317 |
return None
|
| 318 |
|
| 319 |
+
async def gen_hf_image(prompt: str) -> bytes | None:
|
| 320 |
+
"""Try HuggingFace free image generation models. FLUX first (best quality)."""
|
| 321 |
+
if not HF_API_KEY:
|
| 322 |
+
return None
|
| 323 |
+
# Ordered by quality/availability — FLUX.1 schnell is fastest free model
|
| 324 |
+
models = [
|
| 325 |
+
("black-forest-labs/FLUX.1-schnell", {"inputs": prompt}),
|
| 326 |
+
("black-forest-labs/FLUX.1-dev", {"inputs": prompt}),
|
| 327 |
+
("stabilityai/stable-diffusion-xl-base-1.0", {"inputs": prompt, "parameters": {"width": 512, "height": 512}}),
|
| 328 |
+
("stabilityai/stable-diffusion-2-1", {"inputs": prompt, "parameters": {"width": 512, "height": 512}}),
|
| 329 |
+
]
|
| 330 |
+
headers = {"Authorization": f"Bearer {HF_API_KEY}"}
|
| 331 |
+
for model_id, payload in models:
|
| 332 |
+
try:
|
| 333 |
+
async with httpx.AsyncClient(timeout=120) as c:
|
| 334 |
+
r = await c.post(
|
| 335 |
+
f"https://api-inference.huggingface.co/models/{model_id}",
|
| 336 |
+
headers=headers,
|
| 337 |
+
json=payload,
|
| 338 |
+
)
|
| 339 |
+
ct = r.headers.get("content-type", "")
|
| 340 |
+
if r.status_code == 200 and ("image" in ct or r.content[:4] in (b"\xff\xd8\xff\xe0", b"\x89PNG")):
|
| 341 |
+
return r.content
|
| 342 |
+
# 503/loading → try next model immediately
|
| 343 |
+
if r.status_code in (503, 500):
|
| 344 |
+
continue
|
| 345 |
+
# 429 rate limit → stop trying HF
|
| 346 |
+
if r.status_code == 429:
|
| 347 |
+
break
|
| 348 |
+
except Exception:
|
| 349 |
+
continue
|
| 350 |
return None
|
| 351 |
|
| 352 |
CREATIVE_KEYWORDS = [
|
|
|
|
| 362 |
def is_creative(q: str) -> bool:
|
| 363 |
return any(k in q.lower() for k in CREATIVE_KEYWORDS)
|
| 364 |
|
| 365 |
+
async def get_image(q: str, force_generate: bool = False):
|
| 366 |
+
"""Fetch or generate an image for query q.
|
| 367 |
+
force_generate=True: skip Pexels, go straight to HF image generation.
|
| 368 |
+
"""
|
| 369 |
+
if force_generate or is_creative(q):
|
| 370 |
result = await gen_hf_image(q)
|
| 371 |
if result: return result
|
| 372 |
# Real-world → Pexels first, AI fallback
|
|
|
|
| 719 |
parsed = _json2.loads(response.strip())
|
| 720 |
if isinstance(parsed, dict) and parsed.get("action") == "generate_image":
|
| 721 |
queries = parsed.get("queries", [message])[:3]
|
| 722 |
+
_safe = re.sub(r'[^\w]', '_', message[:28])
|
| 723 |
+
_ts = datetime.now().strftime('%H%M%S')
|
| 724 |
+
_base = f"chat_{_safe}_{_ts}"
|
| 725 |
+
imgs = await asyncio.gather(*[get_image(q, force_generate=True) for q in queries])
|
| 726 |
for i, img in enumerate(imgs):
|
| 727 |
if img:
|
| 728 |
+
fname = f"{_base}_img{i+1}.jpg"
|
| 729 |
(DOCS_DIR / fname).write_bytes(img)
|
| 730 |
img_files.append(fname)
|
| 731 |
if img_files:
|
| 732 |
+
response = f"✓ {len(img_files)} imagen(es) generada(s) — haz clic en las miniaturas para verlas"
|
| 733 |
+
img_result = {"img_base": _base, "img_count": len(img_files)}
|
| 734 |
else:
|
| 735 |
+
response = "No pude generar la imagen. Verifica HF_TOKEN y PEXELS_API_KEY en los Secrets del Space."
|
| 736 |
except (ValueError, TypeError, KeyError):
|
| 737 |
pass # not JSON — normal text response
|
| 738 |
|