ivanoctaviogaitansantos commited on
Commit
9c7c393
·
verified ·
1 Parent(s): a983381

Actualizar app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -278
app.py CHANGED
@@ -2,118 +2,11 @@ import gradio as gr
2
  import requests
3
  import json
4
  import os
5
- import random
6
  import base64
7
  import io
8
  from PIL import Image
9
- from typing import List, Optional
10
-
11
- # ==============================================================
12
- # CLASE PRINCIPAL: GENERADOR DE PROMPTS HIPERREALISTAS
13
- # ==============================================================
14
-
15
- class HyperrealisticPromptGenerator:
16
- def __init__(self):
17
- self.ROLES = [
18
- "nurse", "nun", "maid", "flight attendant", "secretary", "teacher", "schoolgirl", "lawyer",
19
- "doctor", "boudoir model", "fitness model", "elegant judge", "seductive librarian",
20
- "business executive", "policewoman", "female military officer", "WWII-era secretary",
21
- "1960s flight attendant", "seductive maid", "mysterious nurse", "captivating schoolgirl"
22
- ]
23
- self.AGES = [
24
- "early 20s youthful vibrance",
25
- "mid 20s graceful confidence",
26
- "late 20s elegant maturity"
27
- ]
28
- self.HAIR_COLORS = [
29
- "deep sapphire blue", "silver platinum", "vibrant ruby red", "glossy jet black",
30
- "luxurious chestnut brown", "emerald green", "vivid amethyst purple",
31
- "chocolate brown", "honey blonde", "burgundy red"
32
- ]
33
- self.EYE_COLORS = [
34
- "intense brown", "bright sapphire blue", "emerald green", "golden amber",
35
- "fascinating hazel", "deep violet", "piercing emerald", "mysterious gray",
36
- "vibrant violet", "intense amber"
37
- ]
38
- self.HAIR_STYLES = [
39
- "long flowing chestnut hair styled in soft waves",
40
- "sleek straight long black hair",
41
- "luxurious long blonde curls",
42
- "elegant updo with loose cascading strands",
43
- "voluminous curls with side part",
44
- "thick braid over the shoulder",
45
- "loose and silky layers",
46
- "messy chic bun"
47
- ]
48
- self.POSES = [
49
- "standing with one leg slightly forward, natural elegance",
50
- "seated on a chair edge, legs crossed, professional expression",
51
- "leaning against a desk, confident look",
52
- "walking with subtle grace, light movement",
53
- "adjusting hair gently, natural body language"
54
- ]
55
- self.SETTINGS = [
56
- "modern office with elegant decor and warm ambient light",
57
- "luxury hotel suite with velvet furnishings and city view",
58
- "classic library with wooden shelves and soft reading lamps",
59
- "outdoor balcony at sunset with urban skyline",
60
- "high-end photo studio with professional soft lighting"
61
- ]
62
- self.ATMOSPHERES = [
63
- "soft professional lighting with smooth skin shadows, perfect color balance",
64
- "warm golden hour sunlight creating rich highlights and depth",
65
- "moody cinematic lighting with subtle shadow play",
66
- "gentle romantic candlelight with warm glows",
67
- "sharp studio flash lighting with balanced illumination"
68
- ]
69
- self.TECHNICAL_DETAILS = (
70
- "Captured in ultra HD 16K (15360×8640) vertical 9:16 full body format. "
71
- "Canon EOS R5 Cine RAW camera and Canon RF 85mm f/1.2L USM lens at f/1.2 aperture for creamy bokeh. "
72
- "ARRI SkyPanel S360-C soft lighting, Path Tracing, PBR, SSS for lifelike skin, and Ray Tracing. "
73
- "Photogrammetry-based textures, displacement maps for skin pores, delicate fabric weave. "
74
- "Natural hair strand flow, low-angle (knee to head) composition."
75
- )
76
- self.CONDITION_FIXED = (
77
- "Wearing elegant professional attire matching the role, natural posture, confident expression. "
78
- "Full body portrait, cinematic tone, vertical 9:16 framing."
79
- )
80
-
81
- def _choose_random(self, options: List[str]) -> str:
82
- return random.choice(options)
83
-
84
- def generate_single_prompt(self, role: Optional[str] = None) -> str:
85
- selected_role = role if role else self._choose_random(self.ROLES)
86
- age = self._choose_random(self.AGES)
87
- hair_color = self._choose_random(self.HAIR_COLORS)
88
- eye_color = self._choose_random(self.EYE_COLORS)
89
- hair_style = self._choose_random(self.HAIR_STYLES)
90
- pose = self._choose_random(self.POSES)
91
- setting = self._choose_random(self.SETTINGS)
92
- atmosphere = self._choose_random(self.ATMOSPHERES)
93
-
94
- prompt = f"""
95
- Highly detailed hyperrealistic full-body portrait of a {selected_role}, {age}, with {hair_style}, {hair_color} hair, and {eye_color} eyes.
96
- She is {pose}, in a {setting}. {atmosphere}.
97
- {self.CONDITION_FIXED}
98
- {self.TECHNICAL_DETAILS}
99
- """
100
- return prompt.strip()
101
-
102
- def generate_prompt_automatic(self):
103
- return self.generate_single_prompt(), ""
104
-
105
- # ==============================================================
106
- # SAMBANOVA API CONFIG
107
- # ==============================================================
108
-
109
- gen = HyperrealisticPromptGenerator()
110
-
111
- # Asegúrate de configurar SAMBANOVA_API_KEY en tu entorno
112
- API_KEY = os.getenv("SAMBANOVA_API_KEY")
113
- API_URL = "https://api.sambanova.ai/v1/chat/completions"
114
- headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
115
-
116
 
 
117
  def process_image(image):
118
  if image is None:
119
  return None
@@ -121,12 +14,13 @@ def process_image(image):
121
  image.save(buffered, format="PNG")
122
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
123
 
124
-
125
  def analizar_imagen_y_generar_prompt(image_base64):
 
 
 
126
  if not API_KEY:
127
- return "```\n⚠️ SAMBANOVA_API_KEY no configurada.\n```"
128
-
129
- # Formato de contenido para el análisis de imagen (puede variar)
130
  messages = [
131
  {"role": "system", "content": "Describe images in detailed English."},
132
  {
@@ -137,178 +31,43 @@ def analizar_imagen_y_generar_prompt(image_base64):
137
  ]
138
  }
139
  ]
140
-
141
  json_data = {"model": "Llama-4-Maverick-17B-128E-Instruct", "messages": messages, "stream": False}
142
  try:
143
  response = requests.post(API_URL, headers=headers, json=json_data)
144
  response.raise_for_status()
145
  text_resp = response.json()["choices"][0]["message"]["content"]
146
- return f"```\n{text_resp}\n```"
147
- except Exception as e:
148
- return f"```\nError analizando imagen: {str(e)}\n```"
149
-
150
-
151
- # ==============================================================
152
- # FUNCIÓN PRINCIPAL DE CHAT Y PROMPT (CORREGIDA)
153
- # ==============================================================
154
-
155
- def chat_sambanova(user_message, image_input, auto_mode, chat_history):
156
- updated_history = chat_history[:] if chat_history else []
157
- image_base64 = process_image(image_input) if image_input else None
158
-
159
- # 1. Yield inicial para mostrar "Procesando..." y borrar la entrada del usuario
160
- yield "", updated_history, "", "Procesando..."
161
-
162
- if not API_KEY:
163
- error_msg = "Error: SAMBANOVA_API_KEY no configurada."
164
- updated_history.append((user_message, error_msg))
165
- # 2. Yield de error
166
- yield user_message, updated_history, error_msg, ""
167
- return
168
-
169
- if auto_mode and image_base64:
170
- prompt = analizar_imagen_y_generar_prompt(image_base64)
171
- updated_history.append((user_message or "Análisis automático (Imagen)", f"IA - Prompt generado:\n{prompt}"))
172
- # 3. Yield de finalización de análisis
173
- yield "", updated_history, "", ""
174
- return
175
-
176
- # Preparar el historial de chat para la API
177
- messages = [{"role": "system", "content": "Eres un asistente útil"}]
178
- for user_msg, ai_msg in updated_history:
179
- # Los mensajes del historial deben ser solo de texto para el rol 'user'
180
- # Esto previene errores de formato en mensajes pasados.
181
- messages.append({"role": "user", "content": [{"type": "text", "text": user_msg}]})
182
- messages.append({"role": "assistant", "content": ai_msg})
183
-
184
- # Preparar el mensaje actual (puede ser texto o multimodal)
185
- user_content = [{"type": "text", "text": user_message}]
186
- if image_base64:
187
- user_content.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}})
188
- messages.append({"role": "user", "content": user_content})
189
-
190
- json_data = {"model": "Llama-4-Maverick-17B-128E-Instruct", "messages": messages, "stream": True}
191
-
192
- try:
193
- response = requests.post(API_URL, headers=headers, json=json_data, stream=True)
194
- response.raise_for_status()
195
- collected_text = ""
196
- updated_history.append((user_message, ""))
197
-
198
- for line in response.iter_lines(decode_unicode=True):
199
- if line.startswith("data: "):
200
- json_str = line[len("data: "):]
201
- if json_str == "[DONE]":
202
- break
203
- try:
204
- data = json.loads(json_str)
205
- delta = data.get("choices", [{}])[0].get("delta", {})
206
- text_fragment = delta.get("content", "")
207
- collected_text += text_fragment
208
- updated_history[-1] = (user_message, collected_text)
209
- # 4. Yield de streaming
210
- yield "", updated_history, "", "Procesando..."
211
- except json.JSONDecodeError:
212
- continue
213
- # 5. Yield final
214
- yield "", updated_history, "", ""
215
  except Exception as e:
216
- error_msg = f"Error inesperado de la API: {str(e)}"
217
- if updated_history and updated_history[-1][1] == "":
218
- updated_history[-1] = (user_message, error_msg)
219
- else:
220
- updated_history.append((user_message, error_msg))
221
- # 6. Yield de error final
222
- yield user_message, updated_history, error_msg, ""
223
-
224
- def generar_prompt_interno():
225
- prompt, _ = gen.generate_single_prompt(), ""
226
- # Retorna el prompt como texto de salida y una cadena vacía para el display de error
227
- return prompt, ""
228
-
229
- # ==============================================================
230
- # INTERFAZ GRADIO (CORREGIDA)
231
- # ==============================================================
232
-
233
- css_batuto = """
234
- body {background-color: #05070A; color: #B0C8FF; font-family: 'Poppins', sans-serif;}
235
- h1, h2, h3, h4 {color: #5CA8FF; text-align: center;}
236
- .gradio-container {background-color: #05070A !important;}
237
- button {background-color: #0B1A33 !important; color: #B0C8FF !important; border-radius: 12px;}
238
- button:hover {background-color: #1B335F !important;}
239
- .prompt-output {background-color: #0A0F1A; color: #A8CFFF; border-radius: 10px; padding: 10px;}
240
- input, textarea {background-color: #0B101A !important; color: #DDE8FF !important;}
241
- """
242
-
243
- with gr.Blocks(css=css_batuto, theme=gr.themes.Soft()) as demo:
244
- gr.Markdown("# ⚡ BATUTO / Prompt Studio — Hyperrealistic Generator")
245
-
246
- chat_history = gr.State([])
247
- error_display = gr.Textbox(label="System messages", value="", visible=True, interactive=False)
248
-
249
- chatbot = gr.Chatbot(label="💬 BATUTO Assistant (SambaNova - Llama-4 Maverick)", type='messages')
250
- # Cambiado a Textbox para mejor funcionalidad de copia.
251
- prompt_output = gr.Textbox(label="🎨 Prompt generado", elem_classes=["prompt-output"], lines=5, max_lines=10)
252
-
253
- with gr.Row():
254
- # La salida msg se usa para limpiar la caja de texto después del envío
255
- msg = gr.Textbox(label="Tu mensaje", scale=4, placeholder="Escribe tu mensaje o usa el modo automático...")
256
- img_input = gr.Image(label="Sube una imagen (opcional)", type="pil", scale=2)
257
-
258
- with gr.Row():
259
- auto_mode = gr.Checkbox(label="Modo automático (Generar prompt desde imagen)", value=False)
260
- btn_send = gr.Button("Enviar mensaje", variant="primary")
261
- btn_gen_prompt = gr.Button("🎲 Generar prompt automático", variant="secondary")
262
- copy_button = gr.Button("📋 Copiar Prompt")
263
-
264
- # Componente para mostrar el estado de carga (Procesando...)
265
- loading_state = gr.Textbox(value="", label="Estado", interactive=False)
266
-
267
- # Asignación de outputs corregida para coincidir con la función chat_sambanova
268
- btn_send.click(
269
- fn=chat_sambanova,
270
- inputs=[msg, img_input, auto_mode, chat_history],
271
- outputs=[msg, chatbot, error_display, loading_state]
272
- )
273
-
274
- msg.submit(
275
- fn=chat_sambanova,
276
- inputs=[msg, img_input, auto_mode, chat_history],
277
- outputs=[msg, chatbot, error_display, loading_state]
278
- )
279
-
280
- # La salida del prompt va al Textbox y la segunda salida es para limpiar errores
281
- btn_gen_prompt.click(
282
- fn=generar_prompt_interno,
283
- inputs=[],
284
- outputs=[prompt_output, error_display]
285
- )
286
-
287
- # CORRECCIÓN DE ERROR: Cambiado _js a js
288
- copy_button.click(
289
- None,
290
- [],
291
- [],
292
- js="""() => {
293
- // Usa el ID o selector de clase del Textbox
294
- const promptBox = document.querySelector('.prompt-output textarea');
295
- const promptText = promptBox ? promptBox.value : '';
296
-
297
- if (promptText) {
298
- navigator.clipboard.writeText(promptText).then(() => {
299
- alert('✅ Prompt copiado al portapapeles');
300
- }).catch(err => {
301
- alert('❌ Error al copiar: ' + err);
302
- });
303
- } else {
304
- alert('❌ No se encontró el prompt para copiar. Genera uno primero.');
305
- }
306
- }"""
307
  )
308
 
309
  if __name__ == "__main__":
310
- try:
311
- demo.launch()
312
- except Exception as e:
313
- print(f"Error al iniciar Gradio: {str(e)}")
314
-
 
2
  import requests
3
  import json
4
  import os
 
5
  import base64
6
  import io
7
  from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ # Procesar imagen a base64
10
  def process_image(image):
11
  if image is None:
12
  return None
 
14
  image.save(buffered, format="PNG")
15
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
16
 
17
+ # Función para llamar a la API Sambanova para análisis de imagen + texto
18
  def analizar_imagen_y_generar_prompt(image_base64):
19
+ API_KEY = os.getenv("SAMBANOVA_API_KEY")
20
+ API_URL = "https://api.sambanova.ai/v1/chat/completions"
21
+ headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
22
  if not API_KEY:
23
+ return "⚠️ SAMBANOVA_API_KEY no configurada."
 
 
24
  messages = [
25
  {"role": "system", "content": "Describe images in detailed English."},
26
  {
 
31
  ]
32
  }
33
  ]
 
34
  json_data = {"model": "Llama-4-Maverick-17B-128E-Instruct", "messages": messages, "stream": False}
35
  try:
36
  response = requests.post(API_URL, headers=headers, json=json_data)
37
  response.raise_for_status()
38
  text_resp = response.json()["choices"][0]["message"]["content"]
39
+ return text_resp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  except Exception as e:
41
+ return f"Error al analizar imagen: {str(e)}"
42
+
43
+ # Función principal del chat dinámico
44
+ def chat_mode(user_message, image_input, chat_history, auto_mode):
45
+ chat_history = chat_history or []
46
+ image_b64 = process_image(image_input) if image_input else None
47
+ chat_history.append(("Usuario", user_message))
48
+ if auto_mode and image_b64:
49
+ respuesta = analizar_imagen_y_generar_prompt(image_b64)
50
+ else:
51
+ respuesta = "Mensaje recibido: " + user_message
52
+ chat_history.append(("IA", respuesta))
53
+ return chat_history
54
+
55
+ # Interfaz Gradio chat dinámica
56
+ with gr.Blocks() as demo:
57
+ gr.Markdown("# ⚡ BATUTO / Prompt Studio — Chat IA + Imagen")
58
+ chat_box = gr.Chatbot()
59
+ msg_input = gr.Textbox(placeholder="Escribe tu mensaje...")
60
+ upload_img = gr.Image(label="Sube una imagen (opcional)", type="pil")
61
+ auto_mode = gr.Checkbox(label="Modo automático: analizar imagen para prompt", value=False)
62
+ send_button = gr.Button("Enviar")
63
+ chat_state = gr.State([])
64
+
65
+ send_button.click(
66
+ chat_mode,
67
+ inputs=[msg_input, upload_img, chat_state, auto_mode],
68
+ outputs=[chat_box, chat_state]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  )
70
 
71
  if __name__ == "__main__":
72
+ demo.launch()
73
+