196 lines
6.8 KiB
Python
196 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
|
|
from auth import require_admin_dep, require_auth
|
|
from db import get_db, row_to_dict
|
|
from engine import EngineError, execute_prompt, preview_prompt
|
|
|
|
router = APIRouter(prefix="/api/prompts", tags=["prompts"])
|
|
|
|
LIST_FIELDS = """
|
|
id, slug, name, description, category, prompt_type, template, required_feature,
|
|
active, is_system_default, sort_order, created, updated
|
|
"""
|
|
|
|
|
|
class PromptWrite(BaseModel):
|
|
slug: str = Field(min_length=1, max_length=80)
|
|
name: str = Field(min_length=1)
|
|
description: str = ""
|
|
category: str = "uncategorized"
|
|
prompt_type: str = "base"
|
|
template: str = ""
|
|
required_feature: str = "ai_calls"
|
|
active: bool = True
|
|
|
|
|
|
class PreviewRequest(BaseModel):
|
|
slug: str | None = None
|
|
prompt_id: str | None = None
|
|
|
|
|
|
class ExecuteRequest(BaseModel):
|
|
slug: str | None = None
|
|
prompt_id: str | None = None
|
|
purpose: str = "reflection"
|
|
data_class: str = Field(default="B")
|
|
|
|
|
|
def _http(exc: EngineError):
|
|
raise HTTPException(status_code=exc.status_code, detail={"code": exc.code, "message": exc.message}) from exc
|
|
|
|
|
|
def _load(conn, prompt_id: str | None, slug: str | None) -> dict:
|
|
if prompt_id:
|
|
row = conn.execute("SELECT * FROM ai_prompts WHERE id = ?", (prompt_id,)).fetchone()
|
|
elif slug:
|
|
row = conn.execute("SELECT * FROM ai_prompts WHERE slug = ?", (slug,)).fetchone()
|
|
else:
|
|
raise HTTPException(400, "prompt_id oder slug ist erforderlich")
|
|
prompt = row_to_dict(row)
|
|
if not prompt:
|
|
raise HTTPException(404, "Prompt nicht gefunden")
|
|
return prompt
|
|
|
|
|
|
@router.get("")
|
|
def list_prompts(session: dict = Depends(require_admin_dep)):
|
|
with get_db() as conn:
|
|
rows = conn.execute(f"SELECT {LIST_FIELDS} FROM ai_prompts ORDER BY sort_order, name").fetchall()
|
|
return [row_to_dict(row) for row in rows]
|
|
|
|
|
|
@router.post("")
|
|
def create_prompt(req: PromptWrite, session: dict = Depends(require_admin_dep)):
|
|
if req.prompt_type not in {"base", "pipeline", "workflow"}:
|
|
raise HTTPException(400, "Ungültiger Prompt-Typ")
|
|
prompt_id = str(uuid.uuid4())
|
|
if req.prompt_type == "base" and req.template:
|
|
try:
|
|
preview_prompt(
|
|
{
|
|
"id": prompt_id,
|
|
"slug": req.slug.strip(),
|
|
"prompt_type": req.prompt_type,
|
|
"template": req.template,
|
|
},
|
|
{},
|
|
)
|
|
except EngineError as exc:
|
|
_http(exc)
|
|
try:
|
|
with get_db() as conn:
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO ai_prompts
|
|
(id, slug, name, description, category, prompt_type, template,
|
|
required_feature, active, default_template)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
prompt_id,
|
|
req.slug.strip(),
|
|
req.name.strip(),
|
|
req.description,
|
|
req.category.strip() or "uncategorized",
|
|
req.prompt_type,
|
|
req.template,
|
|
req.required_feature,
|
|
1 if req.active else 0,
|
|
req.template,
|
|
),
|
|
)
|
|
return row_to_dict(conn.execute(f"SELECT {LIST_FIELDS} FROM ai_prompts WHERE id = ?", (prompt_id,)).fetchone())
|
|
except Exception as exc:
|
|
if "UNIQUE" in str(exc).upper():
|
|
raise HTTPException(409, "Slug ist bereits vergeben") from exc
|
|
raise
|
|
|
|
|
|
@router.put("/{prompt_id}")
|
|
def update_prompt(prompt_id: str, req: PromptWrite, session: dict = Depends(require_admin_dep)):
|
|
with get_db() as conn:
|
|
current = row_to_dict(conn.execute("SELECT id FROM ai_prompts WHERE id = ?", (prompt_id,)).fetchone())
|
|
if not current:
|
|
raise HTTPException(404, "Prompt nicht gefunden")
|
|
if req.prompt_type == "base" and req.template:
|
|
try:
|
|
preview_prompt(
|
|
{
|
|
"id": prompt_id,
|
|
"slug": req.slug.strip(),
|
|
"prompt_type": req.prompt_type,
|
|
"template": req.template,
|
|
},
|
|
{},
|
|
)
|
|
except EngineError as exc:
|
|
_http(exc)
|
|
conn.execute(
|
|
"""
|
|
UPDATE ai_prompts
|
|
SET slug = ?, name = ?, description = ?, category = ?, prompt_type = ?,
|
|
template = ?, required_feature = ?, active = ?, updated = datetime('now')
|
|
WHERE id = ?
|
|
""",
|
|
(
|
|
req.slug.strip(),
|
|
req.name.strip(),
|
|
req.description,
|
|
req.category.strip() or "uncategorized",
|
|
req.prompt_type,
|
|
req.template,
|
|
req.required_feature,
|
|
1 if req.active else 0,
|
|
prompt_id,
|
|
),
|
|
)
|
|
return row_to_dict(conn.execute(f"SELECT {LIST_FIELDS} FROM ai_prompts WHERE id = ?", (prompt_id,)).fetchone())
|
|
|
|
|
|
@router.post("/{prompt_id}/reset")
|
|
def reset_prompt(prompt_id: str, session: dict = Depends(require_admin_dep)):
|
|
with get_db() as conn:
|
|
prompt = row_to_dict(conn.execute("SELECT * FROM ai_prompts WHERE id = ?", (prompt_id,)).fetchone())
|
|
if not prompt:
|
|
raise HTTPException(404, "Prompt nicht gefunden")
|
|
if not prompt["is_system_default"]:
|
|
raise HTTPException(400, "Nur System-Defaults können zurückgesetzt werden")
|
|
conn.execute(
|
|
"UPDATE ai_prompts SET template = default_template, updated = datetime('now') WHERE id = ?",
|
|
(prompt_id,),
|
|
)
|
|
return row_to_dict(conn.execute(f"SELECT {LIST_FIELDS} FROM ai_prompts WHERE id = ?", (prompt_id,)).fetchone())
|
|
|
|
|
|
@router.post("/preview")
|
|
def preview(req: PreviewRequest, session: dict = Depends(require_admin_dep)):
|
|
with get_db() as conn:
|
|
prompt = _load(conn, req.prompt_id, req.slug)
|
|
try:
|
|
return preview_prompt(prompt, {"profile_id": session["profile_id"]})
|
|
except EngineError as exc:
|
|
_http(exc)
|
|
|
|
|
|
@router.post("/execute")
|
|
def execute(req: ExecuteRequest, session: dict = Depends(require_auth)):
|
|
with get_db() as conn:
|
|
prompt = _load(conn, req.prompt_id, req.slug)
|
|
if not prompt["active"] and session.get("role") != "admin":
|
|
raise HTTPException(404, "Prompt nicht gefunden")
|
|
try:
|
|
return execute_prompt(
|
|
prompt,
|
|
profile_id=session["profile_id"],
|
|
purpose=req.purpose,
|
|
data_class=req.data_class,
|
|
context={"profile_id": session["profile_id"]},
|
|
)
|
|
except EngineError as exc:
|
|
_http(exc)
|