update
This commit is contained in:
@@ -31,4 +31,4 @@ CLAUDE_CLI = "claude"
|
||||
|
||||
MODEL_GUIDE = "claude-opus-4-8"
|
||||
MODEL_BAUSTEIN_GEN = "claude-sonnet-4-6"
|
||||
MODEL_BAUSTEIN_REWORK = "claude-haiku-4-5"
|
||||
MODEL_BAUSTEIN_REWORK = "claude-sonnet-4-6"
|
||||
|
||||
@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS bausteine (
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
purpose TEXT NOT NULL DEFAULT '',
|
||||
example TEXT NOT NULL DEFAULT '',
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
@@ -62,6 +63,10 @@ async def init_db():
|
||||
columns = {row[1] for row in await cursor.fetchall()}
|
||||
if "instructions" not in columns:
|
||||
await db.execute("ALTER TABLE guides ADD COLUMN instructions TEXT NOT NULL DEFAULT ''")
|
||||
cursor = await db.execute("PRAGMA table_info(bausteine)")
|
||||
columns = {row[1] for row in await cursor.fetchall()}
|
||||
if "sort_order" not in columns:
|
||||
await db.execute("ALTER TABLE bausteine ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0")
|
||||
await db.execute(
|
||||
"UPDATE guides SET status = 'error', progress = NULL, error_msg = 'Server-Neustart' "
|
||||
"WHERE status IN ('queued', 'generating')"
|
||||
@@ -153,12 +158,22 @@ async def create_baustein(baustein: dict) -> dict:
|
||||
async def list_bausteine(topic: str) -> list[dict]:
|
||||
db = await get_db()
|
||||
cursor = await db.execute(
|
||||
"SELECT * FROM bausteine WHERE topic = ? ORDER BY created_at ASC", (topic,)
|
||||
"SELECT * FROM bausteine WHERE topic = ? ORDER BY sort_order ASC, created_at ASC", (topic,)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return [_row_to_dict(row, cursor) for row in rows]
|
||||
|
||||
|
||||
async def update_baustein_sort_orders(topic: str, order_map: dict) -> None:
|
||||
db = await get_db()
|
||||
for baustein_id, order in order_map.items():
|
||||
await db.execute(
|
||||
"UPDATE bausteine SET sort_order = ? WHERE id = ? AND topic = ?",
|
||||
(order, baustein_id, topic),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def get_baustein(baustein_id: str) -> dict | None:
|
||||
db = await get_db()
|
||||
cursor = await db.execute("SELECT * FROM bausteine WHERE id = ?", (baustein_id,))
|
||||
|
||||
@@ -25,6 +25,7 @@ from database import (
|
||||
delete_pending_suggestions,
|
||||
list_bausteine,
|
||||
update_baustein,
|
||||
update_baustein_sort_orders,
|
||||
)
|
||||
from paths import final_paths, temp_paths
|
||||
|
||||
@@ -327,12 +328,17 @@ async def _fail(guide_id: str, msg: str) -> None:
|
||||
# --- Bausteine ---
|
||||
|
||||
_suggestions_generating: set[str] = set()
|
||||
_sorting: set[str] = set()
|
||||
|
||||
|
||||
def is_suggestions_generating(topic: str) -> bool:
|
||||
return topic in _suggestions_generating
|
||||
|
||||
|
||||
def is_sorting(topic: str) -> bool:
|
||||
return topic in _sorting
|
||||
|
||||
|
||||
def _parse_json(text: str):
|
||||
text = text.strip()
|
||||
text = re.sub(r"^```(?:json)?\s*", "", text)
|
||||
@@ -484,6 +490,8 @@ async def rework_baustein(baustein_id: str, topic: str, title: str, current: dic
|
||||
|
||||
|
||||
def _build_baustein_rework_prompt(topic: str, title: str, current: dict, instructions: str) -> str:
|
||||
spec = (TEMPLATES_DIR / "Format" / "Baustein.md").read_text(encoding="utf-8")
|
||||
|
||||
current_json = json.dumps({
|
||||
"title": title,
|
||||
"description": current.get("description", ""),
|
||||
@@ -499,9 +507,53 @@ AKTUELLER STAND:
|
||||
ANWEISUNGEN VOM NUTZER:
|
||||
{instructions}
|
||||
|
||||
FORMAT-SPEZIFIKATION:
|
||||
{spec}
|
||||
|
||||
Antworte AUSSCHLIESSLICH mit einem JSON-Objekt mit den Feldern "title", "description", "purpose", "examples".
|
||||
"examples" ist ein Array mit Objekten {{"label": "...", "code": "..."}}.
|
||||
Kein weiterer Text, nur das JSON.
|
||||
Orientiere dich an der Spezifikation. Kein weiterer Text, nur das JSON.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
def _build_sort_prompt(topic: str, bausteine: list[dict], instructions: str) -> str:
|
||||
items = "\n".join(
|
||||
f"- id={b['id']} | {b['title']} | {b['description']} | {b['purpose']}"
|
||||
for b in bausteine
|
||||
)
|
||||
if instructions:
|
||||
criterion = f"Sortiere die folgenden Bausteine zum Thema \"{topic}\" STRIKT nach diesem Kriterium:\n\n{instructions}"
|
||||
else:
|
||||
criterion = f"Sortiere die folgenden Bausteine zum Thema \"{topic}\" von Anfaenger zu Experte (erstes = einfachster, letztes = komplexester)."
|
||||
|
||||
return f"""{criterion}
|
||||
|
||||
BAUSTEINE:
|
||||
{items}
|
||||
|
||||
Antworte AUSSCHLIESSLICH mit einem JSON-Array der IDs in der gewuenschten Reihenfolge.
|
||||
Beispiel: [\"id1\", \"id2\", \"id3\"]
|
||||
|
||||
Kein weiterer Text, nur das JSON-Array.
|
||||
"""
|
||||
|
||||
|
||||
async def sort_bausteine(topic: str, bausteine: list[dict], instructions: str = "") -> None:
|
||||
_sorting.add(topic)
|
||||
try:
|
||||
prompt = _build_sort_prompt(topic, bausteine, instructions)
|
||||
returncode, stdout, stderr = await _run_claude("sort-" + topic, prompt, 300, tools=None, model=MODEL_BAUSTEIN_GEN)
|
||||
if returncode != 0:
|
||||
return
|
||||
ids = _parse_json(stdout)
|
||||
if not isinstance(ids, list):
|
||||
return
|
||||
order_map = {bid: i for i, bid in enumerate(ids) if isinstance(bid, str)}
|
||||
if order_map:
|
||||
await update_baustein_sort_orders(topic, order_map)
|
||||
except Exception as e:
|
||||
print(f"[sort] topic={topic} Exception: {type(e).__name__}: {e}")
|
||||
finally:
|
||||
_sorting.discard(topic)
|
||||
|
||||
@@ -49,10 +49,15 @@ class BausteinResponse(BaseModel):
|
||||
description: str
|
||||
purpose: str
|
||||
example: str
|
||||
sort_order: int = 0
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class BausteinSortRequest(BaseModel):
|
||||
instructions: str = Field(default="", max_length=2000)
|
||||
|
||||
|
||||
class SuggestionResponse(BaseModel):
|
||||
id: str
|
||||
topic: str
|
||||
|
||||
@@ -11,10 +11,10 @@ from database import (
|
||||
create_baustein as db_create_baustein, list_bausteine, get_baustein, delete_baustein as db_delete_baustein,
|
||||
list_suggestions, get_suggestion, update_suggestion, delete_suggestion,
|
||||
)
|
||||
from generator import generate_guide, rework_guide, cancel_guide, generate_suggestions, generate_baustein_detail, rework_baustein, is_suggestions_generating
|
||||
from generator import generate_guide, rework_guide, cancel_guide, generate_suggestions, generate_baustein_detail, rework_baustein, sort_bausteine, is_suggestions_generating, is_sorting
|
||||
from models import (
|
||||
GuideCreateRequest, GuideReworkRequest, GuideResponse,
|
||||
BausteinCreateRequest, BausteinReworkRequest, BausteinResponse, SuggestionResponse,
|
||||
BausteinCreateRequest, BausteinReworkRequest, BausteinSortRequest, BausteinResponse, SuggestionResponse,
|
||||
)
|
||||
from paths import final_paths
|
||||
|
||||
@@ -167,6 +167,22 @@ async def rework_baustein_route(baustein_id: str, req: BausteinReworkRequest):
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/bausteine/sort")
|
||||
async def sort_bausteine_route(topic: str, req: BausteinSortRequest):
|
||||
if is_sorting(topic):
|
||||
return {"ok": True, "status": "already_sorting"}
|
||||
bausteine = await list_bausteine(topic)
|
||||
if not bausteine:
|
||||
return {"ok": True}
|
||||
asyncio.create_task(sort_bausteine(topic, bausteine, req.instructions.strip()))
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/bausteine/sort/status")
|
||||
async def sort_status(topic: str):
|
||||
return {"sorting": is_sorting(topic)}
|
||||
|
||||
|
||||
# --- Baustein Suggestions ---
|
||||
|
||||
@router.get("/bausteine/suggestions", response_model=list[SuggestionResponse])
|
||||
|
||||
Reference in New Issue
Block a user