social: UploadFile Import-Fix (ForwardRef-Fehler → 502)
This commit is contained in:
parent
0df6d569c1
commit
ca0ce79815
1 changed files with 130 additions and 9 deletions
|
|
@ -7,7 +7,7 @@ import json
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
||||
from pydantic import BaseModel
|
||||
|
||||
from auth import get_current_user, require_social_media
|
||||
|
|
@ -21,13 +21,34 @@ _FORMATS = {"reel", "story", "post", "carousel"}
|
|||
_STATUSES = {"idea", "draft", "scheduled", "published", "archived"}
|
||||
|
||||
_SYSTEM = (
|
||||
"Du bist ein erfahrener Social-Media-Stratege für Ban Yaro, "
|
||||
"die deutschsprachige Hunde-App (banyaro.app). "
|
||||
"Zielgruppe: Hundebesitzer in DACH, 20–45 Jahre, Fokus Frauen. "
|
||||
"Ton: warmherzig, authentisch, informativ — nie aufdringlich werblich. "
|
||||
"Du bist Luna, der freundliche Social-Media-Coach von Ban Yaro (banyaro.app). "
|
||||
"Du begleitest eine junge Content-Creatorin (15 Jahre) dabei, für die Hunde-App "
|
||||
"auf TikTok und Instagram zu wachsen. "
|
||||
"Erkläre immer kurz WARUM etwas funktioniert — sie soll lernen, nicht nur kopieren. "
|
||||
"Ton: locker, ermutigend, auf Augenhöhe — nie von oben herab. "
|
||||
"Zielgruppe der App: Hundebesitzer in DACH, 20–45 Jahre, Fokus Frauen. "
|
||||
"Antworte immer auf Deutsch."
|
||||
)
|
||||
|
||||
_PROMPT_SUGGESTIONS = '''\
|
||||
Schlage 5 aktuelle, kreative Content-Ideen für Ban Yaro (Hunde-App) vor.
|
||||
Heute: {datum} ({saison}).
|
||||
Bereits verwendete Themen (nicht wiederholen):
|
||||
{used_topics}
|
||||
|
||||
Antworte NUR als JSON-Array:
|
||||
[
|
||||
{{
|
||||
"thema": "Konkretes Thema für einen Post (1 Satz)",
|
||||
"warum": "Kurze Erklärung warum das gerade gut funktioniert (1-2 Sätze, als Coach)",
|
||||
"format": "reel|post|story|carousel",
|
||||
"platform": "tiktok|instagram|both",
|
||||
"emoji": "1 passendes Emoji"
|
||||
}}
|
||||
]
|
||||
Misch Formate und Plattformen. Aktuelle Trends, Saisonales und Evergreen-Themen kombinieren.
|
||||
'''
|
||||
|
||||
_PROMPT_GENERATE = '''\
|
||||
Erstelle einen vollständigen Social-Media-Content-Plan für folgenden Post.
|
||||
|
||||
|
|
@ -49,7 +70,8 @@ Antworte NUR mit einem JSON-Objekt:
|
|||
"canva_notes": "Empfehlung für Canva: Template-Typ, Textüberlagerungen, Farbstimmung",
|
||||
"script": {script_field},
|
||||
"unsplash_query": "2-3 englische Suchbegriffe für Unsplash-Stockfotos",
|
||||
"ai_score": <Zahl 1-5, geschätztes Engagement-Potenzial>
|
||||
"ai_score": <Zahl 1-5, geschätztes Engagement-Potenzial>,
|
||||
"coaching": "2-3 Sätze für die Creatorin: Warum dieser Hook? Warum diese Hashtags? Was macht diesen Post stark? Locker und ermutigend formuliert."
|
||||
}}
|
||||
'''
|
||||
|
||||
|
|
@ -161,6 +183,37 @@ def _parse_json(raw: str) -> dict:
|
|||
raise ValueError(f"Kein JSON in Antwort: {raw[:200]}")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/social/suggestions — KI schlägt 5 Themen vor
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/suggestions")
|
||||
async def get_suggestions(user=Depends(require_social_media)):
|
||||
from datetime import date
|
||||
import calendar
|
||||
|
||||
today = date.today()
|
||||
month = today.month
|
||||
saison = (
|
||||
"Frühling" if month in (3,4,5) else
|
||||
"Sommer" if month in (6,7,8) else
|
||||
"Herbst" if month in (9,10,11) else "Winter"
|
||||
)
|
||||
prompt = _PROMPT_SUGGESTIONS.format(
|
||||
datum=today.strftime("%d.%m.%Y"),
|
||||
saison=saison,
|
||||
used_topics=_used_topics(20),
|
||||
)
|
||||
try:
|
||||
raw = await _ki_complete(prompt)
|
||||
import re
|
||||
m = re.search(r"\[[\s\S]+\]", raw)
|
||||
data = json.loads(m.group(0)) if m else []
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.error("Suggestions-Fehler: %s", e)
|
||||
raise HTTPException(500, f"KI-Fehler: {e}")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/social/content — alle Einträge
|
||||
# ------------------------------------------------------------------
|
||||
|
|
@ -216,8 +269,8 @@ async def generate_content(req: GenerateRequest, user=Depends(require_social_med
|
|||
"""INSERT INTO social_content
|
||||
(created_by, platform, format, topic, caption, hashtags,
|
||||
visual_brief, image_prompt, canva_notes, script, hook, cta,
|
||||
unsplash_query, ai_score, source, breed_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
unsplash_query, ai_score, source, breed_id, coaching)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(
|
||||
user["id"], req.platform, req.format, req.topic,
|
||||
data.get("caption"), data.get("hashtags"),
|
||||
|
|
@ -225,7 +278,7 @@ async def generate_content(req: GenerateRequest, user=Depends(require_social_med
|
|||
data.get("canva_notes"), data.get("script"),
|
||||
data.get("hook"), data.get("cta"),
|
||||
data.get("unsplash_query"), data.get("ai_score"),
|
||||
"generated", req.breed_id,
|
||||
"generated", req.breed_id, data.get("coaching"),
|
||||
),
|
||||
)
|
||||
entry_id = cur.lastrowid
|
||||
|
|
@ -325,3 +378,71 @@ async def social_breeds(user=Depends(require_social_media)):
|
|||
"SELECT id, name FROM wiki_rassen WHERE ki_enriched=1 ORDER BY name ASC"
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/social/stats — Level / XP
|
||||
# ------------------------------------------------------------------
|
||||
_LEVELS = [
|
||||
(0, 50, "🌱 Rookie", "Anfängerin"),
|
||||
(50, 150, "✏️ Creator", "Content Creatorin"),
|
||||
(150, 400, "🌟 Influencer", "Influencerin"),
|
||||
(400, 800, "🏆 Pro Creator", "Profi"),
|
||||
(800, 99999,"👑 Star", "Social Media Star"),
|
||||
]
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_stats(user=Depends(require_social_media)):
|
||||
with db() as conn:
|
||||
generated = conn.execute(
|
||||
"SELECT COUNT(*) FROM social_content WHERE source='generated'"
|
||||
).fetchone()[0]
|
||||
published = conn.execute(
|
||||
"SELECT COUNT(*) FROM social_content WHERE status='published'"
|
||||
).fetchone()[0]
|
||||
evaluated = conn.execute(
|
||||
"SELECT COUNT(*) FROM social_content WHERE source='user'"
|
||||
).fetchone()[0]
|
||||
five_star = conn.execute(
|
||||
"SELECT COUNT(*) FROM social_content WHERE ai_score=5"
|
||||
).fetchone()[0]
|
||||
|
||||
xp = generated * 10 + published * 20 + evaluated * 5 + five_star * 15
|
||||
lvl = _LEVELS[0]
|
||||
for l in _LEVELS:
|
||||
if xp >= l[0]:
|
||||
lvl = l
|
||||
idx = _LEVELS.index(lvl)
|
||||
nxt = _LEVELS[idx + 1] if idx < len(_LEVELS) - 1 else None
|
||||
|
||||
return {
|
||||
"xp": xp, "generated": generated, "published": published,
|
||||
"evaluated": evaluated, "five_star": five_star,
|
||||
"level": lvl[2], "level_desc": lvl[3],
|
||||
"xp_current_min": lvl[0], "xp_next": nxt[0] if nxt else xp,
|
||||
"next_level": nxt[2] if nxt else None,
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/social/media — Medien-Upload (Foto/Video)
|
||||
# ------------------------------------------------------------------
|
||||
@router.post("/media")
|
||||
async def upload_media(
|
||||
file: UploadFile = File(...),
|
||||
user=Depends(require_social_media),
|
||||
):
|
||||
import shutil, uuid, os
|
||||
|
||||
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
||||
social_dir = os.path.join(MEDIA_DIR, "social")
|
||||
os.makedirs(social_dir, exist_ok=True)
|
||||
|
||||
ext = os.path.splitext(file.filename or "")[-1].lower() or ".jpg"
|
||||
fname = f"{uuid.uuid4().hex}{ext}"
|
||||
path = os.path.join(social_dir, fname)
|
||||
|
||||
with open(path, "wb") as f:
|
||||
shutil.copyfileobj(file.file, f)
|
||||
|
||||
return {"url": f"/media/social/{fname}", "filename": fname}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue