Sprint 12: UI-Vereinheitlichung + Läufigkeits-Tracker
- by-tabs/by-tab: einheitliche Tab/Pill-Navigation in allen Seiten - by-section-label, by-toolbar: einheitliche Section-Labels und Toolbars - Design-Tokens: fehlende --c-amber, --c-primary-soft ergänzt, Fallback-Werte entfernt - sitting.js: sitting-layout für konsistentes flush-Layout (wie walks) - Läufigkeits-Tracker: neuer Health-Tab für Hündinnen mit Zyklusvorhersage, Timeline vergangener Läufigkeiten, Erinnerungen und auto-berechnetem Nächst-Datum - emptyState-Bug: icon-Parameter muss SVG sein, nicht Icon-Name (dog/bell/warning gefixt) - SW-Cache: by-v103, APP_VER: 79
This commit is contained in:
parent
32d630d5a1
commit
b58789373c
30 changed files with 4344 additions and 523 deletions
284
backend/routes/admin.py
Normal file
284
backend/routes/admin.py
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
"""BAN YARO — Admin / Moderator Backend"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from database import db
|
||||
from auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Dependency: Moderator oder Admin
|
||||
# ------------------------------------------------------------------
|
||||
def require_mod(user=Depends(get_current_user)):
|
||||
if user["rolle"] not in ("admin", "moderator") and not user.get("is_moderator"):
|
||||
raise HTTPException(403, "Kein Zugriff.")
|
||||
return user
|
||||
|
||||
def require_admin(user=Depends(get_current_user)):
|
||||
if user["rolle"] != "admin":
|
||||
raise HTTPException(403, "Nur Admins.")
|
||||
return user
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schemas
|
||||
# ------------------------------------------------------------------
|
||||
class UserPatch(BaseModel):
|
||||
rolle: Optional[str] = None # user | moderator | admin
|
||||
is_moderator: Optional[int] = None
|
||||
is_banned: Optional[int] = None
|
||||
ban_reason: Optional[str] = None
|
||||
|
||||
class ThreadAdminPatch(BaseModel):
|
||||
is_pinned: Optional[int] = None
|
||||
is_locked: Optional[int] = None
|
||||
is_deleted: Optional[int] = None
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/admin/stats
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/stats")
|
||||
async def stats(user=Depends(require_mod)):
|
||||
with db() as conn:
|
||||
users_total = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
||||
users_today = conn.execute(
|
||||
"SELECT COUNT(*) FROM users WHERE DATE(created_at)=DATE('now')"
|
||||
).fetchone()[0]
|
||||
threads = conn.execute(
|
||||
"SELECT COUNT(*) FROM forum_threads WHERE is_deleted=0"
|
||||
).fetchone()[0]
|
||||
posts = conn.execute(
|
||||
"SELECT COUNT(*) FROM forum_posts WHERE is_deleted=0"
|
||||
).fetchone()[0]
|
||||
open_reports = conn.execute(
|
||||
"SELECT COUNT(*) FROM forum_reports WHERE resolved=0"
|
||||
).fetchone()[0]
|
||||
banned = conn.execute(
|
||||
"SELECT COUNT(*) FROM users WHERE is_banned=1"
|
||||
).fetchone()[0]
|
||||
dogs_total = conn.execute("SELECT COUNT(*) FROM dogs").fetchone()[0]
|
||||
poison_total = conn.execute("SELECT COUNT(*) FROM poison_alerts WHERE status='aktiv'").fetchone()[0]
|
||||
|
||||
return {
|
||||
"users_total": users_total,
|
||||
"users_today": users_today,
|
||||
"threads": threads,
|
||||
"posts": posts,
|
||||
"open_reports": open_reports,
|
||||
"banned": banned,
|
||||
"dogs_total": dogs_total,
|
||||
"poison_active":poison_total,
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/admin/users
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/users")
|
||||
async def list_users(
|
||||
q: str = "",
|
||||
rolle: str = "",
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
user=Depends(require_mod),
|
||||
):
|
||||
with db() as conn:
|
||||
where = "WHERE 1=1"
|
||||
params = []
|
||||
if q.strip():
|
||||
where += " AND (u.name LIKE ? OR u.email LIKE ?)"
|
||||
params.extend([f"%{q.strip()}%", f"%{q.strip()}%"])
|
||||
if rolle:
|
||||
where += " AND u.rolle = ?"
|
||||
params.append(rolle)
|
||||
|
||||
rows = conn.execute(f"""
|
||||
SELECT u.id, u.name, u.email, u.rolle, u.is_premium,
|
||||
u.is_moderator, u.is_banned, u.ban_reason,
|
||||
u.created_at, u.last_login,
|
||||
(SELECT COUNT(*) FROM dogs d WHERE d.user_id=u.id) AS dog_count,
|
||||
(SELECT COUNT(*) FROM forum_threads t WHERE t.user_id=u.id AND t.is_deleted=0) AS thread_count
|
||||
FROM users u
|
||||
{where}
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""", [*params, limit, offset]).fetchall()
|
||||
|
||||
total = conn.execute(f"""
|
||||
SELECT COUNT(*) FROM users u {where}
|
||||
""", params).fetchone()[0]
|
||||
|
||||
return {"users": [dict(r) for r in rows], "total": total}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# PATCH /api/admin/users/{id} — Rolle, Sperre
|
||||
# ------------------------------------------------------------------
|
||||
@router.patch("/users/{uid}")
|
||||
async def patch_user(uid: int, data: UserPatch, user=Depends(require_mod)):
|
||||
# Rollenwechsel nur für Admins
|
||||
if data.rolle is not None and user["rolle"] != "admin":
|
||||
raise HTTPException(403, "Rollenwechsel nur für Admins.")
|
||||
if data.rolle and data.rolle not in ("user", "moderator", "admin"):
|
||||
raise HTTPException(400, "Ungültige Rolle.")
|
||||
|
||||
with db() as conn:
|
||||
target = conn.execute("SELECT id, rolle FROM users WHERE id=?", (uid,)).fetchone()
|
||||
if not target:
|
||||
raise HTTPException(404, "User nicht gefunden.")
|
||||
# Mods dürfen keine Admins sperren
|
||||
if target["rolle"] == "admin" and user["rolle"] != "admin":
|
||||
raise HTTPException(403, "Admins können nur von Admins verwaltet werden.")
|
||||
|
||||
updates = data.model_dump(exclude_none=True)
|
||||
if not updates:
|
||||
raise HTTPException(400, "Keine Änderungen.")
|
||||
|
||||
# is_moderator aus rolle ableiten wenn rolle gesetzt wird
|
||||
if "rolle" in updates:
|
||||
updates["is_moderator"] = 1 if updates["rolle"] in ("moderator", "admin") else 0
|
||||
|
||||
cols = ", ".join(f"{k}=?" for k in updates)
|
||||
conn.execute(f"UPDATE users SET {cols} WHERE id=?", [*updates.values(), uid])
|
||||
row = conn.execute(
|
||||
"SELECT id, name, email, rolle, is_moderator, is_banned, ban_reason FROM users WHERE id=?",
|
||||
(uid,)
|
||||
).fetchone()
|
||||
|
||||
return dict(row)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DELETE /api/admin/users/{id} — Account löschen (Admin only)
|
||||
# ------------------------------------------------------------------
|
||||
@router.delete("/users/{uid}", status_code=204)
|
||||
async def delete_user(uid: int, user=Depends(require_admin)):
|
||||
with db() as conn:
|
||||
target = conn.execute("SELECT id, rolle FROM users WHERE id=?", (uid,)).fetchone()
|
||||
if not target:
|
||||
raise HTTPException(404, "User nicht gefunden.")
|
||||
if target["id"] == user["id"]:
|
||||
raise HTTPException(400, "Du kannst deinen eigenen Account nicht löschen.")
|
||||
conn.execute("DELETE FROM users WHERE id=?", (uid,))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/admin/forum/threads — alle Threads inkl. gelöschte
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/forum/threads")
|
||||
async def admin_threads(
|
||||
q: str = "",
|
||||
deleted: int = 0,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
user=Depends(require_mod),
|
||||
):
|
||||
with db() as conn:
|
||||
where = "WHERE 1=1"
|
||||
params = []
|
||||
if not deleted:
|
||||
where += " AND t.is_deleted=0"
|
||||
if q.strip():
|
||||
where += " AND (t.titel LIKE ? OR t.text LIKE ?)"
|
||||
params.extend([f"%{q.strip()}%", f"%{q.strip()}%"])
|
||||
|
||||
rows = conn.execute(f"""
|
||||
SELECT t.id, t.kategorie, t.titel, SUBSTR(t.text,1,100) AS text_preview,
|
||||
t.antworten, t.likes, t.views,
|
||||
t.is_pinned, t.is_locked, t.is_deleted, t.created_at,
|
||||
u.id AS user_id, u.name AS autor_name
|
||||
FROM forum_threads t
|
||||
LEFT JOIN users u ON u.id=t.user_id
|
||||
{where}
|
||||
ORDER BY t.created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""", [*params, limit, offset]).fetchall()
|
||||
|
||||
total = conn.execute(f"""
|
||||
SELECT COUNT(*) FROM forum_threads t {where}
|
||||
""", params).fetchone()[0]
|
||||
|
||||
return {"threads": [dict(r) for r in rows], "total": total}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# PATCH /api/admin/forum/threads/{id}
|
||||
# ------------------------------------------------------------------
|
||||
@router.patch("/forum/threads/{tid}")
|
||||
async def admin_patch_thread(tid: int, data: ThreadAdminPatch, user=Depends(require_mod)):
|
||||
with db() as conn:
|
||||
if not conn.execute("SELECT 1 FROM forum_threads WHERE id=?", (tid,)).fetchone():
|
||||
raise HTTPException(404, "Thread nicht gefunden.")
|
||||
updates = data.model_dump(exclude_none=True)
|
||||
if not updates:
|
||||
raise HTTPException(400, "Keine Änderungen.")
|
||||
cols = ", ".join(f"{k}=?" for k in updates)
|
||||
conn.execute(f"UPDATE forum_threads SET {cols} WHERE id=?", [*updates.values(), tid])
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DELETE /api/admin/forum/threads/{id}
|
||||
# ------------------------------------------------------------------
|
||||
@router.delete("/forum/threads/{tid}", status_code=204)
|
||||
async def admin_delete_thread(tid: int, user=Depends(require_mod)):
|
||||
with db() as conn:
|
||||
if not conn.execute("SELECT 1 FROM forum_threads WHERE id=?", (tid,)).fetchone():
|
||||
raise HTTPException(404, "Thread nicht gefunden.")
|
||||
conn.execute("UPDATE forum_threads SET is_deleted=1 WHERE id=?", (tid,))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DELETE /api/admin/forum/posts/{id}
|
||||
# ------------------------------------------------------------------
|
||||
@router.delete("/forum/posts/{pid}", status_code=204)
|
||||
async def admin_delete_post(pid: int, user=Depends(require_mod)):
|
||||
with db() as conn:
|
||||
post = conn.execute("SELECT * FROM forum_posts WHERE id=?", (pid,)).fetchone()
|
||||
if not post:
|
||||
raise HTTPException(404, "Beitrag nicht gefunden.")
|
||||
conn.execute("UPDATE forum_posts SET is_deleted=1 WHERE id=?", (pid,))
|
||||
conn.execute(
|
||||
"UPDATE forum_threads SET antworten=MAX(0,antworten-1) WHERE id=?",
|
||||
(post["thread_id"],)
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/admin/reports — offene Meldungen
|
||||
# ------------------------------------------------------------------
|
||||
@router.get("/reports")
|
||||
async def admin_reports(user=Depends(require_mod)):
|
||||
with db() as conn:
|
||||
rows = conn.execute("""
|
||||
SELECT r.id, r.target_type, r.target_id, r.grund, r.resolved, r.created_at,
|
||||
u.name AS melder_name,
|
||||
CASE r.target_type
|
||||
WHEN 'thread' THEN (SELECT t.titel FROM forum_threads t WHERE t.id=r.target_id)
|
||||
WHEN 'post' THEN (SELECT SUBSTR(p.text,1,80) FROM forum_posts p WHERE p.id=r.target_id)
|
||||
END AS content_preview
|
||||
FROM forum_reports r
|
||||
LEFT JOIN users u ON u.id=r.user_id
|
||||
ORDER BY r.resolved ASC, r.created_at DESC
|
||||
LIMIT 100
|
||||
""").fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# PATCH /api/admin/reports/{id} — erledigen / wiedereröffnen
|
||||
# ------------------------------------------------------------------
|
||||
@router.patch("/reports/{rid}")
|
||||
async def admin_resolve_report(rid: int, user=Depends(require_mod)):
|
||||
with db() as conn:
|
||||
r = conn.execute("SELECT resolved FROM forum_reports WHERE id=?", (rid,)).fetchone()
|
||||
if not r:
|
||||
raise HTTPException(404, "Meldung nicht gefunden.")
|
||||
conn.execute(
|
||||
"UPDATE forum_reports SET resolved=? WHERE id=?",
|
||||
(0 if r["resolved"] else 1, rid)
|
||||
)
|
||||
return {"ok": True}
|
||||
|
|
@ -32,20 +32,34 @@ def _set_cookie(response: Response, token: str):
|
|||
|
||||
@router.post("/register")
|
||||
async def register(data: RegisterRequest, response: Response):
|
||||
name = data.name.strip()
|
||||
if len(name) < 2:
|
||||
raise HTTPException(400, "Name muss mindestens 2 Zeichen lang sein.")
|
||||
if len(name) > 40:
|
||||
raise HTTPException(400, "Name darf maximal 40 Zeichen lang sein.")
|
||||
|
||||
with db() as conn:
|
||||
if conn.execute("SELECT 1 FROM users WHERE email=?", (data.email,)).fetchone():
|
||||
raise HTTPException(400, "E-Mail bereits registriert.")
|
||||
conn.execute(
|
||||
"INSERT INTO users (email, pw_hash, name) VALUES (?,?,?)",
|
||||
(data.email, hash_password(data.password), data.name)
|
||||
)
|
||||
if conn.execute(
|
||||
"SELECT 1 FROM users WHERE name=? COLLATE NOCASE", (name,)
|
||||
).fetchone():
|
||||
raise HTTPException(400, "Dieser Name ist bereits vergeben. Bitte wähle einen anderen.")
|
||||
try:
|
||||
conn.execute(
|
||||
"INSERT INTO users (email, pw_hash, name) VALUES (?,?,?)",
|
||||
(data.email, hash_password(data.password), name)
|
||||
)
|
||||
except Exception:
|
||||
# Fallback falls UNIQUE-Index greift (Race Condition)
|
||||
raise HTTPException(400, "Dieser Name ist bereits vergeben. Bitte wähle einen anderen.")
|
||||
user = conn.execute(
|
||||
"SELECT id, rolle FROM users WHERE email=?", (data.email,)
|
||||
).fetchone()
|
||||
|
||||
token = create_token(user["id"], user["rolle"])
|
||||
_set_cookie(response, token)
|
||||
return {"token": token, "name": data.name}
|
||||
return {"token": token, "name": name}
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
|
|
|
|||
|
|
@ -8,22 +8,38 @@ router = APIRouter()
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _dogs_subquery():
|
||||
"""JSON-Array der Hunde eines Users als Subquery."""
|
||||
return """(
|
||||
SELECT json_group_array(json_object(
|
||||
'id', d.id,
|
||||
'name', d.name,
|
||||
'rasse', d.rasse,
|
||||
'foto_url',d.foto_url
|
||||
))
|
||||
FROM dogs d WHERE d.user_id = u.id
|
||||
)"""
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_friends(user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
dogs_sq = _dogs_subquery()
|
||||
with db() as conn:
|
||||
friends = conn.execute("""
|
||||
friends = conn.execute(f"""
|
||||
SELECT f.id, f.status, f.created_at,
|
||||
CASE WHEN f.requester_id=? THEN f.addressee_id ELSE f.requester_id END AS friend_id,
|
||||
u.name AS friend_name
|
||||
u.name AS friend_name,
|
||||
{dogs_sq} AS dogs_json
|
||||
FROM friendships f
|
||||
JOIN users u ON u.id = CASE WHEN f.requester_id=? THEN f.addressee_id ELSE f.requester_id END
|
||||
WHERE (f.requester_id=? OR f.addressee_id=?) AND f.status='accepted'
|
||||
ORDER BY u.name
|
||||
""", (uid, uid, uid, uid)).fetchall()
|
||||
|
||||
incoming = conn.execute("""
|
||||
SELECT f.id, f.created_at, u.name AS requester_name, u.id AS requester_id
|
||||
incoming = conn.execute(f"""
|
||||
SELECT f.id, f.created_at, u.name AS requester_name, u.id AS requester_id,
|
||||
{dogs_sq} AS dogs_json
|
||||
FROM friendships f
|
||||
JOIN users u ON u.id=f.requester_id
|
||||
WHERE f.addressee_id=? AND f.status='pending'
|
||||
|
|
@ -38,9 +54,26 @@ async def list_friends(user=Depends(get_current_user)):
|
|||
ORDER BY f.created_at DESC
|
||||
""", (uid,)).fetchall()
|
||||
|
||||
import json
|
||||
|
||||
def _parse(rows):
|
||||
result = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
if d.get("dogs_json"):
|
||||
try:
|
||||
d["dogs"] = json.loads(d["dogs_json"])
|
||||
except Exception:
|
||||
d["dogs"] = []
|
||||
else:
|
||||
d["dogs"] = []
|
||||
d.pop("dogs_json", None)
|
||||
result.append(d)
|
||||
return result
|
||||
|
||||
return {
|
||||
"friends": [dict(r) for r in friends],
|
||||
"incoming": [dict(r) for r in incoming],
|
||||
"friends": _parse(friends),
|
||||
"incoming": _parse(incoming),
|
||||
"outgoing": [dict(r) for r in outgoing],
|
||||
}
|
||||
|
||||
|
|
@ -50,9 +83,12 @@ async def search_users(q: str = "", user=Depends(get_current_user)):
|
|||
if len(q.strip()) < 2:
|
||||
return []
|
||||
uid = user["id"]
|
||||
import json
|
||||
with db() as conn:
|
||||
rows = conn.execute("""
|
||||
SELECT u.id, u.name
|
||||
SELECT u.id, u.name,
|
||||
(SELECT json_group_array(json_object('name', d.name, 'rasse', d.rasse))
|
||||
FROM dogs d WHERE d.user_id=u.id AND d.is_public=1) AS dogs_json
|
||||
FROM users u
|
||||
WHERE u.id != ?
|
||||
AND u.name LIKE ?
|
||||
|
|
@ -63,7 +99,17 @@ async def search_users(q: str = "", user=Depends(get_current_user)):
|
|||
)
|
||||
LIMIT 20
|
||||
""", (uid, f"%{q.strip()}%", uid, uid)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
result = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
try:
|
||||
d["dogs"] = json.loads(d["dogs_json"]) if d.get("dogs_json") else []
|
||||
except Exception:
|
||||
d["dogs"] = []
|
||||
d.pop("dogs_json", None)
|
||||
result.append(d)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/request/{target_id}", status_code=201)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ router = APIRouter()
|
|||
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
||||
|
||||
# Erlaubte Typen
|
||||
TYPEN = {"impfung", "entwurmung", "tierarzt", "medikament", "gewicht", "allergie", "dokument"}
|
||||
TYPEN = {"impfung", "entwurmung", "tierarzt", "medikament", "gewicht", "allergie", "dokument", "laeufigkeit"}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,3 +1,62 @@
|
|||
"""BAN YARO — ki Routes (Stub, wird ausgebaut)"""
|
||||
from fastapi import APIRouter
|
||||
"""BAN YARO — KI Routes"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
import ki as ki_module
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class TrainingRequest(BaseModel):
|
||||
problem: str
|
||||
rasse: Optional[str] = None
|
||||
alter: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/training")
|
||||
async def ki_training(req: TrainingRequest):
|
||||
"""
|
||||
KI-Trainingsberatung für individuelle Verhaltens- und Trainingsprobleme.
|
||||
Kostenlos für alle (nutzt lokales Modell).
|
||||
"""
|
||||
if not req.problem or len(req.problem.strip()) < 10:
|
||||
raise HTTPException(400, "Bitte beschreibe das Problem genauer.")
|
||||
if len(req.problem) > 1000:
|
||||
raise HTTPException(400, "Beschreibung zu lang (max. 1000 Zeichen).")
|
||||
|
||||
rasse = req.rasse or "unbekannt"
|
||||
alter = req.alter or "unbekannt"
|
||||
|
||||
system = (
|
||||
"Du bist ein erfahrener, zertifizierter Hundetrainer mit Schwerpunkt "
|
||||
"auf positiver Verstärkung und gewaltfreier Erziehung. "
|
||||
"Antworte immer auf Deutsch, konkret, verständlich und motivierend. "
|
||||
"Gib keine Ratschläge die Schmerz oder Zwang beinhalten. "
|
||||
"Wenn das Problem schwerwiegend ist (Aggression, starke Angst), "
|
||||
"empfehle professionellen Hundetrainer vor Ort zusätzlich."
|
||||
)
|
||||
|
||||
prompt = f"""Hund: {rasse}, {alter} alt.
|
||||
|
||||
Problem: {req.problem.strip()}
|
||||
|
||||
Bitte gib:
|
||||
1. Eine kurze Einschätzung des Problems (1-2 Sätze)
|
||||
2. 3-5 konkrete Trainingsschritte die ich heute starten kann
|
||||
3. Was ich vermeiden sollte
|
||||
4. Wann ich einen Profi hinzuziehen sollte (falls relevant)
|
||||
|
||||
Schreibe klar und strukturiert, ohne unnötigen Fachjargon."""
|
||||
|
||||
try:
|
||||
result = await ki_module.complete(
|
||||
prompt=prompt,
|
||||
system=system,
|
||||
max_tokens=600,
|
||||
requires_premium=False,
|
||||
)
|
||||
return {"antwort": result}
|
||||
except ki_module.KIUnavailableError as e:
|
||||
raise HTTPException(503, str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(500, "KI momentan nicht verfügbar.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue