BATUTO-ART commited on
Commit
d05b13f
·
verified ·
1 Parent(s): 69a13cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +341 -116
app.py CHANGED
@@ -1,246 +1,471 @@
1
  import os
2
- import gradio as gr
3
  import base64
 
 
 
 
 
 
4
  from openai import OpenAI
5
 
6
  # ============================================================
7
- # CONFIG
8
  # ============================================================
9
 
10
- API_KEY = os.getenv("OPENROUTER_API_KEY")
11
- if not API_KEY:
 
 
 
 
 
12
  raise ValueError("Falta la variable OPENROUTER_API_KEY.")
13
 
14
- client = OpenAI(
15
  base_url="https://openrouter.ai/api/v1",
16
- api_key=API_KEY
17
  )
18
 
 
 
 
 
 
 
19
  # ============================================================
20
- # TODOS TUS MODELOS COMPLETOS
21
  # ============================================================
22
 
23
  MODELS = {
24
- # -------- META LLAMA --------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  "llama_3.1_70b": {
 
26
  "name": "meta-llama/llama-3.1-70b-instruct",
27
- "role": "Respondes con precisión técnica y claridad."
 
28
  },
29
  "llama_3.1_405b": {
 
30
  "name": "meta-llama/llama-3.1-405b-instruct",
31
- "role": "Eres experto en programación, ciencia y análisis avanzado."
 
32
  },
33
 
34
- # -------- LLAMA 3.2 VISION --------
35
  "llama_3.2_11b_vision": {
 
36
  "name": "meta-llama/llama-3.2-11b-vision-instruct",
37
- "role": "Eres un modelo experto en análisis visual detallado."
 
38
  },
39
  "llama_3.2_90b_vision": {
 
40
  "name": "meta-llama/llama-3.2-90b-vision-instruct",
41
- "role": "Eres un analista visual avanzado altamente preciso."
 
42
  },
43
 
44
- # -------- QWEN --------
45
  "qwen_72b": {
 
46
  "name": "qwen/qwen2.5-72b-instruct",
47
- "role": "Respondes de forma profesional, directa y clara."
 
48
  },
49
  "qwen_110b": {
 
50
  "name": "qwen/qwen2.5-110b-instruct",
51
- "role": "Asistente experto en razonamiento estructurado."
 
52
  },
53
 
54
- # -------- GPT / OPENAI --------
55
  "gpt_4.1": {
 
56
  "name": "openai/gpt-4.1",
57
- "role": "Asistente avanzado para cualquier tarea general."
 
58
  },
59
  "gpt_4.1_mini": {
 
60
  "name": "openai/gpt-4.1-mini",
61
- "role": "Modelo rápido y eficiente, ideal para respuestas concisas."
 
62
  },
63
  "gpt_4o_mini": {
 
64
  "name": "openai/gpt-4o-mini",
65
- "role": "Asistente veloz con buena comprensión general."
 
66
  },
67
 
68
- # -------- CLAUDE --------
69
  "claude_3.5_sonnet": {
 
70
  "name": "anthropic/claude-3.5-sonnet",
71
- "role": "Especialista en redacción, precisión y análisis profundo."
 
72
  },
73
  "claude_3.5_haiku": {
 
74
  "name": "anthropic/claude-3.5-haiku",
75
- "role": "Modelo rápido con buena comprensión general."
 
76
  },
77
  "claude_3_opus": {
 
78
  "name": "anthropic/claude-3-opus",
79
- "role": "Máxima capacidad de análisis y lenguaje."
 
80
  },
81
 
82
- # -------- GOOGLE --------
83
  "gemini_flash": {
 
84
  "name": "google/gemini-flash-1.5",
85
- "role": "Especialista en escenarios visuales y respuestas rápidas."
 
86
  },
87
  "gemini_pro": {
 
88
  "name": "google/gemini-pro-1.5",
89
- "role": "Razonador general robusto y flexible."
 
90
  },
91
  "gemini_thinking": {
 
92
  "name": "google/gemini-1.5-thinking",
93
- "role": "Modelo de razonamiento profundo y detallado."
 
94
  },
95
 
96
- # -------- DEEPSEEK --------
97
  "deepseek_r1": {
 
98
  "name": "deepseek/deepseek-r1",
99
- "role": "Razonamiento profundo y cadena de pensamiento estructurada."
 
100
  },
101
 
102
- # -------- MISTRAL --------
103
  "mistral_large": {
 
104
  "name": "mistral/mistral-large-latest",
105
- "role": "Asistente técnico avanzado y preciso."
 
106
  },
107
  "mixtral_8x7b": {
 
108
  "name": "mistral/mixtral-8x7b-instruct",
109
- "role": "Modelo eficiente para tareas complejas sin alto costo."
 
110
  },
111
 
112
- # -------- REKA --------
113
  "reka_core": {
 
114
  "name": "reka/core",
115
- "role": "Asistente racional y estructurado."
 
116
  },
117
 
118
- # -------- SAMBANOVA --------
119
  "samba_allam_7b": {
 
120
  "name": "sambanova/ALLAM-1-7B",
121
- "role": "Asistente optimizado para rendimiento y claridad."
 
122
  },
123
 
124
- # -------- FLUX --------
125
  "flux_pro": {
 
126
  "name": "black-forest-labs/flux-1.1-pro",
127
- "role": "Experto en generación y análisis de imágenes."
 
128
  }
129
  }
130
 
 
131
  # ============================================================
132
- # BUILDER DE MENSAJES (CORRIGE EL ERROR)
133
  # ============================================================
134
 
135
- def build_messages(system_prompt, user_input, history):
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  messages = []
137
 
138
- # SYSTEM
139
  if system_prompt:
140
  messages.append({"role": "system", "content": system_prompt})
141
 
142
- # HISTORIAL NORMALIZADO
143
- for item in history:
144
- if isinstance(item, tuple) and len(item) == 2:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- user_msg, assistant_msg = item
147
 
148
- if isinstance(user_msg, str) and user_msg.strip():
149
- messages.append({"role": "user", "content": user_msg})
150
 
151
- if isinstance(assistant_msg, str) and assistant_msg.strip():
152
- messages.append({"role": "assistant", "content": assistant_msg})
 
153
 
154
- # MENSAJE ACTUAL
155
- messages.append({"role": "user", "content": user_input})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- return messages
158
 
159
  # ============================================================
160
- # IMAGE base64
161
  # ============================================================
162
 
163
- def encode_image_to_base64(image):
164
- if image is None:
165
- return None
166
- import io
167
- import PIL.Image
168
- buf = io.BytesIO()
169
- image.save(buf, format="PNG")
170
- return base64.b64encode(buf.getvalue()).decode("utf-8")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  # ============================================================
173
- # GENERADOR
174
  # ============================================================
175
 
176
- def generate(model_key, prompt, image, history):
177
- selected = MODELS.get(model_key)
178
- if not selected:
179
- return "Error: Modelo no encontrado."
180
-
181
- system_prompt = selected["role"]
182
- messages = build_messages(system_prompt, prompt, history)
183
-
184
- # Imagen opcional
185
- if image:
186
- img_b64 = encode_image_to_base64(image)
187
-
188
- messages.append({
189
- "role": "user",
190
- "content": [
191
- {"type": "input_image", "image_url": f"data:image/png;base64,{img_b64}"},
192
- {"type": "text", "text": prompt}
193
- ]
194
- })
195
-
196
- # Request
197
- response = client.chat.completions.create(
198
- model=selected["name"],
199
- messages=messages,
200
- temperature=0.7
201
- )
202
 
203
- return response.choices[0].message.content
204
 
205
  # ============================================================
206
- # UI GRADIO
207
  # ============================================================
208
 
209
  def create_ui():
210
  with gr.Blocks(theme="soft") as demo:
211
- gr.Markdown("## Multi-Modelo OpenRouter (Todos tus modelos activados)")
212
 
213
- with gr.Row():
214
- model_dropdown = gr.Dropdown(
215
  choices=list(MODELS.keys()),
216
- value="llama_3.1_70b",
217
  label="Modelo"
218
  )
 
 
 
 
 
 
 
 
 
 
219
 
220
- prompt = gr.Textbox(
221
- label="Mensaje",
222
- placeholder="Escribe tu mensaje…",
223
- lines=4
224
- )
225
-
226
- image_input = gr.Image(type="pil", label="Imagen opcional")
227
- chatbot = gr.Chatbot()
228
- send = gr.Button("Enviar")
229
-
230
- def respond(m, t, img, hist):
231
- output = generate(m, t, img, hist)
232
- hist.append((t, output))
233
- return hist, hist
234
-
235
- send.click(
236
- respond,
237
- inputs=[model_dropdown, prompt, image_input, chatbot],
238
- outputs=[chatbot, chatbot]
239
- )
240
 
241
  return demo
242
 
 
243
  demo = create_ui()
244
 
245
  if __name__ == "__main__":
246
- demo.launch()
 
 
 
 
1
  import os
2
+ import io
3
  import base64
4
+ import json
5
+ import requests
6
+ import tempfile
7
+ from PIL import Image
8
+ import gradio as gr
9
+ from huggingface_hub import InferenceClient
10
  from openai import OpenAI
11
 
12
  # ============================================================
13
+ # CONFIGURACIÓN
14
  # ============================================================
15
 
16
+ # SambaNova
17
+ SAMBA_API_KEY = os.getenv("REVE_API_KEY")
18
+ SAMBA_BASE_URL = "https://api.sambanova.ai/v1"
19
+
20
+ # OpenRouter
21
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
22
+ if not OPENROUTER_API_KEY:
23
  raise ValueError("Falta la variable OPENROUTER_API_KEY.")
24
 
25
+ openrouter_client = OpenAI(
26
  base_url="https://openrouter.ai/api/v1",
27
+ api_key=OPENROUTER_API_KEY
28
  )
29
 
30
+ # Hugging Face (para generación de imágenes)
31
+ HF_TOKEN = os.getenv("HF_TOKEN")
32
+
33
+ DEBUG = False # Ponlo en True si quieres ver el payload enviado a SambaNova
34
+
35
+
36
  # ============================================================
37
+ # TODOS LOS MODELOS (SAMBA + OPENROUTER) CON ROLES
38
  # ============================================================
39
 
40
  MODELS = {
41
+ # ===================== SAMBANOVA =====================
42
+
43
+ "general_fast": {
44
+ "provider": "sambanova",
45
+ "name": "Meta-Llama-3.1-8B-Instruct",
46
+ "role": "🔄 Respuestas rápidas y generales. Conversación ligera y eficiente.",
47
+ "supports_images": False
48
+ },
49
+ "general_smart": {
50
+ "provider": "sambanova",
51
+ "name": "Meta-Llama-3.3-70B-Instruct",
52
+ "role": "🧠 Razonamiento profundo. Análisis detallado y avanzado.",
53
+ "supports_images": False
54
+ },
55
+ "coding_expert": {
56
+ "provider": "sambanova",
57
+ "name": "DeepSeek-V3.1",
58
+ "role": "💻 Programación y debugging. Ideal para desarrollo.",
59
+ "supports_images": False
60
+ },
61
+ "coding_alt": {
62
+ "provider": "sambanova",
63
+ "name": "DeepSeek-V3-0324",
64
+ "role": "⚡ Código rápido. Alternativa veloz.",
65
+ "supports_images": False
66
+ },
67
+ "massive_brain": {
68
+ "provider": "sambanova",
69
+ "name": "gpt-oss-120b",
70
+ "role": "🏛️ Sabiduría masiva. Problemas pesados y complejos.",
71
+ "supports_images": False
72
+ },
73
+ "specialized_1": {
74
+ "provider": "sambanova",
75
+ "name": "DeepSeek-V3.1-Terminus",
76
+ "role": "🎯 Especialista técnico. Tareas científicas y avanzadas.",
77
+ "supports_images": False
78
+ },
79
+ "specialized_2": {
80
+ "provider": "sambanova",
81
+ "name": "Llama-3.3-Swallow-70B-Instruct-v0.4",
82
+ "role": "🔥 Sin censura. Modelo sin restricciones.",
83
+ "supports_images": False
84
+ },
85
+ "multilingual": {
86
+ "provider": "sambanova",
87
+ "name": "Qwen3-32B",
88
+ "role": "🌍 Multilingüe. Manejo de múltiples idiomas.",
89
+ "supports_images": False
90
+ },
91
+ "vision_expert": {
92
+ "provider": "sambanova",
93
+ "name": "Llama-4-Maverick-17B-128E-Instruct",
94
+ "role": "👁️ Visión avanzada. Análisis de imágenes.",
95
+ "supports_images": True
96
+ },
97
+ "boudoir_specialist": {
98
+ "provider": "sambanova",
99
+ "name": "ALLaM-7B-Instruct-preview",
100
+ "role": "🎭 Especialista en Fotografía Íntima Profesional. Experto en prompts para fotografía boudoir.",
101
+ "supports_images": False,
102
+ "specialties": [
103
+ "Fotografía Boudoir",
104
+ "Desnudo Artístico",
105
+ "Moda Sensual",
106
+ "Lencería y moda íntima"
107
+ ],
108
+ "technical_expertise": [
109
+ "Iluminación suave",
110
+ "Composición elegante",
111
+ "Dirección de poses",
112
+ "Edición fina",
113
+ "Escenografía íntima"
114
+ ],
115
+ "ethical_principles": [
116
+ "Consentimiento explícito",
117
+ "Positividad corporal"
118
+ ]
119
+ },
120
+
121
+ # ===================== OPENROUTER =====================
122
+
123
+ # META LLAMA
124
  "llama_3.1_70b": {
125
+ "provider": "openrouter",
126
  "name": "meta-llama/llama-3.1-70b-instruct",
127
+ "role": "Respondes con precisión técnica y claridad.",
128
+ "supports_images": False
129
  },
130
  "llama_3.1_405b": {
131
+ "provider": "openrouter",
132
  "name": "meta-llama/llama-3.1-405b-instruct",
133
+ "role": "Eres experto en programación, ciencia y análisis avanzado.",
134
+ "supports_images": False
135
  },
136
 
137
+ # LLAMA 3.2 VISION
138
  "llama_3.2_11b_vision": {
139
+ "provider": "openrouter",
140
  "name": "meta-llama/llama-3.2-11b-vision-instruct",
141
+ "role": "Eres un modelo experto en análisis visual detallado.",
142
+ "supports_images": True
143
  },
144
  "llama_3.2_90b_vision": {
145
+ "provider": "openrouter",
146
  "name": "meta-llama/llama-3.2-90b-vision-instruct",
147
+ "role": "Eres un analista visual avanzado altamente preciso.",
148
+ "supports_images": True
149
  },
150
 
151
+ # QWEN
152
  "qwen_72b": {
153
+ "provider": "openrouter",
154
  "name": "qwen/qwen2.5-72b-instruct",
155
+ "role": "Respondes de forma profesional, directa y clara.",
156
+ "supports_images": False
157
  },
158
  "qwen_110b": {
159
+ "provider": "openrouter",
160
  "name": "qwen/qwen2.5-110b-instruct",
161
+ "role": "Asistente experto en razonamiento estructurado.",
162
+ "supports_images": False
163
  },
164
 
165
+ # GPT / OPENAI
166
  "gpt_4.1": {
167
+ "provider": "openrouter",
168
  "name": "openai/gpt-4.1",
169
+ "role": "Asistente avanzado para cualquier tarea general.",
170
+ "supports_images": False
171
  },
172
  "gpt_4.1_mini": {
173
+ "provider": "openrouter",
174
  "name": "openai/gpt-4.1-mini",
175
+ "role": "Modelo rápido y eficiente, ideal para respuestas concisas.",
176
+ "supports_images": False
177
  },
178
  "gpt_4o_mini": {
179
+ "provider": "openrouter",
180
  "name": "openai/gpt-4o-mini",
181
+ "role": "Asistente veloz con buena comprensión general.",
182
+ "supports_images": False
183
  },
184
 
185
+ # CLAUDE
186
  "claude_3.5_sonnet": {
187
+ "provider": "openrouter",
188
  "name": "anthropic/claude-3.5-sonnet",
189
+ "role": "Especialista en redacción, precisión y análisis profundo.",
190
+ "supports_images": False
191
  },
192
  "claude_3.5_haiku": {
193
+ "provider": "openrouter",
194
  "name": "anthropic/claude-3.5-haiku",
195
+ "role": "Modelo rápido con buena comprensión general.",
196
+ "supports_images": False
197
  },
198
  "claude_3_opus": {
199
+ "provider": "openrouter",
200
  "name": "anthropic/claude-3-opus",
201
+ "role": "Máxima capacidad de análisis y lenguaje.",
202
+ "supports_images": False
203
  },
204
 
205
+ # GOOGLE GEMINI
206
  "gemini_flash": {
207
+ "provider": "openrouter",
208
  "name": "google/gemini-flash-1.5",
209
+ "role": "Especialista en escenarios visuales y respuestas rápidas.",
210
+ "supports_images": True
211
  },
212
  "gemini_pro": {
213
+ "provider": "openrouter",
214
  "name": "google/gemini-pro-1.5",
215
+ "role": "Razonador general robusto y flexible.",
216
+ "supports_images": True
217
  },
218
  "gemini_thinking": {
219
+ "provider": "openrouter",
220
  "name": "google/gemini-1.5-thinking",
221
+ "role": "Modelo de razonamiento profundo y detallado.",
222
+ "supports_images": True
223
  },
224
 
225
+ # DEEPSEEK
226
  "deepseek_r1": {
227
+ "provider": "openrouter",
228
  "name": "deepseek/deepseek-r1",
229
+ "role": "Razonamiento profundo y cadena de pensamiento estructurada.",
230
+ "supports_images": False
231
  },
232
 
233
+ # MISTRAL
234
  "mistral_large": {
235
+ "provider": "openrouter",
236
  "name": "mistral/mistral-large-latest",
237
+ "role": "Asistente técnico avanzado y preciso.",
238
+ "supports_images": False
239
  },
240
  "mixtral_8x7b": {
241
+ "provider": "openrouter",
242
  "name": "mistral/mixtral-8x7b-instruct",
243
+ "role": "Modelo eficiente para tareas complejas sin alto costo.",
244
+ "supports_images": False
245
  },
246
 
247
+ # REKA
248
  "reka_core": {
249
+ "provider": "openrouter",
250
  "name": "reka/core",
251
+ "role": "Asistente racional y estructurado.",
252
+ "supports_images": False
253
  },
254
 
255
+ # SAMBANOVA EN OPENROUTER
256
  "samba_allam_7b": {
257
+ "provider": "openrouter",
258
  "name": "sambanova/ALLAM-1-7B",
259
+ "role": "Asistente optimizado para rendimiento y claridad.",
260
+ "supports_images": False
261
  },
262
 
263
+ # FLUX (generación de imágenes, lo dejamos como texto aquí)
264
  "flux_pro": {
265
+ "provider": "openrouter",
266
  "name": "black-forest-labs/flux-1.1-pro",
267
+ "role": "Experto en generación y análisis de imágenes.",
268
+ "supports_images": True
269
  }
270
  }
271
 
272
+
273
  # ============================================================
274
+ # HELPERS
275
  # ============================================================
276
 
277
+ def encode_image_to_base64(image):
278
+ if image is None:
279
+ return None
280
+ buf = io.BytesIO()
281
+ image.save(buf, format="PNG")
282
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
283
+
284
+
285
+ def build_messages(system_prompt, user_input, history, image_b64, supports_images):
286
+ """
287
+ Construye una lista de mensajes válida para ambas APIs:
288
+ - Siempre una lista de diccionarios {role, content}
289
+ - content es string, excepto cuando se envía imagen (lista de bloques)
290
+ """
291
  messages = []
292
 
 
293
  if system_prompt:
294
  messages.append({"role": "system", "content": system_prompt})
295
 
296
+ # Historial en formato (usuario, asistente)
297
+ for item in history or []:
298
+ if not isinstance(item, (list, tuple)) or len(item) != 2:
299
+ continue
300
+ user_msg, assistant_msg = item
301
+
302
+ if isinstance(user_msg, str) and user_msg.strip():
303
+ messages.append({"role": "user", "content": user_msg})
304
+
305
+ if isinstance(assistant_msg, str) and assistant_msg.strip():
306
+ messages.append({"role": "assistant", "content": assistant_msg})
307
+
308
+ # Mensaje actual
309
+ if image_b64 and supports_images:
310
+ # Formato compatible con APIs tipo OpenAI/OpenRouter/SambaNova para visión
311
+ content = [
312
+ {"type": "text", "text": user_input or ""},
313
+ {
314
+ "type": "image_url",
315
+ "image_url": {
316
+ "url": f"data:image/png;base64,{image_b64}"
317
+ }
318
+ }
319
+ ]
320
+ messages.append({"role": "user", "content": content})
321
+ else:
322
+ messages.append({"role": "user", "content": user_input or ""})
323
 
324
+ return messages
325
 
 
 
326
 
327
+ # ============================================================
328
+ # LLAMADAS A LOS MODELOS
329
+ # ============================================================
330
 
331
+ def call_sambanova(model_name, messages):
332
+ payload = {
333
+ "model": model_name,
334
+ "messages": messages,
335
+ "stream": False
336
+ }
337
+
338
+ if DEBUG:
339
+ # Para depurar formato de mensajes
340
+ print(json.dumps(payload, indent=2))
341
+
342
+ headers = {
343
+ "Authorization": f"Bearer {SAMBA_API_KEY}",
344
+ "Content-Type": "application/json"
345
+ }
346
+
347
+ r = requests.post(
348
+ f"{SAMBA_BASE_URL}/chat/completions",
349
+ json=payload,
350
+ headers=headers,
351
+ timeout=60
352
+ )
353
+
354
+ try:
355
+ data = r.json()
356
+ return data["choices"][0]["message"]["content"]
357
+ except Exception:
358
+ return "Respuesta inesperada de SambaNova:\n" + r.text
359
+
360
+
361
+ def call_openrouter(model_name, messages):
362
+ response = openrouter_client.chat.completions.create(
363
+ model=model_name,
364
+ messages=messages,
365
+ temperature=0.7
366
+ )
367
+ # El contenido siempre se devuelve como string en esta configuración
368
+ return response.choices[0].message.content
369
 
 
370
 
371
  # ============================================================
372
+ # LÓGICA DEL CHAT
373
  # ============================================================
374
 
375
+ def chat_logic(user_text, user_image, model_key, history):
376
+ if history is None:
377
+ history = []
378
+
379
+ if model_key not in MODELS:
380
+ reply = "Error: modelo no encontrado."
381
+ history.append((user_text or "", reply))
382
+ return history, history
383
+
384
+ model_cfg = MODELS[model_key]
385
+
386
+ image_b64 = encode_image_to_base64(user_image) if user_image else None
387
+
388
+ messages = build_messages(
389
+ system_prompt=model_cfg["role"],
390
+ user_input=user_text,
391
+ history=history,
392
+ image_b64=image_b64,
393
+ supports_images=model_cfg.get("supports_images", False)
394
+ )
395
+
396
+ if model_cfg["provider"] == "sambanova":
397
+ reply = call_sambanova(model_cfg["name"], messages)
398
+ else:
399
+ reply = call_openrouter(model_cfg["name"], messages)
400
+
401
+ history.append((user_text or "[Imagen]", reply))
402
+ return history, history
403
+
404
 
405
  # ============================================================
406
+ # GENERACIÓN DE IMÁGENES (HUGGING FACE, OPCIONAL)
407
  # ============================================================
408
 
409
+ def generate_image_hf(prompt):
410
+ if not HF_TOKEN:
411
+ return None, "❌ Falta HF_TOKEN", gr.update(visible=False)
412
+
413
+ try:
414
+ client = InferenceClient(token=HF_TOKEN)
415
+ img = client.text_to_image(
416
+ prompt,
417
+ model="stabilityai/stable-diffusion-xl-base-1.0"
418
+ )
419
+
420
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
421
+ img.save(tmp)
422
+ return img, " Imagen generada", gr.update(value=tmp.name, visible=True)
423
+
424
+ except Exception as e:
425
+ return None, f"❌ Error: {e}", gr.update(visible=False)
 
 
 
 
 
 
 
 
 
426
 
 
427
 
428
  # ============================================================
429
+ # UI GRADIO UNIFICADA
430
  # ============================================================
431
 
432
  def create_ui():
433
  with gr.Blocks(theme="soft") as demo:
434
+ gr.Markdown("## 🤖 Chat Multimodelo (SambaNova + OpenRouter)")
435
 
436
+ with gr.Tab("Chat"):
437
+ model_sel = gr.Dropdown(
438
  choices=list(MODELS.keys()),
439
+ value="general_fast",
440
  label="Modelo"
441
  )
442
+ chat = gr.Chatbot(height=420, label="Conversación")
443
+ img = gr.Image(type="pil", label="Imagen opcional")
444
+ txt = gr.Textbox(label="Mensaje", placeholder="Escribe tu mensaje…", lines=4)
445
+ send = gr.Button("Enviar")
446
+
447
+ send.click(
448
+ chat_logic,
449
+ inputs=[txt, img, model_sel, chat],
450
+ outputs=[chat, chat]
451
+ )
452
 
453
+ with gr.Tab("Imágenes (HF)"):
454
+ gr.Markdown("Generación de imágenes con Stable Diffusion XL (Hugging Face).")
455
+ p = gr.Textbox(label="Prompt para la imagen")
456
+ b = gr.Button("Generar")
457
+ out = gr.Image(label="Resultado")
458
+ d = gr.DownloadButton("Descargar", visible=False)
459
+ status = gr.Textbox(label="Estado", interactive=False)
460
+ b.click(generate_image_hf, inputs=[p], outputs=[out, status, d])
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
  return demo
463
 
464
+
465
  demo = create_ui()
466
 
467
  if __name__ == "__main__":
468
+ # Asegúrate de tener instalado el paquete openai:
469
+ # pip install openai
470
+ demo.launch()
471
+