File size: 11,355 Bytes
8528be4
0521784
8528be4
 
01c4dd9
a44fa84
 
5c2c5e3
a05bdda
9cba0d4
aa93b8f
5c2c5e3
 
8528be4
6829277
a6139d5
5c2c5e3
a6139d5
5c2c5e3
a6139d5
5c2c5e3
6829277
 
a05bdda
6829277
a05bdda
 
6829277
 
 
 
 
 
a44fa84
 
 
 
 
5a8692e
a44fa84
5a8692e
a44fa84
 
 
5a8692e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6829277
 
 
7fb15fa
a05bdda
7fb15fa
a05bdda
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5f8a1c
a05bdda
 
 
 
 
 
7fb15fa
054beb9
 
 
 
 
3b65bfd
054beb9
aa93b8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b65bfd
054beb9
 
7fb15fa
 
922c047
7fb15fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11fa61a
 
 
7fb15fa
11fa61a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7fb15fa
 
 
 
 
 
 
 
 
 
 
 
 
 
a44fa84
6829277
a44fa84
8528be4
a44fa84
8528be4
6829277
a44fa84
 
 
 
 
8528be4
 
 
 
6829277
a44fa84
 
8528be4
 
a44fa84
 
5c2c5e3
5c7fb94
a05bdda
8528be4
a44fa84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a05bdda
a44fa84
 
 
347bfd4
054beb9
 
 
 
 
 
 
 
347bfd4
b42541c
347bfd4
 
a05bdda
 
347bfd4
b42541c
 
a05bdda
b42541c
054beb9
b42541c
054beb9
 
 
 
b42541c
054beb9
 
b42541c
054beb9
 
b42541c
054beb9
 
 
 
 
 
 
b42541c
054beb9
 
 
b42541c
054beb9
b42541c
054beb9
 
 
 
b42541c
5c2c5e3
054beb9
b42541c
a05bdda
 
347bfd4
a44fa84
 
8528be4
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# agents/specialized.py
import os
from core.llm_client import llm
from utils.prompts import get_today_prefix, get_chat_style
from agents.registry import AGENT_REGISTRY
from core.file_builder import build_docx, build_excel
import re
import json
import asyncio
import requests
import base64
from pathlib import Path
from datetime import datetime

AGENT_ROLES = {
    "manager": """
Eres el gerente principal. Cuando no haya delegación o la tarea sea simple, responde de forma natural, amigable y útil al usuario.
No necesitas generar JSON aquí, solo contesta directamente.
Mantén un tono profesional pero cercano.
""",

    "image_agent": """
Eres el agente de imágenes.
Cuando se te pida generar, crear, mostrar o visualizar una imagen, responde SOLO con un JSON:
{"image_queries": ["término en inglés 1", "término en inglés 2", "término en inglés 3"]}
Los términos deben ser específicos, detallados y en inglés para obtener mejores resultados.
Si la tarea no es sobre imágenes, responde: {"skip":"no image task"}
""",

    "backend_dev": """
Eres programador backend senior.
REGLAS ABSOLUTAS:
1. Entrega SOLO el código pedido, sin explicaciones innecesarias.
2. Para excel/planilla → responde con EXCEL_TEMPLATE:{"title":"...","sheet_name":"...","headers":[...],"sample_rows":[[...],...]}
3. Python → entrega código Python puro y funcional.
4. Groovy/Jenkins → entrega el script completo.
5. Si hay frontend_dev en el equipo, TÚ haces servidor/backend, él hace HTML.
6. Si la tarea no requiere backend → responde: {"skip":"no backend needed"}
""",

    "writer": """
Eres redactor experto. Escribe SOLO contenido real y extenso (500+ palabras).
Sin placeholders. Usa ## para secciones y ### para subsecciones.
Secciones: ## Resumen Ejecutivo, ### Introducción, ### Desarrollo,
### Hallazgos, ### Conclusiones, ### Recomendaciones
""",

    "analyst": """
Eres analista de negocios. REGLAS:
1. Solo haz lo que el manager delegó: revisar documentos, evaluar viabilidad, analizar riesgos.
2. NUNCA describas imágenes ni hagas trabajo de otros agentes.
3. Si la tarea no requiere análisis → responde: {"skip":"no analysis needed"}
""",

    "frontend_dev": """
Eres desarrollador frontend senior. REGLAS ABSOLUTAS:
1. Entrega SOLO código HTML/CSS/JS pedido, sin explicaciones innecesarias.
2. Si hay backend_dev, TÚ haces HTML/interfaz, él hace servidor/lógica.
3. Si la tarea NO requiere frontend → responde: {"skip":"no frontend needed"}
4. Entrega siempre HTML completo y funcional con los estilos incluidos.
"""
}

