REVE_space / app.py
BATUTO-ART's picture
Update app.py
de88b77 verified
import os
import base64
import requests
import time
import concurrent.futures
from io import BytesIO
from PIL import Image
import gradio as gr
# --- CONFIGURACIÓN BACKEND ---
API_URL = "https://api.reve.com/v1/image/create"
def llamar_api(prompt, ratio, version, api_key):
payload = {"prompt": prompt, "aspect_ratio": ratio, "version": version}
headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json", "Content-Type": "application/json"}
try:
response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
if response.status_code == 200:
data = response.json()
if "image" in data:
img = Image.open(BytesIO(base64.b64decode(data["image"])))
return img, None
return None, f"Error {response.status_code}"
except Exception as e:
return None, str(e)
def generar_imagenes(prompt, api_key_ui, ratio, version, num_imagenes):
api_key = api_key_ui.strip() if api_key_ui else os.getenv("REVE_API_KEY")
if not api_key: return [], "ERROR: Falta la API Key"
imgs, errores = [], []
with concurrent.futures.ThreadPoolExecutor(max_workers=int(num_imagenes)) as exec:
futuros = [exec.submit(llamar_api, prompt, ratio, version, api_key) for _ in range(int(num_imagenes))]
for f in concurrent.futures.as_completed(futuros):
img, err = f.result()
if img: imgs.append(img)
else: errores.append(err)
return imgs, "Generacion completada" if imgs else f"Error: {errores}"
# --- CSS PERSONALIZADO (STYLE METAVERSE) ---
css_style = """
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;600&display=swap');
body, .gradio-container {
background: #02020a !important;
color: #e2e8f0 !important;
font-family: 'Rajdhani', sans-serif !important;
min-height: 100vh;
}
/* Fondo ciberespacial */
.gradio-container::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 25% 25%, rgba(188, 19, 254, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(0, 242, 255, 0.1) 0%, transparent 50%),
linear-gradient(45deg, rgba(2, 2, 10, 0.9) 0%, rgba(5, 5, 20, 0.9) 100%);
z-index: -1;
}
/* Grid pattern */
.gradio-container::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(0, 242, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
background-size: 50px 50px;
z-index: -1;
opacity: 0.3;
}
/* Header */
h1 {
font-family: 'Orbitron', sans-serif !important;
font-size: 3.5rem !important;
font-weight: 700 !important;
text-align: center !important;
margin-top: 2rem !important;
margin-bottom: 0.5rem !important;
background: linear-gradient(90deg, #bc13fe 0%, #00f2ff 50%, #ffffff 100%) !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
background-clip: text !important;
text-shadow: 0 0 30px rgba(188, 19, 254, 0.5) !important;
}
.subtitle {
text-align: center !important;
color: rgba(0, 242, 255, 0.8) !important;
font-size: 1.2rem !important;
letter-spacing: 2px !important;
margin-bottom: 3rem !important;
font-weight: 300 !important;
}
/* Paneles */
.gr-box {
background: rgba(10, 10, 25, 0.7) !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(0, 242, 255, 0.2) !important;
border-radius: 1rem !important;
padding: 1.5rem !important;
box-shadow: 0 10px 30px rgba(0, 242, 255, 0.1) !important;
}
/* Títulos de panel */
.panel-title {
font-family: 'Orbitron', sans-serif !important;
color: #00f2ff !important;
font-size: 1.5rem !important;
margin-bottom: 1.5rem !important;
display: flex !important;
align-items: center !important;
gap: 10px !important;
}
.panel-title::before {
content: '';
width: 12px;
height: 12px;
border-radius: 50%;
background: #00f2ff;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Inputs */
input, textarea, select {
background: rgba(5, 5, 15, 0.9) !important;
border: 1px solid rgba(0, 242, 255, 0.3) !important;
color: #00f2ff !important;
border-radius: 0.75rem !important;
padding: 0.75rem 1rem !important;
font-family: 'Rajdhani', sans-serif !important;
font-weight: 300 !important;
}
input:focus, textarea:focus, select:focus {
border-color: #00f2ff !important;
box-shadow: 0 0 15px rgba(0, 242, 255, 0.3) !important;
outline: none !important;
}
textarea {
min-height: 120px !important;
resize: vertical !important;
}
/* Labels */
label {
color: #00f2ff !important;
font-weight: 600 !important;
margin-bottom: 0.5rem !important;
display: block !important;
font-size: 0.9rem !important;
letter-spacing: 1px !important;
}
/* Botones */
button {
border-radius: 0.75rem !important;
padding: 0.75rem 1.5rem !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
border: none !important;
cursor: pointer !important;
}
#btn-clear {
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
color: white !important;
}
#btn-clear:hover {
background: rgba(255, 255, 255, 0.15) !important;
box-shadow: 0 0 20px rgba(255, 255, 255, 0.2) !important;
transform: translateY(-2px) !important;
}
#btn-render {
background: linear-gradient(90deg, #bc13fe 0%, #8c00ff 50%, #00f2ff 100%) !important;
color: white !important;
font-family: 'Orbitron', sans-serif !important;
font-size: 1.1rem !important;
letter-spacing: 1px !important;
box-shadow: 0 0 20px rgba(0, 242, 255, 0.4) !important;
position: relative !important;
overflow: hidden !important;
}
#btn-render:hover {
box-shadow: 0 0 40px rgba(0, 242, 255, 0.8) !important;
transform: translateY(-2px) !important;
}
#btn-render::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: 0.5s;
}
#btn-render:hover::before {
left: 100%;
}
/* Slider */
input[type="range"] {
-webkit-appearance: none !important;
height: 8px !important;
background: rgba(5, 5, 15, 0.9) !important;
border-radius: 4px !important;
border: 1px solid rgba(0, 242, 255, 0.3) !important;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none !important;
width: 20px !important;
height: 20px !important;
border-radius: 50% !important;
background: linear-gradient(45deg, #bc13fe, #00f2ff) !important;
cursor: pointer !important;
border: 2px solid white !important;
box-shadow: 0 0 10px rgba(0, 242, 255, 0.8) !important;
}
/* Gallery */
.gr-image {
border-radius: 0.5rem !important;
border: 1px solid rgba(0, 242, 255, 0.2) !important;
transition: transform 0.3s ease !important;
}
.gr-image:hover {
transform: scale(1.02) !important;
box-shadow: 0 0 20px rgba(0, 242, 255, 0.3) !important;
}
/* Status */
.status-box {
padding: 1rem !important;
border-radius: 0.75rem !important;
margin-top: 1rem !important;
border-left: 4px solid #00f2ff !important;
background: rgba(5, 5, 15, 0.5) !important;
}
.status-success {
border-left-color: #00ff00 !important;
background: rgba(0, 255, 0, 0.1) !important;
}
.status-error {
border-left-color: #ff0000 !important;
background: rgba(255, 0, 0, 0.1) !important;
}
/* Footer */
.footer {
text-align: center !important;
color: rgba(255, 255, 255, 0.5) !important;
padding: 2rem 0 !important;
margin-top: 3rem !important;
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
font-size: 0.9rem !important;
}
/* Contador de imágenes */
.image-counter {
font-family: 'Orbitron', sans-serif !important;
color: rgba(0, 242, 255, 0.7) !important;
font-size: 0.9rem !important;
margin-left: auto !important;
}
/* Empty gallery */
.empty-gallery {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
height: 400px !important;
text-align: center !important;
color: rgba(255, 255, 255, 0.5) !important;
}
/* Badges */
.badge {
background: rgba(0, 0, 0, 0.7) !important;
color: #00f2ff !important;
padding: 0.25rem 0.75rem !important;
border-radius: 1rem !important;
font-size: 0.8rem !important;
border: 1px solid rgba(0, 242, 255, 0.3) !important;
}
"""
# Crear la interfaz de Gradio
with gr.Blocks(theme=gr.themes.Soft(), css=css_style) as demo:
# Header
gr.HTML("""
<div style="text-align: center; padding: 2rem 0 1rem 0;">
<h1>REVE AI</h1>
<div class="subtitle">NEURAL RENDERING INTERFACE • V1.2.3</div>
</div>
""")
with gr.Row():
# Panel de Control
with gr.Column(scale=1):
with gr.Column(elem_classes="gr-box"):
gr.HTML('<div class="panel-title">CONTROL PANEL</div>')
api_key = gr.Textbox(
label="ACCESS KEY",
type="password",
placeholder="sk-...",
elem_classes="api-key-input"
)
prompt = gr.Textbox(
label="PROMPT STREAM",
placeholder="Describe tu vision...",
lines=4,
elem_classes="prompt-input"
)
with gr.Row():
clear_btn = gr.Button("CLEAR STREAM", elem_id="btn-clear")
btn_run = gr.Button("INITIALIZE RENDER", elem_id="btn-render")
with gr.Row():
ratio = gr.Dropdown(
choices=["9:16", "16:9", "1:1"],
value="9:16",
label="ASPECT RATIO",
elem_classes="ratio-select"
)
num_imgs = gr.Slider(
minimum=1,
maximum=4,
step=1,
value=1,
label="QUANTITY",
elem_classes="quantity-slider"
)
status = gr.Textbox(
label="STATUS",
value="Status: Ready",
interactive=False,
elem_classes="status-box"
)
# Panel de Galería
with gr.Column(scale=1):
with gr.Column(elem_classes="gr-box"):
with gr.Row():
gr.HTML('<div class="panel-title">RENDER OUTPUT</div>')
image_counter = gr.HTML('<div class="image-counter" id="image-counter">NO DATA</div>')
gallery = gr.Gallery(
label="",
columns=2,
rows=2,
height="auto",
object_fit="contain",
preview=True,
allow_preview=True,
elem_classes="gallery-display"
)
# Botón para descargar todas las imágenes
download_all_btn = gr.Button(
"DOWNLOAD ALL IMAGES",
visible=False,
elem_id="btn-download-all"
)
# Footer
gr.HTML("""
<div class="footer">
<div>REVE AI NEURAL INTERFACE • v1.2.3</div>
<div style="margin-top: 0.5rem; font-size: 0.8rem;">
<span style="color: #00f2ff;">CONNECTED</span> •
<span id="api-status" style="color: #ff5555;">API: INACTIVE</span>
</div>
</div>
""")
# JavaScript para actualizar contador y estado
js_code = """
<script>
// Actualizar contador de imágenes
function updateImageCounter(images) {
const counter = document.getElementById('image-counter');
if (images && images.length > 0) {
counter.textContent = `${images.length} IMAGE${images.length > 1 ? 'S' : ''}`;
document.getElementById('btn-download-all').style.display = 'block';
} else {
counter.textContent = 'NO DATA';
document.getElementById('btn-download-all').style.display = 'none';
}
}
// Actualizar estado de API
function updateApiStatus(apiKey) {
const statusEl = document.getElementById('api-status');
if (apiKey && apiKey.length > 0) {
statusEl.textContent = 'API: ACTIVE';
statusEl.style.color = '#00ff00';
} else {
statusEl.textContent = 'API: INACTIVE';
statusEl.style.color = '#ff5555';
}
}
// Monitorear cambios en el API key
document.addEventListener('DOMContentLoaded', function() {
const apiInput = document.querySelector('input[type="password"]');
if (apiInput) {
apiInput.addEventListener('input', function(e) {
updateApiStatus(e.target.value);
});
updateApiStatus(apiInput.value);
}
});
</script>
"""
gr.HTML(js_code)
# Funciones para manejar eventos
def update_gallery(images):
# Esta función se llamará cuando se generen imágenes
if images and len(images) > 0:
return images, gr.update(visible=True)
return images, gr.update(visible=False)
def clear_all():
return "", [], gr.update(visible=False), "Status: Ready"
# Conectar eventos
clear_btn.click(
fn=clear_all,
outputs=[prompt, gallery, download_all_btn, status]
)
btn_run.click(
fn=generar_imagenes,
inputs=[prompt, api_key, ratio, gr.State("latest"), num_imgs],
outputs=[gallery, status]
).then(
fn=update_gallery,
inputs=[gallery],
outputs=[gallery, download_all_btn]
)
# Función para descargar imágenes
def download_images(images):
if images and len(images) > 0:
# En un entorno real, aquí se crearían archivos ZIP o similar
# Por ahora, solo mostramos un mensaje
return "Download functionality would be implemented here"
return "No images to download"
download_all_btn.click(
fn=download_images,
inputs=[gallery],
outputs=[status]
)
# Ejecutar la aplicación
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False
)