Grok_TTS / app.py
Scrappy-Doo's picture
Update app.py
93d2bd4 verified
Raw
History Blame Contribute Delete
10.4 kB
import streamlit as st
import os
import requests
from io import BytesIO
from openai import OpenAI
XAI_API_KEY = os.getenv("XAI_API_KEY", "")
# El client se inicializa solo si hay key
def get_client():
key = XAI_API_KEY or st.session_state.get("api_key", "")
if not key:
return None
return OpenAI(api_key=key, base_url="https://api.x.ai/v1")
def get_key():
return XAI_API_KEY or st.session_state.get("api_key", "")
st.set_page_config(page_title="Grok Voice Studio", page_icon="πŸ”Š", layout="wide")
st.title("πŸ”Š Grok Voice Studio - TTS, Chat y Clonador xAI")
st.markdown("**Plataforma completa con voz nativa Grok, chat y clonaciΓ³n de voz**")
# Si no hay key en env, pedir al usuario
if not XAI_API_KEY:
with st.sidebar:
st.subheader("πŸ”‘ ConfiguraciΓ³n")
user_key = st.text_input("XAI_API_KEY", type="password", placeholder="xai-...")
if user_key:
st.session_state["api_key"] = user_key
st.success("Key configurada")
def call_tts(text: str, voice: str = "eve", language: str = "es", speed: float = 1.0):
"""
xAI TTS β€” intentamos el endpoint oficial.
Si no existe todavΓ­a (404/501), devolvemos None y mostramos aviso.
"""
key = get_key()
if not key:
return None, "Sin API key"
try:
# Intentar con el endpoint nativo de xAI
r = requests.post(
"https://api.x.ai/v1/audio/speech",
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
json={
"model": "grok-tts", # nombre tentativo segΓΊn docs xAI
"input": text,
"voice": voice,
"response_format": "mp3",
"speed": speed
},
timeout=30
)
if r.status_code == 200:
return BytesIO(r.content), None
# Fallback: endpoint legacy que usaba el app original
r2 = requests.post(
"https://api.x.ai/v1/tts",
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
json={"text": text, "voice_id": voice, "language": language, "speed": speed},
timeout=30
)
if r2.status_code == 200:
return BytesIO(r2.content), None
# Si ninguno funciona, devolver el error real
return None, f"TTS no disponible aΓΊn (HTTP {r.status_code}): {r.text[:300]}"
except Exception as e:
return None, str(e)
tab1, tab2, tab3, tab4 = st.tabs(["πŸ“ TTS Directo", "πŸ’¬ Chat + Voz", "πŸ—£οΈ Clonador", "ℹ️ Info API"])
# ─── TAB 1: TTS DIRECTO ─────────────────────────────────────────────────────
with tab1:
st.subheader("Lector Directo TTS xAI")
texto = st.text_area("Texto:", height=180, placeholder="Escribe el texto que quieres escuchar...")
c1, c2, c3 = st.columns(3)
with c1:
voice = st.selectbox("Voz", ["eve", "ara", "rex", "sal", "leo"], index=0)
with c2:
lang = st.selectbox("Idioma", ["es", "en", "fr", "pt", "it", "auto"], index=0)
with c3:
speed = st.slider("Velocidad", 0.5, 2.0, 1.0, 0.1)
if st.button("πŸŽ™οΈ Generar Audio", type="primary", key="btn_tts"):
if not get_key():
st.error("Configura tu XAI_API_KEY primero (sidebar o variable de entorno)")
elif texto.strip():
with st.spinner("Procesando con Grok TTS..."):
audio_bytes, err = call_tts(texto, voice, lang, speed)
if audio_bytes:
st.audio(audio_bytes, format="audio/mp3")
st.success("βœ… Audio generado")
else:
st.error(f"Error TTS: {err}")
st.info("πŸ’‘ El endpoint TTS de xAI puede no estar disponible en tu plan actual. Consulta https://docs.x.ai")
else:
st.warning("Escribe algΓΊn texto primero")
# ─── TAB 2: CHAT + VOZ ───────────────────────────────────────────────────────
with tab2:
st.subheader("Chat Grok con Voz")
# Selector de modelo β€” grok-4 puede no estar disponible en todos los planes
model_name = st.selectbox("Modelo", ["grok-3", "grok-3-mini", "grok-2-1212"], index=0, key="chat_model")
tts_on = st.toggle("πŸ”Š Leer respuestas en voz alta", value=True)
if "msgs" not in st.session_state:
st.session_state.msgs = []
# BotΓ³n limpiar chat
if st.button("πŸ—‘οΈ Limpiar chat", key="clear_chat"):
st.session_state.msgs = []
st.rerun()
for m in st.session_state.msgs:
with st.chat_message(m["role"]):
st.write(m["content"])
prompt = st.chat_input("Mensaje...")
if prompt:
if not get_key():
st.error("Configura tu XAI_API_KEY primero")
else:
st.session_state.msgs.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.write(prompt)
with st.chat_message("assistant"):
with st.spinner("Pensando..."):
try:
c = get_client()
resp = c.chat.completions.create(
model=model_name,
messages=[{"role": m["role"], "content": m["content"]} for m in st.session_state.msgs],
temperature=0.7,
max_tokens=2048
)
txt = resp.choices[0].message.content
st.write(txt)
st.session_state.msgs.append({"role": "assistant", "content": txt})
if tts_on:
with st.spinner("Generando voz..."):
audio_bytes, err = call_tts(txt[:4000]) # lΓ­mite seguro
if audio_bytes:
st.audio(audio_bytes, format="audio/mp3")
else:
st.caption(f"⚠️ TTS no disponible: {err}")
except Exception as e:
st.error(f"Error chat: {e}")
# ─── TAB 3: CLONADOR ─────────────────────────────────────────────────────────
with tab3:
st.subheader("Clonador de Voz xAI")
st.info("ℹ️ La clonaciΓ³n de voz requiere acceso al endpoint `/v1/custom-voices` de xAI, actualmente en beta privada.")
aud = st.file_uploader("Audio referencia (wav/mp3, mΓ­n. 30s recomendado)", type=["wav", "mp3"])
vname = st.text_input("Nombre para la voz", "MiVoz")
if aud and st.button("🧬 Clonar Voz", key="btn_clone"):
if not get_key():
st.error("Configura tu XAI_API_KEY primero")
else:
with st.spinner("Clonando voz... (puede tardar varios segundos)"):
try:
files = {"file": (aud.name, aud.getvalue(), "audio/wav")}
cr = requests.post(
"https://api.x.ai/v1/custom-voices",
headers={"Authorization": f"Bearer {get_key()}"},
files=files,
data={"name": vname, "language": "es"},
timeout=60
)
if cr.status_code == 200:
vid = cr.json().get("voice_id") or cr.json().get("id")
st.success(f"βœ… Voz creada! ID: `{vid}`")
st.session_state["cloned_voice_id"] = vid
else:
st.error(f"HTTP {cr.status_code}: {cr.text[:400]}")
st.info("El endpoint de clonaciΓ³n puede no estar disponible pΓΊblicamente aΓΊn.")
except Exception as e:
st.error(str(e))
# Probar voz clonada si existe en session
if "cloned_voice_id" in st.session_state:
st.divider()
st.write(f"**Voz activa:** `{st.session_state['cloned_voice_id']}`")
test_text = st.text_area("Texto de prueba", "Hola, esta es mi voz clonada con Grok.")
if st.button("▢️ Probar voz clonada"):
with st.spinner("Sintetizando..."):
audio_bytes, err = call_tts(test_text, voice=st.session_state["cloned_voice_id"])
if audio_bytes:
st.audio(audio_bytes, format="audio/mp3")
else:
st.error(err)
# ─── TAB 4: INFO API ─────────────────────────────────────────────────────────
with tab4:
st.subheader("Estado de la API xAI")
if st.button("πŸ” Verificar conexiΓ³n API", key="btn_check"):
if not get_key():
st.error("Configura tu XAI_API_KEY primero")
else:
with st.spinner("Verificando..."):
try:
c = get_client()
# Llamada mΓ­nima para verificar autenticaciΓ³n
models = c.models.list()
model_names = [m.id for m in models.data]
st.success("βœ… API key vΓ‘lida")
st.write("**Modelos disponibles en tu cuenta:**")
for mn in model_names:
st.code(mn)
except Exception as e:
st.error(f"Error: {e}")
st.divider()
st.markdown("""
### Endpoints xAI relevantes
| Endpoint | Estado |
|----------|--------|
| `/v1/chat/completions` | βœ… Disponible |
| `/v1/models` | βœ… Disponible |
| `/v1/audio/speech` | ⚠️ Beta / verificar |
| `/v1/tts` | ⚠️ Beta / verificar |
| `/v1/custom-voices` | πŸ”’ Beta privada |
**Docs:** https://docs.x.ai/api
**Console:** https://console.x.ai
""")
st.caption("Grok Voice Studio β€’ Powered by xAI API")