BATUTO-ART commited on
Commit
de88b77
·
verified ·
1 Parent(s): e3d5385

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +491 -402
app.py CHANGED
@@ -1,411 +1,500 @@
1
- import React, { useState, useEffect } from "react";
2
- import { motion } from "framer-motion";
3
-
4
- const API_URL = "https://api.reve.com/v1/image/create";
5
-
6
- const App = () => {
7
- // Load Google Fonts
8
- useEffect(() => {
9
- const link = document.createElement("link");
10
- link.href = "https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;600&display=swap";
11
- link.rel = "stylesheet";
12
- document.head.appendChild(link);
13
- return () => document.head.removeChild(link);
14
- }, []);
15
-
16
- const [apiKey, setApiKey] = useState("");
17
- const [prompt, setPrompt] = useState("");
18
- const [aspectRatio, setAspectRatio] = useState("9:16");
19
- const [numImages, setNumImages] = useState(1);
20
- const [images, setImages] = useState([]);
21
- const [status, setStatus] = useState("Status: Ready");
22
- const [isLoading, setIsLoading] = useState(false);
23
-
24
- const handleClear = () => {
25
- setPrompt("");
26
- setImages([]);
27
- setStatus("Status: Ready");
28
- };
29
-
30
- const downloadImage = (base64Data, index) => {
31
- const link = document.createElement('a');
32
- link.href = base64Data;
33
- link.download = `reve_ai_image_${index + 1}.jpg`;
34
- document.body.appendChild(link);
35
- link.click();
36
- document.body.removeChild(link);
37
- };
38
-
39
- const generateImages = async () => {
40
- if (!apiKey.trim()) {
41
- setStatus("ERROR: Falta la API Key");
42
- return;
43
- }
44
 
45
- if (!prompt.trim()) {
46
- setStatus("ERROR: El prompt no puede estar vacio");
47
- return;
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- setIsLoading(true);
51
- setStatus("Generando imagenes...");
52
- setImages([]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- const generationPromises = Array.from({ length: numImages }, () =>
55
- fetch(API_URL, {
56
- method: "POST",
57
- headers: {
58
- "Authorization": `Bearer ${apiKey.trim()}`,
59
- "Accept": "application/json",
60
- "Content-Type": "application/json"
61
- },
62
- body: JSON.stringify({
63
- prompt: prompt.trim(),
64
- aspect_ratio: aspectRatio,
65
- version: "latest"
66
- })
67
- })
68
- .then(response => {
69
- if (!response.ok) throw new Error(`Error ${response.status}`);
70
- return response.json();
71
- })
72
- .then(data => {
73
- if (data.image) {
74
- return `data:image/jpeg;base64,${data.image}`;
75
- }
76
- throw new Error("No image in response");
77
- })
78
- );
79
-
80
- try {
81
- const results = await Promise.allSettled(generationPromises);
82
- const successfulImages = [];
83
- const errors = [];
84
-
85
- results.forEach(result => {
86
- if (result.status === "fulfilled" && result.value) {
87
- successfulImages.push(result.value);
88
- } else {
89
- errors.push(result.reason?.message || "Unknown error");
90
- }
91
- });
92
-
93
- setImages(successfulImages);
94
-
95
- if (successfulImages.length > 0) {
96
- setStatus(`Generacion completada (${successfulImages.length}/${numImages})`);
97
- } else {
98
- setStatus(`Error: ${errors.join(', ')}`);
99
- }
100
- } catch (error) {
101
- setStatus(`Error: ${error.message}`);
102
- } finally {
103
- setIsLoading(false);
104
- }
105
- };
106
-
107
- return (
108
- <div
109
- className="min-h-screen bg-[#02020a] text-[#e2e8f0] font-rajdhani p-4 md:p-8 overflow-hidden relative"
110
- style={{ fontFamily: "'Rajdhani', sans-serif" }}
111
- >
112
- {/* Background Effects */}
113
- <div className="absolute inset-0 bg-gradient-to-br from-[#bc13fe]/10 via-transparent to-[#00f2ff]/5"></div>
114
- <div className="absolute top-1/4 left-1/4 w-64 h-64 bg-[#bc13fe]/5 rounded-full blur-3xl"></div>
115
- <div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-[#00f2ff]/5 rounded-full blur-3xl"></div>
116
-
117
- {/* Grid Pattern */}
118
- <div className="absolute inset-0 opacity-10" style={{
119
- backgroundImage: `linear-gradient(rgba(0, 242, 255, 0.1) 1px, transparent 1px),
120
- linear-gradient(90deg, rgba(0, 242, 255, 0.1) 1px, transparent 1px)`,
121
- backgroundSize: '50px 50px'
122
- }}></div>
123
-
124
- <div className="max-w-6xl mx-auto relative z-10">
125
- {/* Header */}
126
- <motion.div
127
- initial={{ opacity: 0, y: -30 }}
128
- animate={{ opacity: 1, y: 0 }}
129
- className="text-center mb-12 pt-8"
130
- >
131
- <h1
132
- className="text-5xl md:text-7xl font-orbitron mb-4 tracking-wider"
133
- style={{
134
- fontFamily: "'Orbitron', sans-serif",
135
- background: "linear-gradient(90deg, #bc13fe 0%, #00f2ff 50%, #ffffff 100%)",
136
- WebkitBackgroundClip: "text",
137
- WebkitTextFillColor: "transparent",
138
- textShadow: "0 0 30px rgba(188, 19, 254, 0.5)"
139
- }}
140
- >
141
- REVE AI
142
- </h1>
143
- <p className="text-lg text-[#00f2ff]/80 font-light tracking-wider">
144
- NEURAL RENDERING INTERFACE • V1.2.3
145
- </p>
146
- </motion.div>
147
 
148
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
149
- {/* Control Panel */}
150
- <div className="space-y-6 backdrop-blur-sm bg-[rgba(10,10,25,0.7)] border border-[rgba(0,242,255,0.2)] rounded-2xl p-6 shadow-2xl shadow-[#00f2ff]/10 relative overflow-hidden">
151
- <div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-[#bc13fe] to-[#00f2ff]"></div>
152
-
153
- <div className="relative">
154
- <div className="flex items-center gap-2 mb-4">
155
- <div className="w-3 h-3 rounded-full bg-[#00f2ff] animate-pulse"></div>
156
- <h2 className="text-xl font-orbitron" style={{ fontFamily: "'Orbitron', sans-serif" }}>
157
- CONTROL PANEL
158
- </h2>
159
- </div>
160
-
161
- <div className="mb-6">
162
- <label className="block text-sm font-medium mb-2 text-[#00f2ff] tracking-wider">
163
- <span className="flex items-center gap-2">
164
- <span className="w-2 h-2 bg-[#00f2ff] rounded-full"></span>
165
- ACCESS KEY
166
- </span>
167
- </label>
168
- <input
169
- type="password"
170
- value={apiKey}
171
- onChange={(e) => setApiKey(e.target.value)}
172
- placeholder="sk-..."
173
- className="w-full bg-[rgba(5,5,15,0.9)] border border-[rgba(0,242,255,0.3)] text-[#00f2ff] rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-[#00f2ff] focus:border-transparent font-mono tracking-wider shadow-lg"
174
- />
175
- </div>
176
-
177
- <div className="mb-6">
178
- <label className="block text-sm font-medium mb-2 text-[#00f2ff] tracking-wider">
179
- <span className="flex items-center gap-2">
180
- <span className="w-2 h-2 bg-[#00f2ff] rounded-full"></span>
181
- PROMPT STREAM
182
- </span>
183
- </label>
184
- <textarea
185
- value={prompt}
186
- onChange={(e) => setPrompt(e.target.value)}
187
- placeholder="Describe tu vision..."
188
- rows="4"
189
- className="w-full bg-[rgba(5,5,15,0.9)] border border-[rgba(0,242,255,0.3)] text-[#00f2ff] rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-[#00f2ff] focus:border-transparent resize-none shadow-lg"
190
- />
191
- </div>
192
-
193
- <div className="grid grid-cols-2 gap-6 mb-8">
194
- <div>
195
- <label className="block text-sm font-medium mb-2 text-[#00f2ff] tracking-wider">
196
- ASPECT RATIO
197
- </label>
198
- <div className="relative">
199
- <select
200
- value={aspectRatio}
201
- onChange={(e) => setAspectRatio(e.target.value)}
202
- className="w-full bg-[rgba(5,5,15,0.9)] border border-[rgba(0,242,255,0.3)] text-[#00f2ff] rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-[#00f2ff] focus:border-transparent appearance-none shadow-lg"
203
- >
204
- <option value="9:16">9:16 Portrait</option>
205
- <option value="16:9">16:9 Landscape</option>
206
- <option value="1:1">1:1 Square</option>
207
- </select>
208
- <div className="absolute right-3 top-3 text-[#00f2ff]">▼</div>
209
- </div>
210
- </div>
211
 
212
- <div>
213
- <label className="block text-sm font-medium mb-2 text-[#00f2ff] tracking-wider">
214
- QUANTITY: <span className="text-white ml-2">{numImages}</span>
215
- </label>
216
- <input
217
- type="range"
218
- min="1"
219
- max="4"
220
- value={numImages}
221
- onChange={(e) => setNumImages(Number(e.target.value))}
222
- className="w-full h-2 bg-[rgba(5,5,15,0.9)] rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-gradient-to-r [&::-webkit-slider-thumb]:from-[#bc13fe] [&::-webkit-slider-thumb]:to-[#00f2ff] shadow-lg"
223
- />
224
- </div>
225
- </div>
226
-
227
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
228
- <motion.button
229
- whileHover={{ scale: 1.05, boxShadow: "0 0 20px rgba(255,255,255,0.2)" }}
230
- whileTap={{ scale: 0.95 }}
231
- onClick={handleClear}
232
- className="w-full sm:w-auto bg-[rgba(255,255,255,0.1)] border border-[rgba(255,255,255,0.3)] text-white rounded-xl px-8 py-3 font-medium hover:bg-[rgba(255,255,255,0.15)] transition-all duration-300 shadow-lg"
233
- disabled={isLoading}
234
- >
235
- <span className="flex items-center justify-center gap-2">
236
- CLEAR STREAM
237
- </span>
238
- </motion.button>
239
 
240
- <motion.button
241
- whileHover={{ scale: 1.05, boxShadow: "0 0 30px rgba(0,242,255,0.7)" }}
242
- whileTap={{ scale: 0.95 }}
243
- onClick={generateImages}
244
- disabled={isLoading}
245
- className="w-full sm:w-auto bg-gradient-to-r from-[#bc13fe] via-[#8c00ff] to-[#00f2ff] text-white rounded-xl px-8 py-3 font-orbitron font-bold shadow-2xl hover:shadow-[0_0_40px_rgba(0,242,255,0.8)] transition-all duration-300 relative overflow-hidden group disabled:opacity-50 disabled:cursor-not-allowed"
246
- style={{ fontFamily: "'Orbitron', sans-serif" }}
247
- >
248
- <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div>
249
- <span className="flex items-center justify-center gap-2 relative">
250
- {isLoading ? (
251
- <>
252
- <span>GENERATING...</span>
253
- </>
254
- ) : (
255
- <>
256
- <span>INITIALIZE RENDER</span>
257
- </>
258
- )}
259
- </span>
260
- </motion.button>
261
- </div>
262
- </div>
263
-
264
- {/* Status Display */}
265
- <div
266
- className={`p-4 rounded-xl mt-6 border-l-4 ${
267
- status.includes("Generacion completada") ? "border-green-500 bg-green-900/20" :
268
- status.includes("Error") ? "border-red-500 bg-red-900/20" :
269
- "border-[#00f2ff] bg-[rgba(5,5,15,0.5)]"
270
- }`}
271
- >
272
- <div className="flex items-center gap-3">
273
- <div className={`w-3 h-3 rounded-full ${
274
- status.includes("Generacion completada") ? "bg-green-500 animate-pulse" :
275
- status.includes("Error") ? "bg-red-500" :
276
- "bg-[#00f2ff]"
277
- }`}></div>
278
- <p className="font-medium tracking-wider">{status}</p>
279
- </div>
280
- </div>
281
- </div>
282
-
283
- {/* Gallery Panel */}
284
- <div className="backdrop-blur-sm bg-[rgba(10,10,25,0.7)] border border-[rgba(0,242,255,0.2)] rounded-2xl p-6 shadow-2xl shadow-[#00f2ff]/10 relative overflow-hidden h-fit">
285
- <div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-[#00f2ff] to-[#bc13fe]"></div>
286
-
287
- <div className="flex items-center gap-2 mb-6">
288
- <div className="w-3 h-3 rounded-full bg-[#00f2ff] animate-pulse"></div>
289
- <h2 className="text-xl font-orbitron" style={{ fontFamily: "'Orbitron', sans-serif" }}>
290
- RENDER OUTPUT
291
- </h2>
292
- <div className="ml-auto text-sm text-[#00f2ff]/70">
293
- {images.length > 0 ? `${images.length} IMAGE${images.length > 1 ? 'S' : ''}` : 'NO DATA'}
294
- </div>
295
- </div>
296
-
297
- {images.length > 0 ? (
298
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
299
- {images.map((imgSrc, index) => (
300
- <motion.div
301
- key={index}
302
- initial={{ opacity: 0, scale: 0.9, y: 20 }}
303
- animate={{ opacity: 1, scale: 1, y: 0 }}
304
- transition={{ delay: index * 0.1 }}
305
- className="bg-[rgba(5,5,15,0.9)] border border-[rgba(0,242,255,0.2)] rounded-xl overflow-hidden shadow-xl group relative"
306
- >
307
- <div className="relative">
308
- <img
309
- src={imgSrc}
310
- alt={`Generacion ${index + 1}`}
311
- className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-500"
312
- />
313
- <div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
314
-
315
- {/* Download Button */}
316
- <motion.button
317
- whileHover={{ scale: 1.1 }}
318
- whileTap={{ scale: 0.9 }}
319
- onClick={() => downloadImage(imgSrc, index)}
320
- className="absolute top-3 right-3 bg-black/70 hover:bg-black/90 text-white p-2 rounded-full opacity-0 group-hover:opacity-100 transition-all duration-300 shadow-lg"
321
- title="Descargar imagen"
322
- >
323
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
324
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v-6m0 0l-3 3m3-3l3 3M3 15a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 00-2-2H5a2 2 0 00-2 2v3z"></path>
325
- </svg>
326
- </motion.button>
327
-
328
- <div className="absolute bottom-3 right-3 bg-black/70 text-xs px-3 py-1 rounded-full border border-[#00f2ff]/30">
329
- {aspectRatio}
330
- </div>
331
- <div className="absolute bottom-3 left-3 bg-black/70 text-xs px-3 py-1 rounded-full">
332
- #{index + 1}
333
- </div>
334
- </div>
335
- <div className="p-3">
336
- <p className="text-xs text-[#00f2ff]/70 truncate">
337
- {prompt.substring(0, 40)}...
338
- </p>
339
- </div>
340
- </motion.div>
341
- ))}
342
- </div>
343
- ) : (
344
- <div className="bg-[rgba(5,5,15,0.5)] border-2 border-dashed border-[rgba(0,242,255,0.2)] rounded-xl h-96 flex flex-col items-center justify-center p-8 text-center">
345
- <div className="w-20 h-20 mb-6 rounded-full bg-gradient-to-br from-[#bc13fe]/20 to-[#00f2ff]/20 flex items-center justify-center border border-[rgba(0,242,255,0.1)]">
346
- <span className="text-3xl text-[#00f2ff]">[ ]</span>
347
- </div>
348
- <h3 className="text-xl font-orbitron mb-2 text-[#00f2ff]" style={{ fontFamily: "'Orbitron', sans-serif" }}>
349
- RENDER QUEUE EMPTY
350
- </h3>
351
- <p className="text-gray-400 max-w-md">
352
- Configure your parameters and click "INITIALIZE RENDER" to generate neural imagery.
353
- </p>
354
- <div className="mt-6 flex gap-2">
355
- {[1, 2, 3, 4].map(i => (
356
- <div key={i} className="w-2 h-2 rounded-full bg-[#00f2ff]/30 animate-pulse" style={{ animationDelay: `${i * 0.2}s` }}></div>
357
- ))}
358
- </div>
359
- </div>
360
- )}
361
-
362
- {/* Gallery Stats */}
363
- {images.length > 0 && (
364
- <div className="mt-6 pt-6 border-t border-[rgba(0,242,255,0.1)]">
365
- <div className="flex justify-between text-sm">
366
- <div className="text-[#00f2ff]">
367
- <span className="opacity-70">TOTAL SIZE:</span>
368
- <span className="ml-2">{images.length * 2.5} MB</span>
369
- </div>
370
- <div className="text-[#00f2ff]">
371
- <span className="opacity-70">RESOLUTION:</span>
372
- <span className="ml-2">{aspectRatio}</span>
373
- </div>
374
- <button
375
- onClick={() => images.forEach((img, i) => downloadImage(img, i))}
376
- className="text-[#bc13fe] hover:text-white transition-colors"
377
- >
378
- DOWNLOAD ALL
379
- </button>
380
- </div>
381
- </div>
382
- )}
383
- </div>
384
  </div>
385
-
386
- {/* Footer */}
387
- <footer className="mt-16 pt-8 border-t border-[rgba(255,255,255,0.1)] text-center">
388
- <div className="flex flex-col md:flex-row items-center justify-between text-sm text-gray-500">
389
- <div className="mb-4 md:mb-0">
390
- <p className="flex items-center justify-center gap-2">
391
- <span className="w-2 h-2 rounded-full bg-[#00f2ff] animate-pulse"></span>
392
- REVE AI NEURAL INTERFACE • v1.2.3
393
- </p>
394
- </div>
395
- <div className="flex items-center gap-6">
396
- <span className="text-[#00f2ff]/70">CONNECTED</span>
397
- <div className="flex gap-1">
398
- {[1, 2, 3].map(i => (
399
- <div key={i} className="w-2 h-2 rounded-full bg-green-500 animate-pulse" style={{ animationDelay: `${i * 0.2}s` }}></div>
400
- ))}
401
- </div>
402
- <span>API: {apiKey ? 'ACTIVE' : 'INACTIVE'}</span>
403
- </div>
404
- </div>
405
- </footer>
406
- </div>
407
  </div>
408
- );
409
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
- export default App;
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ import requests
4
+ import time
5
+ import concurrent.futures
6
+ from io import BytesIO
7
+ from PIL import Image
8
+ import gradio as gr
9
+
10
+ # --- CONFIGURACIÓN BACKEND ---
11
+ API_URL = "https://api.reve.com/v1/image/create"
12
+
13
+ def llamar_api(prompt, ratio, version, api_key):
14
+ payload = {"prompt": prompt, "aspect_ratio": ratio, "version": version}
15
+ headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json", "Content-Type": "application/json"}
16
+ try:
17
+ response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
18
+ if response.status_code == 200:
19
+ data = response.json()
20
+ if "image" in data:
21
+ img = Image.open(BytesIO(base64.b64decode(data["image"])))
22
+ return img, None
23
+ return None, f"Error {response.status_code}"
24
+ except Exception as e:
25
+ return None, str(e)
26
+
27
+ def generar_imagenes(prompt, api_key_ui, ratio, version, num_imagenes):
28
+ api_key = api_key_ui.strip() if api_key_ui else os.getenv("REVE_API_KEY")
29
+ if not api_key: return [], "ERROR: Falta la API Key"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ imgs, errores = [], []
32
+ with concurrent.futures.ThreadPoolExecutor(max_workers=int(num_imagenes)) as exec:
33
+ futuros = [exec.submit(llamar_api, prompt, ratio, version, api_key) for _ in range(int(num_imagenes))]
34
+ for f in concurrent.futures.as_completed(futuros):
35
+ img, err = f.result()
36
+ if img: imgs.append(img)
37
+ else: errores.append(err)
38
+
39
+ return imgs, "Generacion completada" if imgs else f"Error: {errores}"
40
+
41
+ # --- CSS PERSONALIZADO (STYLE METAVERSE) ---
42
+ css_style = """
43
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;600&display=swap');
44
+
45
+ body, .gradio-container {
46
+ background: #02020a !important;
47
+ color: #e2e8f0 !important;
48
+ font-family: 'Rajdhani', sans-serif !important;
49
+ min-height: 100vh;
50
+ }
51
+
52
+ /* Fondo ciberespacial */
53
+ .gradio-container::before {
54
+ content: '';
55
+ position: fixed;
56
+ top: 0;
57
+ left: 0;
58
+ width: 100%;
59
+ height: 100%;
60
+ background:
61
+ radial-gradient(circle at 25% 25%, rgba(188, 19, 254, 0.1) 0%, transparent 50%),
62
+ radial-gradient(circle at 75% 75%, rgba(0, 242, 255, 0.1) 0%, transparent 50%),
63
+ linear-gradient(45deg, rgba(2, 2, 10, 0.9) 0%, rgba(5, 5, 20, 0.9) 100%);
64
+ z-index: -1;
65
+ }
66
+
67
+ /* Grid pattern */
68
+ .gradio-container::after {
69
+ content: '';
70
+ position: fixed;
71
+ top: 0;
72
+ left: 0;
73
+ width: 100%;
74
+ height: 100%;
75
+ background-image:
76
+ linear-gradient(rgba(0, 242, 255, 0.05) 1px, transparent 1px),
77
+ linear-gradient(90deg, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
78
+ background-size: 50px 50px;
79
+ z-index: -1;
80
+ opacity: 0.3;
81
+ }
82
+
83
+ /* Header */
84
+ h1 {
85
+ font-family: 'Orbitron', sans-serif !important;
86
+ font-size: 3.5rem !important;
87
+ font-weight: 700 !important;
88
+ text-align: center !important;
89
+ margin-top: 2rem !important;
90
+ margin-bottom: 0.5rem !important;
91
+ background: linear-gradient(90deg, #bc13fe 0%, #00f2ff 50%, #ffffff 100%) !important;
92
+ -webkit-background-clip: text !important;
93
+ -webkit-text-fill-color: transparent !important;
94
+ background-clip: text !important;
95
+ text-shadow: 0 0 30px rgba(188, 19, 254, 0.5) !important;
96
+ }
97
+
98
+ .subtitle {
99
+ text-align: center !important;
100
+ color: rgba(0, 242, 255, 0.8) !important;
101
+ font-size: 1.2rem !important;
102
+ letter-spacing: 2px !important;
103
+ margin-bottom: 3rem !important;
104
+ font-weight: 300 !important;
105
+ }
106
+
107
+ /* Paneles */
108
+ .gr-box {
109
+ background: rgba(10, 10, 25, 0.7) !important;
110
+ backdrop-filter: blur(10px) !important;
111
+ border: 1px solid rgba(0, 242, 255, 0.2) !important;
112
+ border-radius: 1rem !important;
113
+ padding: 1.5rem !important;
114
+ box-shadow: 0 10px 30px rgba(0, 242, 255, 0.1) !important;
115
+ }
116
+
117
+ /* Títulos de panel */
118
+ .panel-title {
119
+ font-family: 'Orbitron', sans-serif !important;
120
+ color: #00f2ff !important;
121
+ font-size: 1.5rem !important;
122
+ margin-bottom: 1.5rem !important;
123
+ display: flex !important;
124
+ align-items: center !important;
125
+ gap: 10px !important;
126
+ }
127
+
128
+ .panel-title::before {
129
+ content: '';
130
+ width: 12px;
131
+ height: 12px;
132
+ border-radius: 50%;
133
+ background: #00f2ff;
134
+ animation: pulse 2s infinite;
135
+ }
136
+
137
+ @keyframes pulse {
138
+ 0%, 100% { opacity: 1; }
139
+ 50% { opacity: 0.5; }
140
+ }
141
+
142
+ /* Inputs */
143
+ input, textarea, select {
144
+ background: rgba(5, 5, 15, 0.9) !important;
145
+ border: 1px solid rgba(0, 242, 255, 0.3) !important;
146
+ color: #00f2ff !important;
147
+ border-radius: 0.75rem !important;
148
+ padding: 0.75rem 1rem !important;
149
+ font-family: 'Rajdhani', sans-serif !important;
150
+ font-weight: 300 !important;
151
+ }
152
+
153
+ input:focus, textarea:focus, select:focus {
154
+ border-color: #00f2ff !important;
155
+ box-shadow: 0 0 15px rgba(0, 242, 255, 0.3) !important;
156
+ outline: none !important;
157
+ }
158
+
159
+ textarea {
160
+ min-height: 120px !important;
161
+ resize: vertical !important;
162
+ }
163
+
164
+ /* Labels */
165
+ label {
166
+ color: #00f2ff !important;
167
+ font-weight: 600 !important;
168
+ margin-bottom: 0.5rem !important;
169
+ display: block !important;
170
+ font-size: 0.9rem !important;
171
+ letter-spacing: 1px !important;
172
+ }
173
+
174
+ /* Botones */
175
+ button {
176
+ border-radius: 0.75rem !important;
177
+ padding: 0.75rem 1.5rem !important;
178
+ font-weight: 600 !important;
179
+ transition: all 0.3s ease !important;
180
+ border: none !important;
181
+ cursor: pointer !important;
182
+ }
183
+
184
+ #btn-clear {
185
+ background: rgba(255, 255, 255, 0.1) !important;
186
+ border: 1px solid rgba(255, 255, 255, 0.3) !important;
187
+ color: white !important;
188
+ }
189
+
190
+ #btn-clear:hover {
191
+ background: rgba(255, 255, 255, 0.15) !important;
192
+ box-shadow: 0 0 20px rgba(255, 255, 255, 0.2) !important;
193
+ transform: translateY(-2px) !important;
194
+ }
195
+
196
+ #btn-render {
197
+ background: linear-gradient(90deg, #bc13fe 0%, #8c00ff 50%, #00f2ff 100%) !important;
198
+ color: white !important;
199
+ font-family: 'Orbitron', sans-serif !important;
200
+ font-size: 1.1rem !important;
201
+ letter-spacing: 1px !important;
202
+ box-shadow: 0 0 20px rgba(0, 242, 255, 0.4) !important;
203
+ position: relative !important;
204
+ overflow: hidden !important;
205
+ }
206
+
207
+ #btn-render:hover {
208
+ box-shadow: 0 0 40px rgba(0, 242, 255, 0.8) !important;
209
+ transform: translateY(-2px) !important;
210
+ }
211
+
212
+ #btn-render::before {
213
+ content: '';
214
+ position: absolute;
215
+ top: 0;
216
+ left: -100%;
217
+ width: 100%;
218
+ height: 100%;
219
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
220
+ transition: 0.5s;
221
+ }
222
+
223
+ #btn-render:hover::before {
224
+ left: 100%;
225
+ }
226
+
227
+ /* Slider */
228
+ input[type="range"] {
229
+ -webkit-appearance: none !important;
230
+ height: 8px !important;
231
+ background: rgba(5, 5, 15, 0.9) !important;
232
+ border-radius: 4px !important;
233
+ border: 1px solid rgba(0, 242, 255, 0.3) !important;
234
+ }
235
+
236
+ input[type="range"]::-webkit-slider-thumb {
237
+ -webkit-appearance: none !important;
238
+ width: 20px !important;
239
+ height: 20px !important;
240
+ border-radius: 50% !important;
241
+ background: linear-gradient(45deg, #bc13fe, #00f2ff) !important;
242
+ cursor: pointer !important;
243
+ border: 2px solid white !important;
244
+ box-shadow: 0 0 10px rgba(0, 242, 255, 0.8) !important;
245
+ }
246
+
247
+ /* Gallery */
248
+ .gr-image {
249
+ border-radius: 0.5rem !important;
250
+ border: 1px solid rgba(0, 242, 255, 0.2) !important;
251
+ transition: transform 0.3s ease !important;
252
+ }
253
+
254
+ .gr-image:hover {
255
+ transform: scale(1.02) !important;
256
+ box-shadow: 0 0 20px rgba(0, 242, 255, 0.3) !important;
257
+ }
258
+
259
+ /* Status */
260
+ .status-box {
261
+ padding: 1rem !important;
262
+ border-radius: 0.75rem !important;
263
+ margin-top: 1rem !important;
264
+ border-left: 4px solid #00f2ff !important;
265
+ background: rgba(5, 5, 15, 0.5) !important;
266
+ }
267
+
268
+ .status-success {
269
+ border-left-color: #00ff00 !important;
270
+ background: rgba(0, 255, 0, 0.1) !important;
271
+ }
272
 
273
+ .status-error {
274
+ border-left-color: #ff0000 !important;
275
+ background: rgba(255, 0, 0, 0.1) !important;
276
+ }
277
+
278
+ /* Footer */
279
+ .footer {
280
+ text-align: center !important;
281
+ color: rgba(255, 255, 255, 0.5) !important;
282
+ padding: 2rem 0 !important;
283
+ margin-top: 3rem !important;
284
+ border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
285
+ font-size: 0.9rem !important;
286
+ }
287
+
288
+ /* Contador de imágenes */
289
+ .image-counter {
290
+ font-family: 'Orbitron', sans-serif !important;
291
+ color: rgba(0, 242, 255, 0.7) !important;
292
+ font-size: 0.9rem !important;
293
+ margin-left: auto !important;
294
+ }
295
+
296
+ /* Empty gallery */
297
+ .empty-gallery {
298
+ display: flex !important;
299
+ flex-direction: column !important;
300
+ align-items: center !important;
301
+ justify-content: center !important;
302
+ height: 400px !important;
303
+ text-align: center !important;
304
+ color: rgba(255, 255, 255, 0.5) !important;
305
+ }
306
+
307
+ /* Badges */
308
+ .badge {
309
+ background: rgba(0, 0, 0, 0.7) !important;
310
+ color: #00f2ff !important;
311
+ padding: 0.25rem 0.75rem !important;
312
+ border-radius: 1rem !important;
313
+ font-size: 0.8rem !important;
314
+ border: 1px solid rgba(0, 242, 255, 0.3) !important;
315
+ }
316
+ """
317
+
318
+ # Crear la interfaz de Gradio
319
+ with gr.Blocks(theme=gr.themes.Soft(), css=css_style) as demo:
320
+ # Header
321
+ gr.HTML("""
322
+ <div style="text-align: center; padding: 2rem 0 1rem 0;">
323
+ <h1>REVE AI</h1>
324
+ <div class="subtitle">NEURAL RENDERING INTERFACE • V1.2.3</div>
325
+ </div>
326
+ """)
327
 
328
+ with gr.Row():
329
+ # Panel de Control
330
+ with gr.Column(scale=1):
331
+ with gr.Column(elem_classes="gr-box"):
332
+ gr.HTML('<div class="panel-title">CONTROL PANEL</div>')
333
+
334
+ api_key = gr.Textbox(
335
+ label="ACCESS KEY",
336
+ type="password",
337
+ placeholder="sk-...",
338
+ elem_classes="api-key-input"
339
+ )
340
+
341
+ prompt = gr.Textbox(
342
+ label="PROMPT STREAM",
343
+ placeholder="Describe tu vision...",
344
+ lines=4,
345
+ elem_classes="prompt-input"
346
+ )
347
+
348
+ with gr.Row():
349
+ clear_btn = gr.Button("CLEAR STREAM", elem_id="btn-clear")
350
+ btn_run = gr.Button("INITIALIZE RENDER", elem_id="btn-render")
351
+
352
+ with gr.Row():
353
+ ratio = gr.Dropdown(
354
+ choices=["9:16", "16:9", "1:1"],
355
+ value="9:16",
356
+ label="ASPECT RATIO",
357
+ elem_classes="ratio-select"
358
+ )
359
+
360
+ num_imgs = gr.Slider(
361
+ minimum=1,
362
+ maximum=4,
363
+ step=1,
364
+ value=1,
365
+ label="QUANTITY",
366
+ elem_classes="quantity-slider"
367
+ )
368
+
369
+ status = gr.Textbox(
370
+ label="STATUS",
371
+ value="Status: Ready",
372
+ interactive=False,
373
+ elem_classes="status-box"
374
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
+ # Panel de Galería
377
+ with gr.Column(scale=1):
378
+ with gr.Column(elem_classes="gr-box"):
379
+ with gr.Row():
380
+ gr.HTML('<div class="panel-title">RENDER OUTPUT</div>')
381
+ image_counter = gr.HTML('<div class="image-counter" id="image-counter">NO DATA</div>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ gallery = gr.Gallery(
384
+ label="",
385
+ columns=2,
386
+ rows=2,
387
+ height="auto",
388
+ object_fit="contain",
389
+ preview=True,
390
+ allow_preview=True,
391
+ elem_classes="gallery-display"
392
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
+ # Botón para descargar todas las imágenes
395
+ download_all_btn = gr.Button(
396
+ "DOWNLOAD ALL IMAGES",
397
+ visible=False,
398
+ elem_id="btn-download-all"
399
+ )
400
+
401
+ # Footer
402
+ gr.HTML("""
403
+ <div class="footer">
404
+ <div>REVE AI NEURAL INTERFACE • v1.2.3</div>
405
+ <div style="margin-top: 0.5rem; font-size: 0.8rem;">
406
+ <span style="color: #00f2ff;">CONNECTED</span>
407
+ <span id="api-status" style="color: #ff5555;">API: INACTIVE</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  </div>
410
+ """)
411
+
412
+ # JavaScript para actualizar contador y estado
413
+ js_code = """
414
+ <script>
415
+ // Actualizar contador de imágenes
416
+ function updateImageCounter(images) {
417
+ const counter = document.getElementById('image-counter');
418
+ if (images && images.length > 0) {
419
+ counter.textContent = `${images.length} IMAGE${images.length > 1 ? 'S' : ''}`;
420
+ document.getElementById('btn-download-all').style.display = 'block';
421
+ } else {
422
+ counter.textContent = 'NO DATA';
423
+ document.getElementById('btn-download-all').style.display = 'none';
424
+ }
425
+ }
426
+
427
+ // Actualizar estado de API
428
+ function updateApiStatus(apiKey) {
429
+ const statusEl = document.getElementById('api-status');
430
+ if (apiKey && apiKey.length > 0) {
431
+ statusEl.textContent = 'API: ACTIVE';
432
+ statusEl.style.color = '#00ff00';
433
+ } else {
434
+ statusEl.textContent = 'API: INACTIVE';
435
+ statusEl.style.color = '#ff5555';
436
+ }
437
+ }
438
+
439
+ // Monitorear cambios en el API key
440
+ document.addEventListener('DOMContentLoaded', function() {
441
+ const apiInput = document.querySelector('input[type="password"]');
442
+ if (apiInput) {
443
+ apiInput.addEventListener('input', function(e) {
444
+ updateApiStatus(e.target.value);
445
+ });
446
+ updateApiStatus(apiInput.value);
447
+ }
448
+ });
449
+ </script>
450
+ """
451
+
452
+ gr.HTML(js_code)
453
+
454
+ # Funciones para manejar eventos
455
+ def update_gallery(images):
456
+ # Esta función se llamará cuando se generen imágenes
457
+ if images and len(images) > 0:
458
+ return images, gr.update(visible=True)
459
+ return images, gr.update(visible=False)
460
+
461
+ def clear_all():
462
+ return "", [], gr.update(visible=False), "Status: Ready"
463
+
464
+ # Conectar eventos
465
+ clear_btn.click(
466
+ fn=clear_all,
467
+ outputs=[prompt, gallery, download_all_btn, status]
468
+ )
469
+
470
+ btn_run.click(
471
+ fn=generar_imagenes,
472
+ inputs=[prompt, api_key, ratio, gr.State("latest"), num_imgs],
473
+ outputs=[gallery, status]
474
+ ).then(
475
+ fn=update_gallery,
476
+ inputs=[gallery],
477
+ outputs=[gallery, download_all_btn]
478
+ )
479
+
480
+ # Función para descargar imágenes
481
+ def download_images(images):
482
+ if images and len(images) > 0:
483
+ # En un entorno real, aquí se crearían archivos ZIP o similar
484
+ # Por ahora, solo mostramos un mensaje
485
+ return "Download functionality would be implemented here"
486
+ return "No images to download"
487
+
488
+ download_all_btn.click(
489
+ fn=download_images,
490
+ inputs=[gallery],
491
+ outputs=[status]
492
+ )
493
 
494
+ # Ejecutar la aplicación
495
+ if __name__ == "__main__":
496
+ demo.launch(
497
+ server_name="0.0.0.0",
498
+ server_port=7860,
499
+ share=False
500
+ )