update
This commit is contained in:
@@ -13,28 +13,24 @@ from database import (
|
||||
create_guide, delete_guide, get_guide, list_guides,
|
||||
create_topic, list_topics as db_list_topics, delete_topic,
|
||||
list_progress, set_progress, delete_progress,
|
||||
create_element, list_elements, get_element, update_element, delete_element,
|
||||
list_block_progress, get_block_progress, set_open_question,
|
||||
set_block_score_and_streak, set_block_completed,
|
||||
set_block_score_and_streak,
|
||||
delete_block_data, delete_block_progress, subs_per_level, subs_per_level_raw,
|
||||
delete_topic_pipeline, delete_source, get_guide_content, delete_guide_content,
|
||||
get_sub_artefakte, kanban_reset, delete_guide_board,
|
||||
)
|
||||
from blocks import generate_blocks, cancel_blocks, blocks_status, active_blocks, reset_blocks, load_source, load_overview, subblocks_title, subblocks_frei, load_question_pattern, load_question_pattern_free, _blocks_files
|
||||
from board_inventory import add_research_agent, board_snapshot, requeue_dead, reset_board_from_stage
|
||||
from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion
|
||||
from learning import block_chat, block_discussion, create_block_element, exam_rating, exam_rating_fast, exam_question, exam_question_variant, generate_quiz, generate_gapchoice, generate_gaptext, check_gaptext, hurdles_distractor_block, compute_score, floor_from_score, level_from_score, cap_final, cap_aktuell, freie_level, thresholds, points_delta, cap_followup
|
||||
from guide import generate_guide, guide_slot_files, block_pruefen, block_adopt, content_fuer_level
|
||||
from board_inventory import add_research_agent, board_snapshot, requeue_dead, reset_board_from_stage, restart_artefact_card
|
||||
from learning import block_chat, block_discussion, exam_rating, exam_rating_fast, exam_question, exam_question_variant, generate_quiz, generate_gapchoice, generate_gaptext, check_gaptext, hurdles_distractor_block, compute_score, floor_from_score, level_from_score, cap_final, cap_aktuell, freie_level, thresholds, points_delta, cap_followup
|
||||
from guide import chat_with_guide, generate_guide, guide_slot_files, block_pruefen, block_adopt, content_fuer_level
|
||||
from pipeline import cancel_guide
|
||||
from rules import FORMATE, formats_stats, guide_lock, ist_completed, load_learnstate, topic_completed
|
||||
from models import (
|
||||
GuideCreateRequest, GuideResponse,
|
||||
TopicCreateRequest,
|
||||
BlocksCreateRequest, BlocksResetStageRequest, BlocksStatusResponse,
|
||||
BlocksCreateRequest, BlocksResetStageRequest, BlocksCardRestartRequest, BlocksStatusResponse,
|
||||
GuideCardResetRequest,
|
||||
GuideBoardResetRequest, GuideChatRequest, GuideChatResponse,
|
||||
ElementCreateRequest, ElementChatRequest, ElementChatResponse, ElementResponse,
|
||||
ElementUpdateRequest, ElementCheckRequest, ElementCheckResponse, ElementStyleResponse,
|
||||
ElementRefineRequest, ElementRefineResponse,
|
||||
ProgressUpdate, ProgressResponse, ProjectResponse, ProviderInfo,
|
||||
FolderResponse, BlocksSourceUpdate, BlocksSourceResponse, BlockOverview,
|
||||
BlockChatRequest, BlockChatResponse,
|
||||
@@ -177,14 +173,14 @@ async def get_blocks_board(topic: str):
|
||||
snap["generating"] = status["generating"]
|
||||
snap["progress"] = status["progress"]
|
||||
snap["error"] = status["error"]
|
||||
snap["agents"] = [{"label": a["key"].removeprefix(f"blocks-{topic}-"), "runtime": a["runtime"]}
|
||||
snap["agents"] = [{"key": a["key"], "label": a["label"] or a["key"].removeprefix(f"blocks-{topic}-"), "runtime": a["runtime"]}
|
||||
for a in active_agents(f"blocks-{topic}-")]
|
||||
return snap
|
||||
|
||||
|
||||
@router.get("/blocks/agents")
|
||||
async def get_blocks_agents(topic: str):
|
||||
return [{"label": a["key"].removeprefix(f"blocks-{topic}-"), "runtime": a["runtime"]}
|
||||
return [{"key": a["key"], "label": a["label"] or a["key"].removeprefix(f"blocks-{topic}-"), "runtime": a["runtime"]}
|
||||
for a in active_agents(f"blocks-{topic}-")]
|
||||
|
||||
|
||||
@@ -214,6 +210,28 @@ async def requeue_blocks_dead(topic: str):
|
||||
return {"ok": True, "requeued": await requeue_dead(topic)}
|
||||
|
||||
|
||||
@router.post("/blocks/card-restart")
|
||||
async def blocks_card_restart(req: BlocksCardRestartRequest):
|
||||
if (await blocks_status(req.topic))["generating"]:
|
||||
return {"ok": True, "status": "generating"}
|
||||
if not await restart_artefact_card(req.topic, req.card_id):
|
||||
raise HTTPException(404, "Card not found")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/guides/board/card-reset")
|
||||
async def guide_card_reset(req: GuideCardResetRequest):
|
||||
running = [g for g in await list_guides()
|
||||
if g["topic"] == req.topic and g["format"] == req.format
|
||||
and g["status"] in ("queued", "generating")]
|
||||
if running:
|
||||
return {"ok": True, "status": "generating"}
|
||||
from guide_board import reset_card
|
||||
if not await reset_card(req.topic, req.format, req.block_norm, req.ab_stage):
|
||||
raise HTTPException(404, "Card not found")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/blocks/cancel")
|
||||
async def cancel_blocks_route(topic: str):
|
||||
if not cancel_blocks(topic):
|
||||
@@ -361,11 +379,10 @@ def _color(points: int) -> str:
|
||||
async def _book_score(req, question: str, tier: str, n_je_level: dict[int, int]) -> dict:
|
||||
"""Book score+streak drift-free (lock + open-question/open-streak anchor). Tier →
|
||||
points delta (streak-modulated) or progressive malus on error. cap_aktuell is derived
|
||||
from the base (delayed unlock at the level threshold); element once from beginner level.
|
||||
from the base (delayed unlock at the level threshold).
|
||||
Re-rating of the same question uses the open streak anchor → idempotent."""
|
||||
async with _check_lock(req.topic, req.block):
|
||||
state = await get_block_progress(req.topic, req.block)
|
||||
was_level = state["completed"] is not None # element guard: ever created already?
|
||||
basis, re_rating = _basis(state, question)
|
||||
streak_basis = state["offene_streak"] if re_rating else state["streak"]
|
||||
if not re_rating:
|
||||
@@ -378,10 +395,6 @@ async def _book_score(req, question: str, tier: str, n_je_level: dict[int, int])
|
||||
score = compute_score(basis, d, floor, ca, cf)
|
||||
points = score - basis
|
||||
good, streak = await set_block_score_and_streak(req.topic, req.block, score, new_streak)
|
||||
# Create the learning element once, as soon as the first level (beginner) is reached.
|
||||
if not was_level and level_from_score(score, cf) is not None:
|
||||
if await set_block_completed(req.topic, req.block):
|
||||
asyncio.create_task(create_block_element(req.topic, req.block, req.section, req.provider))
|
||||
return {"points": points, "rating": _color(points), "good_answers": good, "streak": streak, "cap": cf}
|
||||
|
||||
|
||||
@@ -571,7 +584,7 @@ async def get_guide_board(topic: str, format: str = "Guide"):
|
||||
snap["progress"] = guide.get("progress") if guide else None
|
||||
snap["error"] = guide.get("error_msg") if guide else None
|
||||
prefix = f"{guide['id']}-" if guide else "-"
|
||||
snap["agents"] = [{"label": a["key"].removeprefix(prefix), "runtime": a["runtime"]}
|
||||
snap["agents"] = [{"key": a["key"], "label": a["label"] or a["key"].removeprefix(prefix), "runtime": a["runtime"]}
|
||||
for a in active_agents(prefix)]
|
||||
return snap
|
||||
|
||||
@@ -658,82 +671,6 @@ async def block_adopt_route(guide_id: str, req: BlockUebernehmenRequest):
|
||||
return res
|
||||
|
||||
|
||||
# --- Elements (personal summary) ---
|
||||
|
||||
@router.get("/elements", response_model=list[ElementResponse])
|
||||
async def get_elements(topic: str):
|
||||
return await list_elements(topic)
|
||||
|
||||
|
||||
@router.post("/elements", response_model=ElementResponse)
|
||||
async def post_element(req: ElementCreateRequest):
|
||||
fields = await generate_element(req.topic, req.hint, provider=req.provider)
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
element = {"id": str(uuid.uuid4()), "topic": req.topic, **fields, "created_at": now, "updated_at": now}
|
||||
await create_element(element)
|
||||
return element
|
||||
|
||||
|
||||
@router.post("/elements/{element_id}/chat", response_model=ElementChatResponse)
|
||||
async def element_chat(element_id: str, req: ElementChatRequest):
|
||||
element = await get_element(element_id)
|
||||
if element is None:
|
||||
raise HTTPException(404, "Element not found")
|
||||
reply, changes = await chat_with_element(element, [m.model_dump() for m in req.messages], provider=req.provider)
|
||||
return {"reply": reply, "changes": changes}
|
||||
|
||||
|
||||
@router.post("/elements/{element_id}/refine", response_model=ElementRefineResponse)
|
||||
async def element_refine(element_id: str, req: ElementRefineRequest):
|
||||
element = await get_element(element_id)
|
||||
if element is None:
|
||||
raise HTTPException(404, "Element not found")
|
||||
change = await refine_suggestion(element, req.suggestion.model_dump(), req.instruction, provider=req.provider)
|
||||
if change is None:
|
||||
raise HTTPException(502, "Revision failed — please try again")
|
||||
return {"change": change}
|
||||
|
||||
|
||||
@router.put("/elements/{element_id}", response_model=ElementResponse)
|
||||
async def put_element(element_id: str, req: ElementUpdateRequest):
|
||||
if await get_element(element_id) is None:
|
||||
raise HTTPException(404, "Element not found")
|
||||
fields = req.model_dump(exclude_unset=True, exclude_none=True)
|
||||
if fields:
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
await update_element(element_id, **fields, updated_at=now)
|
||||
return await get_element(element_id)
|
||||
|
||||
|
||||
@router.post("/elements/{element_id}/style", response_model=ElementStyleResponse)
|
||||
async def element_style(element_id: str, req: ElementCheckRequest):
|
||||
element = await get_element(element_id)
|
||||
if element is None:
|
||||
raise HTTPException(404, "Element not found")
|
||||
changes = await style_element(element, provider=req.provider)
|
||||
if changes is None:
|
||||
raise HTTPException(502, "Style check failed — please try again")
|
||||
return {"changes": changes}
|
||||
|
||||
|
||||
@router.post("/elements/{element_id}/check", response_model=ElementCheckResponse)
|
||||
async def element_check(element_id: str, req: ElementCheckRequest):
|
||||
element = await get_element(element_id)
|
||||
if element is None:
|
||||
raise HTTPException(404, "Element not found")
|
||||
suggestions = await check_element(element, provider=req.provider)
|
||||
if suggestions is None:
|
||||
raise HTTPException(502, "Check failed — please try again")
|
||||
return {"suggestions": suggestions}
|
||||
|
||||
|
||||
@router.delete("/elements/{element_id}")
|
||||
async def remove_element(element_id: str):
|
||||
if not await delete_element(element_id):
|
||||
raise HTTPException(404, "Element not found")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/guides/{guide_id}/cancel")
|
||||
async def cancel(guide_id: str):
|
||||
cancelled = await cancel_guide(guide_id)
|
||||
|
||||
Reference in New Issue
Block a user