feat: initial project setup

This commit is contained in:
2026-03-21 08:49:51 +01:00
commit 0ae87f16c7
88 changed files with 10755 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir \
discord.py==2.3.2 \
httpx==0.27.0 \
PyGithub==2.3.0 \
python-dotenv==1.0.1
COPY ../shared/ ./shared/
COPY prompt.txt .
COPY main.py .
CMD ["python", "main.py"]

96
agents/daenerys/main.py Normal file
View File

@@ -0,0 +1,96 @@
import os
import sys
import asyncio
import httpx
import discord
from discord.ext import commands
sys.path.insert(0, "/app/shared")
from github_client import create_content_pr
with open("prompt.txt", "r", encoding="utf-8") as f:
SYSTEM_PROMPT = f.read()
OPENWEBUI_URL = os.environ["OPENWEBUI_URL"]
OPENWEBUI_API_KEY = os.environ["OPENWEBUI_API_KEY"]
MY_CHANNEL_ID = int(os.environ["DISCORD_CHANNEL_ID"])
OPENWEBUI_MODEL = os.environ.get("OPENWEBUI_MODEL", "gpt-4o")
AGENT_NAME = "Daenerys"
async def call_llm(messages: list[dict]) -> str:
async with httpx.AsyncClient(timeout=180) as client:
response = await client.post(
f"{OPENWEBUI_URL}/api/chat/completions",
headers={"Authorization": f"Bearer {OPENWEBUI_API_KEY}"},
json={"model": OPENWEBUI_MODEL, "messages": messages},
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
print(f"[DAENERYS] Conectado como {bot.user}")
@bot.event
async def on_message(message: discord.Message):
if message.author.bot:
return
if message.channel.id != MY_CHANNEL_ID:
return
async with message.channel.typing():
try:
content = await call_llm([
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": message.content},
])
# Intentar detectar si la respuesta es un documento para PR
if "---" in content and ("title:" in content or "status:" in content):
# Es un guión o artículo — crear PR
lines = content.split("\n")
title_line = next((l for l in lines if l.startswith("title:")), None)
title = title_line.split(":", 1)[1].strip().strip('"') if title_line else "Nuevo contenido"
# Determinar si es blog o guión
if "status:" in content:
file_path = f"src/content/guiones/{title.lower().replace(' ', '-')}.md"
branch_prefix = "guiones"
else:
from datetime import date
today = date.today().isoformat()
file_path = f"src/content/blog/{today}-{title.lower().replace(' ', '-')}.md"
branch_prefix = "blog"
pr_url = create_content_pr(
file_path=file_path,
content=content,
title=title,
description=f"Contenido generado por {AGENT_NAME} para: {message.content[:200]}",
agent_name=AGENT_NAME,
branch_prefix=branch_prefix,
)
await message.reply(f"He terminado mi trabajo. PR creado: {pr_url}")
else:
# Respuesta conversacional — enviar directamente
# Dividir si supera el límite de Discord (2000 chars)
for chunk in [content[i:i+1990] for i in range(0, len(content), 1990)]:
await message.channel.send(chunk)
except Exception as e:
await message.reply(f"Error al procesar: {e}")
await bot.process_commands(message)
bot.run(os.environ["DISCORD_TOKEN"])

View File

@@ -0,0 +1,43 @@
Eres Daenerys Targaryen, la Madre de Dragones y responsable de recursos visuales de carlospalanca.es.
PERSONALIDAD:
- Visionaria, creativa y con un estilo que rompe los moldes establecidos.
- "Dracarys" — cuando lanzas un componente Remotion, arde de calidad.
- Eres exigente con la identidad visual: cada frame importa.
- Pragmática: propones soluciones que Carlos puede implementar con sus herramientas.
ROL Y RESPONSABILIDADES:
- Diseñar y crear animaciones con Remotion (TypeScript/React)
- Proponer diseños de thumbnails para YouTube
- Crear componentes de lower thirds y motion graphics
- Mantener la identidad visual consistente del canal
- Generar código Remotion parametrizable y reutilizable
CANAL DE DISCORD:
- Operas en #poniente-en-llamas
STACK TÉCNICO:
- Remotion (animaciones React programáticas)
- TypeScript/React para todos los componentes
- SVG/CSS para assets estáticos
- Resolución estándar: 1920x1080 (16:9), 1080x1920 para Shorts
- FPS: 30 para general, 60 para animaciones de alta fluidez
ESTRUCTURA DE COMPONENTES REMOTION:
- Todos los componentes en remotion/src/components/
- Props tipadas con TypeScript interfaces
- Colores como variables para facilitar restyling
- JSDoc en cada componente
ENTREGABLES TÍPICOS:
1. Componentes Remotion → PR en remotion/src/components/
2. Thumbnails → Descripción detallada + código SVG/Remotion
3. Lower thirds → Componentes con props: nombre, cargo, duración
4. Intros/outros → Secuencias de 3-5 segundos
REGLAS ABSOLUTAS:
1. NUNCA hagas commit directo a main
2. SIEMPRE crea PRs con descripción detallada del visual (qué se ve, colores, animación)
3. Los assets binarios (imágenes) van en /public/assets/, nunca en /src/
4. Todos los componentes deben ser parametrizables mediante props
5. Rama de PRs: visual/daenerys-YYYYMMDD-componente