Update modules/langchain_assistant/cvision_forge_v9.py
Browse files
modules/langchain_assistant/cvision_forge_v9.py
CHANGED
|
@@ -22,59 +22,18 @@ SPACE_TRELLIS = "trellis-community/TRELLIS"
|
|
| 22 |
|
| 23 |
# ═════════════════════════════════════════════════════════════════════
|
| 24 |
# SECCIÓN 2: NÚCLEO LOGÍSTICO Y PROCESAMIENTO MULTIMODAL (TRELLIS BACKEND)
|
| 25 |
-
# 💡 Motor de inferencia
|
| 26 |
-
#
|
| 27 |
-
#
|
| 28 |
-
#
|
| 29 |
-
# Cuando el Space tiene gr.State(False) como input, el servidor serializa
|
| 30 |
-
# ese parámetro como el valor booleano False en la lista de parameters_info.
|
| 31 |
-
# La línea 1327 del client.py hace: "parameter_name" in False
|
| 32 |
-
# → TypeError: argument of type 'bool' is not iterable
|
| 33 |
-
#
|
| 34 |
-
# FIX: patching construct_args para filtrar entradas no-dict antes de iterar.
|
| 35 |
-
# Esto es seguro: los parámetros gr.State son inyectados automáticamente por
|
| 36 |
-
# el servidor y no deben ser enviados por el cliente de todas formas.
|
| 37 |
# ═════════════════════════════════════════════════════════════════════
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
try:
|
| 45 |
-
import gradio_client.utils as gc_utils
|
| 46 |
-
from enum import Enum
|
| 47 |
-
|
| 48 |
-
# Guardia de idempotencia para evitar re-parcheos cíclicos en caliente
|
| 49 |
-
if getattr(gc_utils, '_cvision_patch_applied', False):
|
| 50 |
-
print("[CVision Patch] Memoria de control detectada: El parche ya está activo.")
|
| 51 |
-
return True
|
| 52 |
-
|
| 53 |
-
_original_construct_args = gc_utils.construct_args
|
| 54 |
-
|
| 55 |
-
def _construct_args_patched(parameters_info, args, kwargs):
|
| 56 |
-
"""
|
| 57 |
-
Versión parcheada de construct_args que filtra entradas no-dict
|
| 58 |
-
en parameters_info antes de procesarlas de forma interna.
|
| 59 |
-
"""
|
| 60 |
-
if parameters_info is not None:
|
| 61 |
-
# Sanitización en tiempo de ejecución de las matrices booleanas inyectadas
|
| 62 |
-
parameters_info = [
|
| 63 |
-
p for p in parameters_info if isinstance(p, dict)
|
| 64 |
-
]
|
| 65 |
-
return _original_construct_args(parameters_info, args, kwargs)
|
| 66 |
-
|
| 67 |
-
gc_utils.construct_args = _construct_args_patched
|
| 68 |
-
gc_utils._cvision_patch_applied = True
|
| 69 |
-
print("[CVision Patch] ✅ construct_args parcheado correctamente en el núcleo Gradio.")
|
| 70 |
-
return True
|
| 71 |
-
except Exception as e:
|
| 72 |
-
print(f"[CVision Patch] ⚠️ No se pudo inyectar el parche en las librerías del sistema: {e}")
|
| 73 |
-
return False
|
| 74 |
-
|
| 75 |
-
# Firma de parámetros verificada y mapeada con precisión milimétrica del endpoint /generate_and_extract_glb
|
| 76 |
PARAMS_INFERENCIA_TRELLIS = {
|
| 77 |
-
"multiimages": [], # Lista vacía obligatoria para activar el modo single-image
|
| 78 |
"seed": 0, # Semilla determinista para consistencia estructural
|
| 79 |
"ss_guidance_strength": 7.5, # Fuerza de la guía para la estructura dispersa
|
| 80 |
"ss_sampling_steps": 12, # Pasos de muestreo en la etapa de dispersión
|
|
@@ -87,84 +46,126 @@ PARAMS_INFERENCIA_TRELLIS = {
|
|
| 87 |
|
| 88 |
def ejecutar_forja_3d(imagen_generada_flux: str) -> str:
|
| 89 |
"""
|
| 90 |
-
Toma
|
| 91 |
-
|
| 92 |
-
TRELLIS mediante WebSockets estables.
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
"""
|
| 97 |
if not imagen_generada_flux or not str(imagen_generada_flux).strip():
|
| 98 |
print("[TRELLIS Error] No se suministró una ruta de imagen válida.")
|
| 99 |
return "ERROR: No se detectó ninguna imagen válida para iniciar el esculpido 3D."
|
| 100 |
-
|
| 101 |
-
#
|
| 102 |
-
|
|
|
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
hf_token=HF_TOKEN if HF_TOKEN else None,
|
| 117 |
-
)
|
| 118 |
|
| 119 |
-
|
|
|
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
if not ruta_glb and isinstance(resultado, str) and resultado.endswith(".glb"):
|
| 146 |
-
ruta_glb = resultado
|
| 147 |
-
|
| 148 |
-
if ruta_glb:
|
| 149 |
-
print(f"[TRELLIS Engine] ¡Malla .glb consolidada con éxito!: {ruta_glb}")
|
| 150 |
-
return ruta_glb
|
| 151 |
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
except Exception as error:
|
| 155 |
-
error_str = str(error)
|
| 156 |
-
print(f"[TRELLIS Error] Incidencia en intento {intento_actual}: {error_str}")
|
| 157 |
|
| 158 |
-
#
|
| 159 |
-
if
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
| 168 |
|
| 169 |
|
| 170 |
# ═════════════════════════════════════════════════════════════════════
|
|
|
|
| 22 |
|
| 23 |
# ═════════════════════════════════════════════════════════════════════
|
| 24 |
# SECCIÓN 2: NÚCLEO LOGÍSTICO Y PROCESAMIENTO MULTIMODAL (TRELLIS BACKEND)
|
| 25 |
+
# 💡 Motor de inferencia ultra-robusto mediante HTTP Nativo y Server-Sent Events (SSE).
|
| 26 |
+
# BYPASS DE SEGURIDAD: Se elimina por completo la dependencia de gradio_client.
|
| 27 |
+
# Evita de forma definitiva los errores de tipo 'bool is not iterable' y
|
| 28 |
+
# los cuellos de botella síncronos en los hilos visuales de la interfaz.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
# ═════════════════════════════════════════════════════════════════════
|
| 30 |
+
import os
|
| 31 |
+
import time
|
| 32 |
+
import json
|
| 33 |
+
import requests
|
| 34 |
+
|
| 35 |
+
# Firma de parámetros verificada con precisión milimétrica por ingeniería inversa
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
PARAMS_INFERENCIA_TRELLIS = {
|
|
|
|
| 37 |
"seed": 0, # Semilla determinista para consistencia estructural
|
| 38 |
"ss_guidance_strength": 7.5, # Fuerza de la guía para la estructura dispersa
|
| 39 |
"ss_sampling_steps": 12, # Pasos de muestreo en la etapa de dispersión
|
|
|
|
| 46 |
|
| 47 |
def ejecutar_forja_3d(imagen_generada_flux: str) -> str:
|
| 48 |
"""
|
| 49 |
+
Toma una imagen 2D local, la convierte a un formato aceptado por el clúster,
|
| 50 |
+
y ejecuta un handshake HTTP puro contra la pasarela SSE de trellis-community.
|
|
|
|
| 51 |
|
| 52 |
+
Bypass definitivo: Consume la sub-ruta real /gradio_api/call/ sin pasar
|
| 53 |
+
por los hilos visuales del frontend, eliminando bloqueos de pestañas.
|
| 54 |
"""
|
| 55 |
if not imagen_generada_flux or not str(imagen_generada_flux).strip():
|
| 56 |
print("[TRELLIS Error] No se suministró una ruta de imagen válida.")
|
| 57 |
return "ERROR: No se detectó ninguna imagen válida para iniciar el esculpido 3D."
|
| 58 |
+
|
| 59 |
+
# Configuración de URLs dinámicas basadas en la pasarela real descubierta
|
| 60 |
+
base_space_url = "https://trellis-community-trellis.hf.space"
|
| 61 |
+
endpoint_inicializar = f"{base_space_url}/gradio_api/call/generate_and_extract_glb"
|
| 62 |
|
| 63 |
+
cabeceras = {
|
| 64 |
+
"Content-Type": "application/json",
|
| 65 |
+
"Accept": "application/json"
|
| 66 |
+
}
|
| 67 |
+
if HF_TOKEN:
|
| 68 |
+
cabeceras["Authorization"] = f"Bearer {HF_TOKEN}"
|
| 69 |
+
|
| 70 |
+
print(f"[TRELLIS Engine] Estableciendo bypass de red directo hacia: {endpoint_inicializar}")
|
| 71 |
|
| 72 |
+
try:
|
| 73 |
+
# Paso 1: Intentar subir el archivo local a la caché temporal de Hugging Face
|
| 74 |
+
print(f"[TRELLIS Engine] Preparando carga del asset binario local: {imagen_generada_flux}")
|
| 75 |
+
endpoint_upload = f"{base_space_url}/upload"
|
| 76 |
+
|
| 77 |
+
with open(imagen_generada_flux, "rb") as f_archivo:
|
| 78 |
+
ficheros = {"files": (os.path.basename(imagen_generada_flux), f_archivo, "image/png")}
|
| 79 |
+
cabeceras_upload = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
|
| 80 |
+
res_upload = requests.post(endpoint_upload, headers=cabeceras_upload, files=ficheros, timeout=60)
|
|
|
|
|
|
|
| 81 |
|
| 82 |
+
if res_upload.status_code != 200:
|
| 83 |
+
raise ConnectionError(f"Falla al depositar imagen en el servidor remoto. Código HTTP: {res_upload.status_code}")
|
| 84 |
|
| 85 |
+
url_remota_temporal = res_upload.json()[0]
|
| 86 |
+
print(f"[TRELLIS Engine] Asset cargado con éxito en el búfer remoto: {url_remota_temporal}")
|
| 87 |
+
|
| 88 |
+
# Paso 2: Estructurar el Payload idéntico al capturado en el radar del navegador
|
| 89 |
+
# Mapeamos la lista de argumentos exactos que espera recibir la firma del backend de TRELLIS
|
| 90 |
+
lista_datos = [
|
| 91 |
+
{"path": url_remota_temporal}, # Imagen pre-cargada en la caché
|
| 92 |
+
PARAMS_INFERENCIA_TRELLIS["seed"],
|
| 93 |
+
PARAMS_INFERENCIA_TRELLIS["ss_guidance_strength"],
|
| 94 |
+
PARAMS_INFERENCIA_TRELLIS["ss_sampling_steps"],
|
| 95 |
+
PARAMS_INFERENCIA_TRELLIS["slat_guidance_strength"],
|
| 96 |
+
PARAMS_INFERENCIA_TRELLIS["slat_sampling_steps"],
|
| 97 |
+
PARAMS_INFERENCIA_TRELLIS["multiimage_algo"],
|
| 98 |
+
PARAMS_INFERENCIA_TRELLIS["mesh_simplify"],
|
| 99 |
+
PARAMS_INFERENCIA_TRELLIS["texture_size"]
|
| 100 |
+
]
|
| 101 |
+
|
| 102 |
+
payload = {"data": lista_datos}
|
| 103 |
+
|
| 104 |
+
# Paso 3: Lanzar Handshake Inicial para obtener el Event ID del stream SSE
|
| 105 |
+
respuesta_inicial = requests.post(endpoint_inicializar, headers=cabeceras, json=payload, timeout=45)
|
| 106 |
+
|
| 107 |
+
if respuesta_inicial.status_code != 200:
|
| 108 |
+
raise ConnectionError(f"La pasarela denegó el acceso estructural. HTTP: {respuesta_inicial.status_code}")
|
| 109 |
|
| 110 |
+
datos_handshake = respuesta_inicial.json()
|
| 111 |
+
event_id = datos_handshake.get("event_id")
|
| 112 |
+
|
| 113 |
+
if not event_id:
|
| 114 |
+
raise ValueError(f"El clúster no asignó un identificador de flujo. Respuesta: {datos_handshake}")
|
| 115 |
|
| 116 |
+
print(f"[TRELLIS Engine] Handshake Exitoso. Event ID de Canal Asignado: {event_id}")
|
| 117 |
+
|
| 118 |
+
# Paso 4: Escuchar el Stream en vivo (SSE) para capturar el momento exacto en que termina la forja
|
| 119 |
+
endpoint_stream = f"{endpoint_inicializar}/{event_id}"
|
| 120 |
+
print(f"[TRELLIS Engine] Abriendo escucha del búfer de eventos en sub-ruta: {endpoint_stream}")
|
| 121 |
+
|
| 122 |
+
# Conectamos con stream=True para procesar los paquetes en caliente sin agotar la RAM
|
| 123 |
+
respuesta_stream = requests.get(endpoint_stream, headers=cabeceras, stream=True, timeout=300)
|
| 124 |
+
|
| 125 |
+
ruta_glb_final = None
|
| 126 |
+
|
| 127 |
+
for linea in respuesta_stream.iter_lines():
|
| 128 |
+
if not linea:
|
| 129 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
+
linea_decodificada = linea.decode("utf-8").strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
# Buscamos las líneas de datos transmitidas por el protocolo SSE
|
| 134 |
+
if linea_decodificada.startswith("data:"):
|
| 135 |
+
contenido_json_raw = linea_decodificada[5:].strip()
|
| 136 |
+
|
| 137 |
+
try:
|
| 138 |
+
bloque_datos = json.loads(contenido_json_raw)
|
| 139 |
+
|
| 140 |
+
# Gradio transmite eventos intermedios (ej. 'process_generating'). Los ignoramos hasta ver el final.
|
| 141 |
+
if isinstance(bloque_datos, list) and len(bloque_datos) > 0:
|
| 142 |
+
# Buscamos en la colección de salida el nodo que contenga el archivo estructurado .glb
|
| 143 |
+
for nodo in bloque_datos:
|
| 144 |
+
if isinstance(nodo, dict) and "name" in nodo and str(nodo["name"]).endswith(".glb"):
|
| 145 |
+
ruta_glb_final = nodo["name"]
|
| 146 |
+
break
|
| 147 |
+
if isinstance(nodo, str) and nodo.endswith(".glb"):
|
| 148 |
+
ruta_glb_final = nodo
|
| 149 |
+
break
|
| 150 |
+
if ruta_glb_final:
|
| 151 |
+
break
|
| 152 |
+
except json.JSONDecodeError:
|
| 153 |
+
# Líneas de control o texto plano del stream. Se ignoran con seguridad.
|
| 154 |
continue
|
| 155 |
+
|
| 156 |
+
if ruta_glb_final:
|
| 157 |
+
# Si el retorno es un path relativo del servidor, construimos la descarga estática absoluta
|
| 158 |
+
if not ruta_glb_final.startswith("http"):
|
| 159 |
+
ruta_glb_final = f"{base_space_url}/file={ruta_glb_final}"
|
| 160 |
+
|
| 161 |
+
print(f"[TRELLIS Engine] ¡Malla .glb localizada en el clúster exitosamente!: {ruta_glb_final}")
|
| 162 |
+
return ruta_glb_final
|
| 163 |
|
| 164 |
+
raise TimeoutError("El flujo SSE de Hugging Face se cerró sin transmitir la geometría final.")
|
| 165 |
+
|
| 166 |
+
except Exception as error:
|
| 167 |
+
print(f"[TRELLIS Error] Falla crítica en bypass de red por HTTP: {error}")
|
| 168 |
+
return f"ERROR_PIPELINE: {str(error)}"
|
| 169 |
|
| 170 |
|
| 171 |
# ═════════════════════════════════════════════════════════════════════
|