homelab-brain/redax-wp/src/openrouter.py
root 82eaa1e4bc feat(redax-wp): Multi-Publish, Dashboard-Verbesserungen, ESP32-Serie Teil 2
Redax-WP (Redakteur):
- WordPressMirrorClient: Multi-Publish an mehrere WP-Instanzen
- Target-Toggles im Dashboard (Checkbox, server-side rendering)
- WP-Admin Direktzugang via socat-Proxy (bypass Cloudflare WAF)
- Drag & Drop im Redaktionsplan
- Artikel-Karten mit Titel + SEO-Snippet sichtbar
- Entwürfe ohne Datum in separater Sektion
- DB-Cleanup-Job (Sonntag 03:00 Uhr)
- openrouter.py: sync generate() Wrapper
- mirror_posts Tabelle in DB

ESP32-Serie (Arakava News):
- Teil 1 veröffentlicht (Post 1209)
- Teil 2 als WP-Entwurf erstellt (Post 1340)
- Animiertes Hydraulikschema (SVG, 4 Betriebsmodi) in Teil 2 eingebaut
- Hardware liegt in DE, Einbau ab April nach Kambodscha-Rückkehr

Doku:
- STATE.md Redax-WP vollständig aktualisiert
- STATE.md Arakava-News: Serie-Status + Hardware-Timeline

Made-with: Cursor
2026-02-28 19:25:43 +07:00

97 lines
3.6 KiB
Python

import os
import logging
import aiohttp
import asyncio
logger = logging.getLogger(__name__)
OPENROUTER_API_KEY = os.environ.get('OPENROUTER_API_KEY', '')
OPENROUTER_BASE = "https://openrouter.ai/api/v1"
DEFAULT_MODEL = os.environ.get('AI_MODEL', 'openai/gpt-4o-mini')
async def generate_article(source: str, prompt_template: str, date_str: str, tag: str = "allgemein") -> str:
system_prompt = prompt_template.format(
source=source,
date=date_str,
tag=tag.lower().replace(" ", "")
)
payload = {
"model": DEFAULT_MODEL,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Schreibe jetzt den Artikel basierend auf dieser Quelle:\n\n{source}"}
],
"max_tokens": 600,
"temperature": 0.8
}
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
"HTTP-Referer": "https://fuenfvoracht.orbitalo.net",
"X-Title": "FünfVorAcht Bot"
}
async with aiohttp.ClientSession() as session:
async with session.post(f"{OPENROUTER_BASE}/chat/completions", json=payload, headers=headers) as resp:
data = await resp.json()
if resp.status != 200:
raise Exception(f"OpenRouter Fehler {resp.status}: {data}")
return data["choices"][0]["message"]["content"].strip()
async def get_balance() -> dict:
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json"
}
try:
async with aiohttp.ClientSession() as session:
async with session.get(f"{OPENROUTER_BASE}/auth/key", headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
key_data = data.get("data", {})
limit = key_data.get("limit")
usage = key_data.get("usage", 0)
if limit:
remaining = round(limit - usage, 4)
else:
remaining = None
return {
"usage": round(usage, 4),
"limit": limit,
"remaining": remaining,
"label": key_data.get("label", ""),
"is_free_tier": key_data.get("is_free_tier", False)
}
except Exception as e:
logger.error("Balance-Abfrage fehlgeschlagen: %s", e)
return {"usage": None, "limit": None, "remaining": None}
def get_balance_sync() -> dict:
return asyncio.run(get_balance())
def generate(system_prompt: str, user_message: str) -> str:
import asyncio, aiohttp as _aiohttp
async def _gen():
payload = {
"model": DEFAULT_MODEL,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
],
"max_tokens": 2500,
"temperature": 0.8
}
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
}
async with _aiohttp.ClientSession() as session:
async with session.post(f"{OPENROUTER_BASE}/chat/completions", json=payload, headers=headers) as resp:
data = await resp.json()
if resp.status != 200:
raise Exception(f"OpenRouter Fehler {resp.status}: {data}")
return data["choices"][0]["message"]["content"].strip()
return asyncio.run(_gen())