"""
async def generate_real_image(prompts: list, model: str = "black-forest-labs/FLUX.1-schnell"):
    Genera una o más imágenes reales usando HF Inference API
    results = []
    for i, prompt in enumerate(prompts[:3]):  # límite de 3 imágenes por seguridad
        try:
            from huggingface_hub import InferenceClient
            client = InferenceClient(model=model, token=os.getenv("HF_API_TOKEN"))

            enhanced_prompt = f"{prompt}, highly detailed, cinematic lighting, 8k, masterpiece, best quality"

            image = await asyncio.to_thread(
                client.text_to_image,
                enhanced_prompt,
                num_inference_steps=20,
                guidance_scale=7.5,
                height=768,
                width=768
            )

            DOCS_DIR = Path("data/docs")
            DOCS_DIR.mkdir(exist_ok=True)
            ts = datetime.now().strftime("%Y%m%d_%H%M%S")
            file_name = f"generated_image_{ts}_{i+1}.png"
            file_path = DOCS_DIR / file_name
            image.save(file_path)

            results.append({
                "success": True,
                "image_url": f"/docs/{file_name}",
                "prompt_used": enhanced_prompt
            })
        except Exception as e:
            results.append({"success": False, "error": str(e)})

    return results
"""
# 🔵 FUNCIONES AUXILIARES (AQUÍ VAN)

async def generate_with_gemini(queries, model_name):
    # ⚠️ por ahora fallback
    return []

async def generate_with_hf(queries):
    urls = []

    for i, prompt in enumerate(queries[:2]):
        response = requests.post(
            "https://router.huggingface.co/v1/images/generations",
            headers={
                "Authorization": f"Bearer {os.getenv('HF_API_TOKEN')}"
            },
            json={
                "model": "black-forest-labs/FLUX.1-schnell",
                "prompt": prompt,
                "size": "1024x1024"
            }
        )

        if response.status_code == 200:
            data = response.json()

            image_base64 = data["data"][0]["b64_json"]
            image_bytes = base64.b64decode(image_base64)

            DOCS_DIR = Path("data/docs")
            DOCS_DIR.mkdir(exist_ok=True)

            file_name = f"hf_image_{datetime.now().timestamp()}.png"
            file_path = DOCS_DIR / file_name

            with open(file_path, "wb") as f:
                f.write(image_bytes)

            urls.append(f"/docs/{file_name}")

        else:
            print("HF error:", response.text)

    return urls

# 🟢 FUNCIÓN PRINCIPAL

async def generate_real_image(prompts: list):
    results = []

    for prompt in prompts[:3]:
        try:
            response = requests.post(
                "https://router.huggingface.co/v1/images/generations",
                headers={
                    "Authorization": f"Bearer {os.getenv('HF_API_TOKEN')}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": "black-forest-labs/FLUX.1-schnell",
                    "prompt": prompt,
                    "size": "1024x1024"
                }
            )

            # 🔥 DEBUG
            print("STATUS:", response.status_code)
            print("RAW:", response.text[:200])

            if response.status_code != 200:
                raise Exception(f"HTTP {response.status_code}: {response.text}")

            # intentar parsear JSON
            try:
                data = response.json()
            except:
                raise Exception("Respuesta no es JSON válida")

            if "data" not in data:
                raise Exception(f"Formato inesperado: {data}")

            image_url = data["data"][0].get("url")

            if not image_url:
                raise Exception("No se encontró URL de imagen")

            results.append({
                "success": True,
                "image_url": image_url
            })

        except Exception as e:
            results.append({
                "success": False,
                "error": str(e)
            })

    return results
    
