ivanoctaviogaitansantos commited on
Commit
653a30c
·
verified ·
1 Parent(s): eddb12f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -73
app.py CHANGED
@@ -1,73 +1,209 @@
1
- # 🐾 BATUTO_encicloPromt🐾
2
-
3
- **BATUTO_encicloPromt** es un gestor visual y clasificador automático de prompts, diseñado para creadores que buscan organizar, explorar y compartir sus prompts de forma profesional.
4
- Incluye un **modo cliente** (solo lectura) y un **modo admin** protegido por contraseña para agregar, editar o eliminar contenido.
5
-
6
- ---
7
-
8
- ## ✨ Características
9
-
10
- - **Clasificación automática**: Detecta si un prompt es de **Personajes** o **Fotografía profesional** según palabras clave.
11
- - **Dos espacios separados**:
12
- - 📂 **Personajes**: prompts de diseño de personajes, criaturas, retratos, etc.
13
- - 📂 **Fotografía profesional**: prompts de producto, moda, macro, editorial, etc.
14
- - **Modo cliente**:
15
- - Visualiza todos los prompts por categoría.
16
- - Copia todos los prompts de una categoría con un clic.
17
- - Descarga la categoría completa en formato `.json`.
18
- - **Modo admin** (protegido por contraseña):
19
- - Agregar prompts uno por uno o en lista masiva.
20
- - Editar o eliminar prompts existentes.
21
- - Clasificación automática al agregar.
22
- - **Persistencia local** en `prompts.json` para mantener los datos entre sesiones.
23
- - **Interfaz amigable** construida con [Gradio](https://gradio.app/).
24
-
25
- ---
26
-
27
- ## 📂 Estructura del proyecto
28
-
29
- | Archivo / Carpeta | Descripción |
30
- |-------------------|-------------|
31
- | `app.py` | Código principal de la aplicación Gradio. Contiene la lógica de clasificación, gestión de prompts y la interfaz. |
32
- | `requirements.txt` | Lista de dependencias necesarias para ejecutar la app en Hugging Face Spaces. |
33
- | `prompts.json` | Base de datos local en formato JSON con todos los prompts clasificados. |
34
- | `README.md` | Este documento de presentación y guía de uso. |
35
- | `LICENSE` *(opcional)* | Licencia del proyecto, define los términos de uso. |
36
-
37
- ---
38
-
39
- ## 🚀 Despliegue en Hugging Face Spaces
40
-
41
- 1. **Crear un Space** en [Hugging Face](https://huggingface.co/spaces):
42
- - Tipo: **Gradio**.
43
- - Licencia: la que prefieras.
44
- 2. **Subir archivos**: `app.py`, `requirements.txt`, `README.md` y opcionalmente `LICENSE`.
45
- 3. **Configurar Secrets** (en *Settings → Repository secrets*):
46
- - `ADMIN_PASSWORD`: tu contraseña para el modo admin.
47
- 4. **Guardar y construir** el Space.
48
- Hugging Face instalará automáticamente las dependencias y lanzará la app.
49
-
50
- ---
51
-
52
- ## 🔑 Uso del modo admin
53
-
54
- 1. En la interfaz, busca la sección **Acceso admin**.
55
- 2. Ingresa la contraseña configurada en `ADMIN_PASSWORD`.
56
- 3. Una vez validada, se desbloquearán las herramientas para:
57
- - Agregar prompts (individual o lista).
58
- - Editar o eliminar existentes.
59
- 4. Los cambios se guardan automáticamente en `prompts.json`.
60
-
61
- ---
62
-
63
- ## 👥 Uso del modo cliente
64
-
65
- - **Explorar**: Navega por las categorías Personajes y Fotografía profesional.
66
- - **Copiar todos**: Botón para copiar todos los prompts de una categoría.
67
- - **Descargar JSON**: Botón para descargar la categoría completa.
68
-
69
- ---
70
-
71
- ## 📌 Ejemplo de prompt
72
-
73
- **Categoría:** Personajes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 🐾 BATUTO_encicloPromt🐾
4
+ Gestor de prompts con interfaz Gradio para Hugging Face Spaces
5
+ - Clasificación automática: Personajes / Fotografía profesional
6
+ - Modo admin protegido por contraseña (ADMIN_PASSWORD en Secrets)
7
+ - Modo cliente: solo visual, copiar y descargar categorías
8
+ """
9
+
10
+ import os
11
+ import json
12
+ from datetime import datetime
13
+ from io import StringIO
14
+ import tempfile
15
+ import gradio as gr
16
+
17
+ DATA_FILE = "prompts.json"
18
+
19
+ # ---------------------------
20
+ # Núcleo de datos y lógica
21
+ # ---------------------------
22
+
23
+ class GestorPrompts:
24
+ def __init__(self):
25
+ self.personajes = {}
26
+ self.fotografia = {}
27
+
28
+ self.keywords_personajes = [
29
+ "character", "personaje", "humanoid", "guardian", "portrait", "retrato",
30
+ "warrior", "creature", "avatar", "hero", "villain", "body", "full body",
31
+ "pose", "anatomy", "armor", "criatura"
32
+ ]
33
+ self.keywords_fotografia = [
34
+ "photography", "foto", "bokeh", "macro", "studio", "dslr",
35
+ "aperture", "shutter", "lens", "professional", "portrait photography",
36
+ "product", "still life", "food", "fashion", "editorial"
37
+ ]
38
+
39
+ def cargar(self, ruta=DATA_FILE):
40
+ if os.path.exists(ruta):
41
+ with open(ruta, "r", encoding="utf-8") as f:
42
+ data = json.load(f)
43
+ self.personajes = data.get("personajes", {})
44
+ self.fotografia = data.get("fotografia", {})
45
+ return self
46
+
47
+ def guardar(self, ruta=DATA_FILE):
48
+ data = {"personajes": self.personajes, "fotografia": self.fotografia}
49
+ with open(ruta, "w", encoding="utf-8") as f:
50
+ json.dump(data, f, ensure_ascii=False, indent=2)
51
+
52
+ def clasificar(self, texto: str) -> str:
53
+ t = texto.lower()
54
+ if any(k in t for k in self.keywords_personajes):
55
+ return "personajes"
56
+ if any(k in t for k in self.keywords_fotografia):
57
+ return "fotografia"
58
+ return "fotografia"
59
+
60
+ def agregar(self, nombre: str, texto: str, tags=None):
61
+ categoria = self.clasificar(texto)
62
+ entrada = {
63
+ "texto": texto.strip(),
64
+ "fecha": datetime.now().isoformat(timespec="seconds"),
65
+ "tags": tags or []
66
+ }
67
+ if categoria == "personajes":
68
+ self.personajes[nombre] = entrada
69
+ else:
70
+ self.fotografia[nombre] = entrada
71
+ return categoria
72
+
73
+ def agregar_lista(self, lista_textos):
74
+ resultados = []
75
+ for i, texto in enumerate(lista_textos, start=1):
76
+ if not texto.strip():
77
+ continue
78
+ nombre = f"Prompt_{i}"
79
+ cat = self.agregar(nombre, texto)
80
+ resultados.append((nombre, cat))
81
+ return resultados
82
+
83
+ def obtener_categoria(self, categoria: str):
84
+ return self.personajes if categoria == "personajes" else self.fotografia
85
+
86
+ def eliminar(self, categoria: str, nombre: str) -> bool:
87
+ coleccion = self.obtener_categoria(categoria)
88
+ if nombre in coleccion:
89
+ del coleccion[nombre]
90
+ return True
91
+ return False
92
+
93
+ def actualizar(self, categoria: str, nombre: str, nuevo_texto: str = None, nuevos_tags=None) -> bool:
94
+ coleccion = self.obtener_categoria(categoria)
95
+ if nombre not in coleccion:
96
+ return False
97
+ if nuevo_texto is not None:
98
+ coleccion[nombre]["texto"] = nuevo_texto.strip()
99
+ if nuevos_tags is not None:
100
+ coleccion[nombre]["tags"] = nuevos_tags
101
+ return True
102
+
103
+ def buscar(self, query: str, categoria: str = "todas"):
104
+ q = query.strip().lower()
105
+ res = []
106
+ cats = ["personajes", "fotografia"] if categoria == "todas" else [categoria]
107
+ for c in cats:
108
+ col = self.personajes if c == "personajes" else self.fotografia
109
+ for nombre, data in col.items():
110
+ if q in nombre.lower() or q in data["texto"].lower() or any(q in t.lower() for t in data.get("tags", [])):
111
+ res.append((c, nombre, data["texto"]))
112
+ return res
113
+
114
+ def exportar_categoria_json_bytes(self, categoria: str) -> bytes:
115
+ data = self.obtener_categoria(categoria)
116
+ si = StringIO()
117
+ json.dump(data, si, ensure_ascii=False, indent=2)
118
+ return si.getvalue().encode("utf-8")
119
+
120
+ def concatenar_prompts_categoria(self, categoria: str) -> str:
121
+ data = self.obtener_categoria(categoria)
122
+ bloques = [f"# {nombre}\n{info['texto']}" for nombre, info in data.items()]
123
+ return "\n\n---\n\n".join(bloques)
124
+
125
+
126
+ # ---------------------------
127
+ # App Gradio
128
+ # ---------------------------
129
+
130
+ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "")
131
+ store = GestorPrompts().cargar()
132
+
133
+ def es_admin(pass_ingresado):
134
+ return bool(ADMIN_PASSWORD) and pass_ingresado == ADMIN_PASSWORD
135
+
136
+ def do_login(pass_ingresado):
137
+ ok = es_admin(pass_ingresado)
138
+ msg = "✅ Acceso admin concedido." if ok else "❌ Contraseña incorrecta."
139
+ return ok, gr.update(visible=ok), gr.update(visible=ok), gr.update(value=msg), gr.update(value="")
140
+
141
+ def refrescar_listas():
142
+ def to_table(dic):
143
+ return [[nombre, data["texto"]] for nombre, data in dic.items()]
144
+ tabla_personajes = to_table(store.personajes)
145
+ tabla_foto = to_table(store.fotografia)
146
+ concat_personajes = store.concatenar_prompts_categoria("personajes")
147
+ concat_foto = store.concatenar_prompts_categoria("fotografia")
148
+ nombres_personajes = list(store.personajes.keys())
149
+ nombres_foto = list(store.fotografia.keys())
150
+ return (tabla_personajes, tabla_foto, concat_personajes, concat_foto,
151
+ gr.update(choices=nombres_personajes), gr.update(choices=nombres_foto))
152
+
153
+ def admin_agregar(nombre, texto, lista_masiva):
154
+ if (not nombre or not texto) and not lista_masiva.strip():
155
+ return "⚠️ Provee un prompt o una lista.", *refrescar_listas()
156
+ msg = []
157
+ if nombre and texto:
158
+ cat = store.agregar(nombre, texto)
159
+ msg.append(f"Agregado '{nombre}' en {cat}.")
160
+ if lista_masiva.strip():
161
+ textos = [t.strip() for t in lista_masiva.split("\n") if t.strip()]
162
+ res = store.agregar_lista(textos)
163
+ if res:
164
+ msg.append(f"Lista agregada ({len(res)} prompts).")
165
+ store.guardar()
166
+ return " | ".join(msg) if msg else "Sin cambios.", *refrescar_listas()
167
+
168
+ def admin_actualizar(categoria, nombre, nuevo_texto):
169
+ if not nombre:
170
+ return "⚠️ Selecciona un prompt.", *refrescar_listas()
171
+ ok = store.actualizar(categoria, nombre, nuevo_texto=nuevo_texto)
172
+ store.guardar()
173
+ msg = "✏️ Actualizado." if ok else "⚠️ No existe."
174
+ return msg, *refrescar_listas()
175
+
176
+ def admin_eliminar(categoria, nombre):
177
+ if not nombre:
178
+ return "⚠️ Selecciona un prompt.", *refrescar_listas()
179
+ ok = store.eliminar(categoria, nombre)
180
+ store.guardar()
181
+ msg = "🗑️ Eliminado." if ok else "⚠️ No existe."
182
+ return msg, *refrescar_listas()
183
+
184
+ def buscar(query, categoria):
185
+ resultados = store.buscar(query, categoria)
186
+ if not resultados:
187
+ return [["-", "-", "Sin resultados"]]
188
+ return [[c, n, t] for c, n, t in resultados]
189
+
190
+ def preparar_descarga(categoria):
191
+ data = store.exportar_categoria_json_bytes(categoria)
192
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=f"_{categoria}.json")
193
+ tmp.write(data)
194
+ tmp.flush()
195
+ return tmp.name
196
+
197
+ with gr.Blocks(theme="soft", fill_height=True) as demo:
198
+ gr.Markdown("# 🐾 BATUTO_encicloPromt🐾")
199
+ gr.Markdown("Explora, copia y descarga categorías. Solo el admin puede agregar o editar.")
200
+
201
+ admin_state = gr.State(False)
202
+
203
+ with gr.Row():
204
+ with gr.Column():
205
+ gr.Markdown("### 🔍 Búsqueda")
206
+ in_query = gr.Textbox(label="Buscar texto o tag")
207
+ sel_cat = gr.Dropdown(choices=["todas", "personajes", "fotografia"], value="todas", label="Categoría")
208
+ btn_buscar = gr.Button("Buscar")
209
+ tabla_busqueda = gr.Dataframe(headers=["Categoría