feat: mejoras de navegación y visualización en sub-páginas

- sub-nav.css: barra de navegación compartida con botones estilo home
  (fondos por sección, texto negro, borde negro, hover neón, fix WebGL z-index)
- Móvil: panel de detalle ocupa 50% inferior, grafo permanece visible arriba
- Imágenes de nodo duplicadas en tamaño (160×104), detail-img a 28vh
- output_*.js: función showEgoGraph — filtra el grafo al ego-network del nodo
  seleccionado; botón "Ver solo conexiones" (solo si hay relaciones);
  botón flotante "← Volver al grafo completo"
- int-sec.js: eliminado makeTextSprite, igualado al resto (nulos para no-imagen)
- Eliminado footer de los 5 sub-HTML
- image_analyzer.py: cuantización int4 (NF4) para Qwen3-VL-8B → 6.4 GB VRAM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
CAPITANSITO 2026-04-28 20:23:22 +02:00
parent 778db90d78
commit 9b67e2915b
15 changed files with 2070 additions and 1083 deletions

View file

@ -24,7 +24,7 @@ from pathlib import Path
import torch
from PIL import Image
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor, BitsAndBytesConfig
# ── Configuración ──────────────────────────────────────────────────────────────
@ -33,10 +33,9 @@ CACHE_DIR = os.getenv("HF_HOME", "/var/www/theflows.net/flujos/FLUJOS_DATOS/IMAG
SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}
# RAM por imagen en batch (aprox): ~500MB activaciones encoder
# Modelo base bfloat16: ~16GB
# Batch de 4: ~18GB total → seguro con 64GB
DEFAULT_BATCH_SIZE = 4
# int4 via bitsandbytes: modelo ocupa ~4-5GB VRAM en lugar de ~16GB bfloat16
# RTX 3060 12GB → sobra VRAM para activaciones
DEFAULT_BATCH_SIZE = 1 # batch 1 para seguridad con 12GB
KEYWORD_PROMPT = """Analiza esta imagen en detalle.
Devuelve ÚNICAMENTE un objeto JSON válido con esta estructura exacta, sin texto adicional:
@ -73,17 +72,35 @@ class ImageAnalyzer:
print(f"[ImageAnalyzer] Cargando modelo {self.model_id}...")
print(f"[ImageAnalyzer] Cache: {CACHE_DIR}")
self._model = Qwen3VLForConditionalGeneration.from_pretrained(
self.model_id,
torch_dtype=torch.bfloat16,
device_map="cpu",
cache_dir=CACHE_DIR,
)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"[ImageAnalyzer] Dispositivo: {device}")
if device == "cuda":
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
)
self._model = Qwen3VLForConditionalGeneration.from_pretrained(
self.model_id,
quantization_config=bnb_config,
device_map="auto",
cache_dir=CACHE_DIR,
)
else:
self._model = Qwen3VLForConditionalGeneration.from_pretrained(
self.model_id,
torch_dtype=torch.bfloat16,
device_map="cpu",
cache_dir=CACHE_DIR,
)
self._processor = AutoProcessor.from_pretrained(
self.model_id,
cache_dir=CACHE_DIR,
)
print("[ImageAnalyzer] Modelo cargado.")
print("[ImageAnalyzer] Modelo cargado (int4 cuantizado).")
# ── Opción 3: Resume — obtener archivos ya analizados en MongoDB ───────────