| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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=["*"], |
| | ) |
| |
|
| | |
| | |
| | |
| | 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 |
| |
|
| |
|
| | |
| | |
| | |
| | @app.get("/", response_class=HTMLResponse) |
| | def home(): |
| | sugg_openai = LLM_INGEST_URL_OPENAI or "https://<tu-openai-space>.hf.space/ingest_csv" |
| | sugg_groq = LLM_INGEST_URL_GROQ or "https://<tu-groq-space>.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 = """ |
| | <!doctype html> |
| | <html lang="es"> |
| | <head> |
| | <meta charset="utf-8"/> |
| | <meta name="viewport" content="width=device-width,initial-scale=1"/> |
| | <title>Programa para enviar csv</title> |
| | <style> |
| | body{font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:#0b1220;color:#e5e7eb;margin:0;padding:24px;} |
| | .card{max-width:980px;margin:0 auto;background:#111827;border:1px solid #243244;border-radius:18px;padding:18px 18px;} |
| | h1{margin:0 0 10px 0;font-size:1.35rem;} |
| | .muted{color:#9ca3af;font-size:.92rem;line-height:1.45;} |
| | .row{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px;align-items:center;} |
| | .grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px;} |
| | @media (max-width: 820px){ .grid2{grid-template-columns:1fr;} } |
| | button{cursor:pointer;border:0;border-radius:999px;padding:10px 16px;font-weight:800;background:linear-gradient(135deg,#22d3ee,#4ade80);color:#0b1220;} |
| | button.secondary{background:#0b1220;color:#e5e7eb;border:1px solid #243244;font-weight:700;} |
| | select,input{width:100%;padding:10px 12px;border-radius:12px;border:1px solid #243244;background:#0b1220;color:#e5e7eb;} |
| | label{display:block;color:#9ca3af;font-size:.85rem;margin-bottom:6px;} |
| | pre{white-space:pre-wrap;background:#0b1220;border:1px solid #243244;border-radius:12px;padding:12px;margin-top:12px;max-height:48vh;overflow:auto;} |
| | code{color:#93c5fd;} |
| | .pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:1px solid #243244;border-radius:999px;background:#0b1220;color:#9ca3af;font-size:.8rem;} |
| | </style> |
| | </head> |
| | <body> |
| | <div class="card"> |
| | <h1>Programa para enviar csv</h1> |
| | <div class="muted"> |
| | Este servicio lee el CSV desde <code>__CSV_FILE_PATH__</code> y lo envía a un Space destino |
| | usando <code>POST /ingest_csv</code>. (Ahora abre el Space destino inmediatamente al hacer click). |
| | </div> |
| | <div class="row"> |
| | <div class="pill">Modo: <code>__DEFAULT_MODE__</code></div> |
| | <div class="pill">Token en Laundry: <code>__HAS_TOKEN__</code></div> |
| | </div> |
| | <div class="grid2"> |
| | <div> |
| | <label>Destino</label> |
| | <select id="destKey"> |
| | <option value="openai">OpenAI (llm-consumertec)</option> |
| | <option value="groq">Groq (consumetec-llm)</option> |
| | <option value="custom">Custom (pegar URL)</option> |
| | </select> |
| | </div> |
| | <div> |
| | <label>Custom URL (si eliges "Custom")</label> |
| | <input id="customUrl" placeholder="https://<space>.hf.space/ingest_csv" /> |
| | </div> |
| | </div> |
| | <div class="grid2"> |
| | <div> |
| | <label>Sugerencia OpenAI</label> |
| | <input value="__SUGG_OPENAI__" readonly /> |
| | </div> |
| | <div> |
| | <label>Sugerencia Groq</label> |
| | <input value="__SUGG_GROQ__" readonly /> |
| | </div> |
| | </div> |
| | <div class="row"> |
| | <button id="sendBtn">Enviar CSV (Destino seleccionado) + Abrir Space</button> |
| | <button class="secondary" id="sendOpenAI">Enviar a OpenAI + Abrir</button> |
| | <button class="secondary" id="sendGroq">Enviar a Groq + Abrir</button> |
| | <button class="secondary" id="statusBtn">Ver Status</button> |
| | </div> |
| | <pre id="out">// Resultado aparecerá aquí…</pre> |
| | </div> |
| | <script> |
| | const out = document.getElementById("out"); |
| | |
| | const UI_OPENAI = "__UI_OPENAI__"; |
| | const UI_GROQ = "__UI_GROQ__"; |
| | |
| | function uiFromIngest(url){ |
| | if(!url) return ""; |
| | let u = String(url).trim(); |
| | if(!/^https?:\\/\\//i.test(u)) return ""; |
| | u = u.split("?",1)[0].trim(); |
| | while(u.endsWith("/")) u = u.slice(0,-1); |
| | if(u.toLowerCase().endsWith("/ingest_csv")) u = u.slice(0, -("/ingest_csv".length)); |
| | if(u && !u.endsWith("/")) u += "/"; |
| | return u; |
| | } |
| | |
| | async function callSend(destKey){ |
| | const customUrl = (document.getElementById("customUrl").value || "").trim(); |
| | |
| | let targetUI = ""; |
| | if(destKey === "openai") targetUI = UI_OPENAI; |
| | else if(destKey === "groq") targetUI = UI_GROQ; |
| | else if(destKey === "custom") targetUI = uiFromIngest(customUrl); |
| | |
| | // ✅ ABRIR DIRECTO EL SPACE (evita pestaña blanca) |
| | if(targetUI){ |
| | try{ |
| | window.open(targetUI, "_blank"); |
| | }catch(e){ |
| | // si el navegador bloquea, no rompemos el flujo |
| | } |
| | } |
| | |
| | out.textContent = "Enviando CSV…"; |
| | |
| | try{ |
| | const r = await fetch("/send_csv", { |
| | method:"POST", |
| | headers: {"Content-Type":"application/json"}, |
| | body: JSON.stringify({dest_key: destKey, custom_url: customUrl}) |
| | }); |
| | const t = await r.text(); |
| | out.textContent = t; |
| | }catch(e){ |
| | out.textContent = "ERROR llamando /send_csv: " + e; |
| | } |
| | } |
| | |
| | document.getElementById("sendBtn").onclick = async () => { |
| | const destKey = document.getElementById("destKey").value; |
| | await callSend(destKey); |
| | }; |
| | document.getElementById("sendOpenAI").onclick = async () => { |
| | await callSend("openai"); |
| | }; |
| | document.getElementById("sendGroq").onclick = async () => { |
| | await callSend("groq"); |
| | }; |
| | document.getElementById("statusBtn").onclick = async () => { |
| | out.textContent = "Consultando /status…"; |
| | try{ |
| | const r = await fetch("/status"); |
| | const t = await r.text(); |
| | out.textContent = t; |
| | }catch(e){ |
| | out.textContent = "ERROR llamando /status: " + e; |
| | } |
| | }; |
| | </script> |
| | </body> |
| | </html> |
| | """ |
| | 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) |
| |
|
| |
|
| | |
| | |
| | |
| | @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), |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | @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") |
| |
|
| |
|
| | |
| | |
| | |
| | class SendRequest(BaseModel): |
| | dest_key: str = "openai" |
| | custom_url: str = "" |
| |
|
| |
|
| | @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) |
| |
|