97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
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 = "Bronn"
|
|
|
|
|
|
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"[BRONN] 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"])
|