# ============================================================ # Laundry Space - Programa para enviar csv (UNIVERSAL) # # - UI: GET / -> página con selector de destino + botones # - Status: GET /status -> healthcheck + config # - Export: GET /export_csv -> devuelve CSV desde disco (o sample) # - Push: POST /send_csv -> envía CSV al destino seleccionado (/ingest_csv) # # Variables: # CSV_FILE_PATH # DEFAULT_MODE # LLM_INGEST_URL_OPENAI # LLM_INGEST_URL_GROQ # LLM_INGEST_URL (fallback) # INGEST_TOKEN (opcional, header X-INGEST-TOKEN) # ============================================================ import os from fastapi import FastAPI, HTTPException from fastapi.responses import Response, FileResponse, HTMLResponse from fastapi.middleware.cors import CORSMiddleware import uvicorn import httpx from pydantic import BaseModel app = FastAPI(title="Programa para enviar csv", version="3.0.3") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) # ------------------------------------------------------------ # CONFIG # ------------------------------------------------------------ CSV_FILE_PATH = (os.getenv("CSV_FILE_PATH", "/data/OUTPUT_MAP_COMPARISONS.csv") or "").strip() DEFAULT_MODE = (os.getenv("DEFAULT_MODE", "comparaciones") or "comparaciones").strip() LLM_INGEST_URL_OPENAI = (os.getenv("LLM_INGEST_URL_OPENAI", "") or "").strip() LLM_INGEST_URL_GROQ = (os.getenv("LLM_INGEST_URL_GROQ", "") or "").strip() LLM_INGEST_URL = (os.getenv("LLM_INGEST_URL", "") or "").strip() INGEST_TOKEN = (os.getenv("INGEST_TOKEN", "") or "").strip() def _sample_csv() -> str: return ( "PRODUCT_TEST,PRODUCT_BENCH,RESPONSE,MODALITY,SCENARIOS,WASH,PROCESS,MONITOR,INDEX,THRESHOLD\n" "P1,P0,01.SUPERIOR,STANDARD,S1,1,NORMAL,a.1.DSPCG,1.2.WI_STw,T1\n" "P2,P0,03.PARITY,BLUISH,S2,1,NORMAL,b.1.DSCCG,2.2.WI_GEw,T1\n" ) def _resolve_dest(dest_key: str, custom_url: str) -> str: dest_key = (dest_key or "").strip().lower() custom_url = (custom_url or "").strip() if dest_key == "openai": return LLM_INGEST_URL_OPENAI or LLM_INGEST_URL if dest_key == "groq": return LLM_INGEST_URL_GROQ or LLM_INGEST_URL if dest_key == "custom": return custom_url or LLM_INGEST_URL return LLM_INGEST_URL def _ui_from_ingest(ingest_url: str) -> str: """ Convierte: https://xxxx.hf.space/ingest_csv -> https://xxxx.hf.space/ """ u = (ingest_url or "").strip() if not u: return "" u = u.split("?", 1)[0].strip() while u.endswith("/"): u = u[:-1] if u.lower().endswith("/ingest_csv"): u = u[: -len("/ingest_csv")] if u and not u.endswith("/"): u = u + "/" if not (u.startswith("http://") or u.startswith("https://")): return "" return u # ------------------------------------------------------------ # UI # ------------------------------------------------------------ @app.get("/", response_class=HTMLResponse) def home(): sugg_openai = LLM_INGEST_URL_OPENAI or "https://.hf.space/ingest_csv" sugg_groq = LLM_INGEST_URL_GROQ or "https://.hf.space/ingest_csv" ui_openai = _ui_from_ingest(LLM_INGEST_URL_OPENAI) or "https://alexacido-llm-consumertec.hf.space/" ui_groq = _ui_from_ingest(LLM_INGEST_URL_GROQ) or "https://alexacido-consumetec-llm.hf.space/" html = """ Programa para enviar csv

Programa para enviar csv

Este servicio lee el CSV desde __CSV_FILE_PATH__ y lo envía a un Space destino usando POST /ingest_csv. (Ahora abre el Space destino inmediatamente al hacer click).
Modo: __DEFAULT_MODE__
Token en Laundry: __HAS_TOKEN__
// Resultado aparecerá aquí…
""" html = html.replace("__CSV_FILE_PATH__", CSV_FILE_PATH) html = html.replace("__DEFAULT_MODE__", DEFAULT_MODE) html = html.replace("__HAS_TOKEN__", "SI" if bool(INGEST_TOKEN) else "NO") html = html.replace("__SUGG_OPENAI__", sugg_openai) html = html.replace("__SUGG_GROQ__", sugg_groq) html = html.replace("__UI_OPENAI__", ui_openai) html = html.replace("__UI_GROQ__", ui_groq) return HTMLResponse(content=html) # ------------------------------------------------------------ # STATUS # ------------------------------------------------------------ @app.get("/status") def status(): return { "status": "ok", "csv_file_path": CSV_FILE_PATH, "exists": os.path.exists(CSV_FILE_PATH), "default_mode": DEFAULT_MODE, "ingest_token_in_laundry": bool(INGEST_TOKEN), "llm_ingest_url_default": bool(LLM_INGEST_URL), "llm_ingest_url_openai": bool(LLM_INGEST_URL_OPENAI), "llm_ingest_url_groq": bool(LLM_INGEST_URL_GROQ), } # ------------------------------------------------------------ # EXPORT CSV (pull) # ------------------------------------------------------------ @app.get("/export_csv") def export_csv(): if os.path.exists(CSV_FILE_PATH): return FileResponse( CSV_FILE_PATH, media_type="text/csv; charset=utf-8", filename=os.path.basename(CSV_FILE_PATH), ) return Response(content=_sample_csv(), media_type="text/csv; charset=utf-8") # ------------------------------------------------------------ # SEND CSV (push) # ------------------------------------------------------------ class SendRequest(BaseModel): dest_key: str = "openai" # openai | groq | custom custom_url: str = "" # se usa si dest_key=custom @app.post("/send_csv") async def send_csv(payload: SendRequest): dest_url = _resolve_dest(payload.dest_key, payload.custom_url) if not dest_url: raise HTTPException( status_code=400, detail="Destino no configurado. Configura LLM_INGEST_URL_* o usa Custom URL." ) if not os.path.exists(CSV_FILE_PATH): raise HTTPException(status_code=404, detail=f"No existe el archivo CSV en: {CSV_FILE_PATH}") try: with open(CSV_FILE_PATH, "r", encoding="utf-8") as f: csv_text = f.read() except Exception as e: raise HTTPException(status_code=500, detail=f"No se pudo leer CSV: {e}") out_payload = { "csv_text": csv_text, "filename": os.path.basename(CSV_FILE_PATH), "mode": DEFAULT_MODE } headers = {"Content-Type": "application/json"} if INGEST_TOKEN: headers["X-INGEST-TOKEN"] = INGEST_TOKEN timeout = httpx.Timeout(60.0, connect=20.0) try: async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: r = await client.post(dest_url, json=out_payload, headers=headers) except Exception as e: raise HTTPException(status_code=502, detail=f"Error enviando al destino: {e}") if r.status_code != 200: return { "status": "error", "message": "El destino respondió con error.", "destination": dest_url, "destination_status_code": r.status_code, "destination_body": (r.text or "")[:1500], } return { "status": "ok", "message": "CSV enviado correctamente al destino.", "destination": dest_url, "destination_status_code": r.status_code, "destination_body": (r.text or "")[:1500], "bytes_sent": len(csv_text.encode("utf-8")), } if __name__ == "__main__": port = int(os.environ.get("PORT", "7860")) uvicorn.run(app, host="0.0.0.0", port=port)