Spaces:
Running
Running
Tu Nombre commited on
Commit ·
5d03516
1
Parent(s): c2b1784
fase 1: campo libre + groq parsea config
Browse files
main.py
CHANGED
|
@@ -501,6 +501,8 @@ input:focus,select:focus{border-color:#ff2d2d;}
|
|
| 501 |
<option value="largo">📺 Video Largo</option>
|
| 502 |
<option value="short">📱 Short Vertical</option>
|
| 503 |
</select>
|
|
|
|
|
|
|
| 504 |
`;
|
| 505 |
container.appendChild(div);
|
| 506 |
}
|
|
@@ -542,7 +544,8 @@ async function activar(){
|
|
| 542 |
for(let i=1; i<=numCanales; i++){
|
| 543 |
const n = document.getElementById('nicho'+i);
|
| 544 |
const t = document.getElementById('tipo'+i);
|
| 545 |
-
|
|
|
|
| 546 |
}
|
| 547 |
document.getElementById('btn').disabled = true;
|
| 548 |
document.getElementById('loading').style.display = 'block';
|
|
@@ -594,12 +597,57 @@ async def activar_canal(token: str = "", plan: str = ""):
|
|
| 594 |
return ACTIVAR_HTML.replace("PLAN_PLACEHOLDER", plan_token).replace("TOKEN_PLACEHOLDER", token).replace("CANALES_PLACEHOLDER", str(PLANES_CANALES.get(plan_token, 1)))
|
| 595 |
return HTMLResponse("<html><body style='background:#080808;color:#fff;text-align:center;padding:80px'><h1 style='color:#ff2d2d'>Acceso no autorizado</h1><a href='/' style='color:#ff2d2d'>Volver</a></body></html>", status_code=403)
|
| 596 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
@app.post("/crear")
|
| 598 |
async def crear(request: Request):
|
| 599 |
data = await request.json()
|
| 600 |
email = data.get("email","").strip().lower()
|
| 601 |
-
|
| 602 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
token = data.get("token","")
|
| 604 |
# Verificar token o sesion existente
|
| 605 |
plan = "basico"
|
|
@@ -620,13 +668,15 @@ async def crear(request: Request):
|
|
| 620 |
return JSONResponse({"error": "No se pudo crear el canal"}, status_code=500)
|
| 621 |
u = resultado["url"]
|
| 622 |
repo = resultado["repo"]
|
|
|
|
|
|
|
| 623 |
# Guardar en CLIENTES
|
| 624 |
if email not in CLIENTES:
|
| 625 |
CLIENTES[email] = {"canales": [], "plan": plan}
|
| 626 |
CLIENTES[email]["plan"] = plan
|
| 627 |
if "canales" not in CLIENTES[email]:
|
| 628 |
CLIENTES[email]["canales"] = []
|
| 629 |
-
CLIENTES[email]["canales"].append({"url": u, "nicho": nicho, "tipo": tipo, "repo": repo, "youtube": False})
|
| 630 |
guardar_clientes()
|
| 631 |
# Consumir token si era nuevo
|
| 632 |
if token in TOKENS_VALIDOS:
|
|
|
|
| 501 |
<option value="largo">📺 Video Largo</option>
|
| 502 |
<option value="short">📱 Short Vertical</option>
|
| 503 |
</select>
|
| 504 |
+
<label style="font-size:.85rem;color:#aaa;margin-top:12px;display:block">✨ ¿Tienes alguna preferencia? <span style="color:#666">(opcional)</span></label>
|
| 505 |
+
<textarea id="config${i}" rows="4" placeholder="Ej: voz femenina en ingles, subtitulos grandes arriba, tono motivacional, sin musica..." style="width:100%;padding:12px;background:#080808;color:#fff;border:1px solid #333;border-radius:8px;resize:vertical;font-family:system-ui;margin-top:6px"></textarea>
|
| 506 |
`;
|
| 507 |
container.appendChild(div);
|
| 508 |
}
|
|
|
|
| 544 |
for(let i=1; i<=numCanales; i++){
|
| 545 |
const n = document.getElementById('nicho'+i);
|
| 546 |
const t = document.getElementById('tipo'+i);
|
| 547 |
+
const conf = document.getElementById('config'+i);
|
| 548 |
+
if(n && t) canales.push({nicho: n.value, tipo: t.value, config: conf ? conf.value : ""});
|
| 549 |
}
|
| 550 |
document.getElementById('btn').disabled = true;
|
| 551 |
document.getElementById('loading').style.display = 'block';
|
|
|
|
| 597 |
return ACTIVAR_HTML.replace("PLAN_PLACEHOLDER", plan_token).replace("TOKEN_PLACEHOLDER", token).replace("CANALES_PLACEHOLDER", str(PLANES_CANALES.get(plan_token, 1)))
|
| 598 |
return HTMLResponse("<html><body style='background:#080808;color:#fff;text-align:center;padding:80px'><h1 style='color:#ff2d2d'>Acceso no autorizado</h1><a href='/' style='color:#ff2d2d'>Volver</a></body></html>", status_code=403)
|
| 599 |
|
| 600 |
+
async def parsear_config_groq(texto):
|
| 601 |
+
"""Parsea instrucciones del cliente con Groq a JSON de configuracion"""
|
| 602 |
+
if not texto or not texto.strip():
|
| 603 |
+
return {}
|
| 604 |
+
try:
|
| 605 |
+
import httpx
|
| 606 |
+
prompt = f"""Analiza las preferencias del cliente y devuelve SOLO un JSON valido con estas keys (omite las que no se mencionen):
|
| 607 |
+
- voz_ia: true/false (si quiere voz IA, default true si no dice nada)
|
| 608 |
+
- voz_genero: "masculina" o "femenina" (default masculina)
|
| 609 |
+
- idioma: "es"/"en"/"pt"/"fr" (default es)
|
| 610 |
+
- velocidad_voz: 0.8 a 1.5 (default 1.0)
|
| 611 |
+
- subir_musica: true/false (default false)
|
| 612 |
+
- subir_voz: true/false (default false)
|
| 613 |
+
- tono: "serio"/"divertido"/"motivacional"/"educativo" (default educativo)
|
| 614 |
+
- subtitulos: true/false (default true)
|
| 615 |
+
- estilo_subtitulos: "tiktok"/"clasico"/"minimal" (default tiktok)
|
| 616 |
+
- color_subtitulos: codigo hex como "#D4AF37" (default #D4AF37)
|
| 617 |
+
- posicion_subtitulos: "arriba"/"centro"/"abajo" (default abajo)
|
| 618 |
+
|
| 619 |
+
Preferencias del cliente: {texto}
|
| 620 |
+
|
| 621 |
+
Responde SOLO el JSON, sin explicaciones, sin markdown."""
|
| 622 |
+
async with httpx.AsyncClient(timeout=20) as cli:
|
| 623 |
+
r = await cli.post("https://api.groq.com/openai/v1/chat/completions",
|
| 624 |
+
headers={"Authorization": f"Bearer {os.environ.get('GROQ_KEY','')}"},
|
| 625 |
+
json={"model":"llama-3.3-70b-versatile","messages":[{"role":"user","content":prompt}],"temperature":0.2}
|
| 626 |
+
)
|
| 627 |
+
txt = r.json()["choices"][0]["message"]["content"].strip()
|
| 628 |
+
# Limpiar markdown si lo trae
|
| 629 |
+
if "```" in txt:
|
| 630 |
+
txt = txt.split("```")[1].replace("json","",1).strip()
|
| 631 |
+
import json
|
| 632 |
+
return json.loads(txt)
|
| 633 |
+
except Exception as e:
|
| 634 |
+
print(f"Error parseando config: {e}", flush=True)
|
| 635 |
+
return {}
|
| 636 |
+
|
| 637 |
+
|
| 638 |
@app.post("/crear")
|
| 639 |
async def crear(request: Request):
|
| 640 |
data = await request.json()
|
| 641 |
email = data.get("email","").strip().lower()
|
| 642 |
+
canales_data = data.get("canales", [])
|
| 643 |
+
if canales_data:
|
| 644 |
+
nicho = canales_data[0].get("nicho","tecnologia")
|
| 645 |
+
tipo = canales_data[0].get("tipo","largo")
|
| 646 |
+
config_libre = canales_data[0].get("config","").strip()
|
| 647 |
+
else:
|
| 648 |
+
nicho = data.get("nicho","tecnologia")
|
| 649 |
+
tipo = data.get("tipo","largo")
|
| 650 |
+
config_libre = ""
|
| 651 |
token = data.get("token","")
|
| 652 |
# Verificar token o sesion existente
|
| 653 |
plan = "basico"
|
|
|
|
| 668 |
return JSONResponse({"error": "No se pudo crear el canal"}, status_code=500)
|
| 669 |
u = resultado["url"]
|
| 670 |
repo = resultado["repo"]
|
| 671 |
+
# Parsear preferencias del cliente con Groq
|
| 672 |
+
config_parsed = await parsear_config_groq(config_libre)
|
| 673 |
# Guardar en CLIENTES
|
| 674 |
if email not in CLIENTES:
|
| 675 |
CLIENTES[email] = {"canales": [], "plan": plan}
|
| 676 |
CLIENTES[email]["plan"] = plan
|
| 677 |
if "canales" not in CLIENTES[email]:
|
| 678 |
CLIENTES[email]["canales"] = []
|
| 679 |
+
CLIENTES[email]["canales"].append({"url": u, "nicho": nicho, "tipo": tipo, "repo": repo, "youtube": False, "config": config_parsed, "config_texto": config_libre})
|
| 680 |
guardar_clientes()
|
| 681 |
# Consumir token si era nuevo
|
| 682 |
if token in TOKENS_VALIDOS:
|