async def run_specialized_agent(agent_key: str, task: str, context: dict = None) -> dict:
    if agent_key not in AGENT_ROLES:
        return {"error": f"Agente {agent_key} no implementado aún", "success": False}

    agent = AGENT_REGISTRY.get(agent_key, {})
    today = get_today_prefix()
    role = AGENT_ROLES[agent_key]

    full_prompt = today + role + get_chat_style() + f"\n\nTarea asignada: {task}"

    if context:
        full_prompt += "\n\nContexto de otros agentes:\n" + "\n".join([f"[{k.upper()}] {v[:300]}..." for k, v in context.items()])

    messages = [{"role": "user", "content": full_prompt}]

    try:
        response = await llm.call(agent_key, messages, temperature=0.45)

        result = {
            "agent": agent_key,
            "response": response,
            "success": True,
            "file_path": None,
            "file_type": None,
            "image_urls": [],  # ahora lista para múltiples imágenes
            "queries": None
        }

        # Generar archivo según agente
        if agent_key == "writer" and len(response.strip()) > 300:
            title = task[:60].strip('"') or "Informe generado"
            try:
                file_path = build_docx(title, response, images=None)
                if file_path:
                    result["file_path"] = file_path.name
                    result["file_type"] = "docx"
            except Exception as e:
                result["file_error"] = str(e)

        elif agent_key == "backend_dev":
            match = re.search(r'EXCEL_TEMPLATE:\s*(\{.*\})', response, re.DOTALL | re.IGNORECASE)
            if match:
                try:
                    excel_data = json.loads(match.group(1))
                    file_path = build_excel(task, excel_data)
                    if file_path:
                        result["file_path"] = file_path.name
                        result["file_type"] = "xlsx"
                        result["response"] = response.split("EXCEL_TEMPLATE:")[0].strip()
                except Exception as e:
                    result["file_error"] = str(e)

        elif agent_key == "image_agent":

            agent_config = AGENT_REGISTRY.get(agent_key, {})
            provider = agent_config.get("provider")
            model_name = agent_config.get("models", [None])[0]
        
            print("USANDO PROVIDER:", provider)
            print("USANDO MODELO:", model_name)
        
            match = re.search(r'\{.*"image_queries".*\}', response, re.DOTALL)
        
            if match:
                try:
                    data = json.loads(match.group(0))
                    queries = data.get("image_queries", [])
                    result["queries"] = queries
        
                    # 🧠 respuesta base
                    result["response"] = "Ideas de imágenes generadas:\n" + "\n".join(queries)
        
                    image_urls = []
        
                    # 🔥 intento 1: provider principal (registry)
                    try:
                        if provider == "gemini":
                            image_urls = await generate_with_gemini(queries, model_name)
        
                        elif provider == "huggingface":
                            image_urls = await generate_with_hf(queries)
        
                    except Exception as e:
                        print("Provider principal falló:", str(e))
        
                    # 🔥 fallback automático
                    if not image_urls:
                        try:
                            print("Intentando fallback HF...")
                            image_urls = await generate_with_hf(queries)
                        except Exception as e:
                            print("Fallback falló:", str(e))
        
                    # 🔥 fallback final visual
                    if not image_urls:
                        image_urls = ["https://picsum.photos/400/300"]
        
                    result["image_urls"] = image_urls
        
                    if image_urls:
                        result["response"] += "\n\nImagen generada ✔"
                    else:
                        result["response"] += "\n\n⚠️ No se pudo generar imagen"
        
                except Exception as e:
                    result["response"] = f"Error procesando JSON: {str(e)}"
        
            else:
                result["response"] = "No se detectaron instrucciones claras para imágenes."

        return result

    except Exception as e:
        return {
            "agent": agent_key,
            "error": str(e),
            "success": False
        }