update
This commit is contained in:
@@ -7,7 +7,7 @@ from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import Response
|
||||
|
||||
from agents import provider_available
|
||||
from agents import active_agents, provider_available
|
||||
from config import PROJECTS_DIR, UNI_DIR, PROVIDERS
|
||||
from database import (
|
||||
create_guide, delete_guide, get_guide, list_guides,
|
||||
@@ -18,19 +18,20 @@ from database import (
|
||||
set_block_score_and_streak, set_block_completed,
|
||||
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,
|
||||
get_sub_artefakte, kanban_reset, delete_guide_board,
|
||||
)
|
||||
from blocks import generate_blocks, cancel_blocks, blocks_status, active_blocks, reset_blocks, reset_blocks_ab_step, load_source, load_overview, subblocks_title, subblocks_frei, load_question_pattern, load_question_pattern_free
|
||||
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, guide_done_step, block_pruefen, block_adopt, content_fuer_level
|
||||
from guide import 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, BlocksResetStepRequest, BlocksStatusResponse,
|
||||
GuideChatRequest, GuideChatResponse,
|
||||
BlocksCreateRequest, BlocksResetStageRequest, BlocksStatusResponse,
|
||||
GuideBoardResetRequest, GuideChatRequest, GuideChatResponse,
|
||||
ElementCreateRequest, ElementChatRequest, ElementChatResponse, ElementResponse,
|
||||
ElementUpdateRequest, ElementCheckRequest, ElementCheckResponse, ElementStyleResponse,
|
||||
ElementRefineRequest, ElementRefineResponse,
|
||||
@@ -164,10 +165,55 @@ async def create_blocks(req: BlocksCreateRequest):
|
||||
raise HTTPException(400, "Link must start with http:// or https://.")
|
||||
qp.parent.mkdir(parents=True, exist_ok=True)
|
||||
atomic_write_json(qp, {"type": type, "location": location, "spec": req.instructions.strip()})
|
||||
asyncio.create_task(generate_blocks(topic, req.instructions.strip(), req.provider, ab_phase=req.ab_phase, ab_step=req.ab_step, to_step=req.to_step))
|
||||
asyncio.create_task(generate_blocks(topic, req.instructions.strip(), req.provider, research=req.research))
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/blocks/board")
|
||||
async def get_blocks_board(topic: str):
|
||||
"""Live kanban board: columns with counts + newest cards, dead-letter, agents."""
|
||||
snap = await board_snapshot(topic)
|
||||
status = await blocks_status(topic)
|
||||
snap["generating"] = status["generating"]
|
||||
snap["progress"] = status["progress"]
|
||||
snap["error"] = status["error"]
|
||||
snap["agents"] = [{"label": 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"]}
|
||||
for a in active_agents(f"blocks-{topic}-")]
|
||||
|
||||
|
||||
@router.post("/blocks/research")
|
||||
async def add_blocks_research(topic: str, provider: str = "claude"):
|
||||
"""Attach one more research agent — to the live flow, or attach-or-start."""
|
||||
if add_research_agent(topic):
|
||||
return {"ok": True, "attached": True}
|
||||
if (await blocks_status(topic))["generating"]:
|
||||
return {"ok": False, "status": "starting"} # flow is booting, try again shortly
|
||||
asyncio.create_task(generate_blocks(topic, "", provider, research=True))
|
||||
return {"ok": True, "attached": False}
|
||||
|
||||
|
||||
@router.post("/blocks/reset-stage")
|
||||
async def reset_blocks_stage(req: BlocksResetStageRequest):
|
||||
"""Reset cards from a column onward back to that column (no regeneration)."""
|
||||
topic = req.topic.strip()
|
||||
if (await blocks_status(topic))["generating"]:
|
||||
return {"ok": True, "status": "generating"} # don't interfere with a running generation
|
||||
moved = await reset_board_from_stage(topic, req.board, req.stage, _blocks_files(topic))
|
||||
return {"ok": True, "moved": moved}
|
||||
|
||||
|
||||
@router.post("/blocks/requeue-dead")
|
||||
async def requeue_blocks_dead(topic: str):
|
||||
return {"ok": True, "requeued": await requeue_dead(topic)}
|
||||
|
||||
|
||||
@router.post("/blocks/cancel")
|
||||
async def cancel_blocks_route(topic: str):
|
||||
if not cancel_blocks(topic):
|
||||
@@ -179,15 +225,7 @@ async def cancel_blocks_route(topic: str):
|
||||
async def remove_blocks(topic: str):
|
||||
reset_blocks(topic) # Files: crawl + triage + inventory…questions gone; source.json stays
|
||||
await delete_topic_pipeline(topic) # DB: blocks area gone; topic config (source) stays
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/blocks/reset-step")
|
||||
async def reset_blocks_step(req: BlocksResetStepRequest):
|
||||
topic = req.topic.strip()
|
||||
if (await blocks_status(topic))["generating"]:
|
||||
return {"ok": True, "status": "generating"} # don't interfere with a running generation
|
||||
await reset_blocks_ab_step(topic, req.ab_step)
|
||||
await kanban_reset(topic) # kanban cards + cluster membership gone
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@@ -509,9 +547,46 @@ async def guide_locks(topic: str):
|
||||
|
||||
@router.get("/guides/steps")
|
||||
async def guide_steps(topic: str):
|
||||
"""Highest fully completed step index per format (artifact-based, -1 = none).
|
||||
Drives the clickable step bubbles (like the blocks phases)."""
|
||||
return {fmt: guide_done_step(guide_content_path(topic, fmt)) for fmt in ("Guide", "FullGuide", "Rest")}
|
||||
"""Highest fully completed stage index per format (card-based, -1 = none).
|
||||
Content file present (legacy without cards) → everything done."""
|
||||
import guide_board
|
||||
out = {}
|
||||
for fmt in ("Guide", "FullGuide", "Rest"):
|
||||
step = await guide_board.done_step(topic, fmt)
|
||||
if step < 0 and guide_content_path(topic, fmt).exists():
|
||||
step = len(guide_board.GUIDE_STAGES)
|
||||
out[fmt] = step
|
||||
return out
|
||||
|
||||
|
||||
@router.get("/guides/board")
|
||||
async def get_guide_board(topic: str, format: str = "Guide"):
|
||||
"""Live guide board: columns with counts + cards (rounds, covered objectives), agents."""
|
||||
import guide_board
|
||||
snap = await guide_board.board_snapshot(topic, format)
|
||||
guide = next((g for g in await list_guides()
|
||||
if g["topic"] == topic and g["format"] == format), None)
|
||||
snap["generating"] = bool(guide and guide["status"] in ("queued", "generating"))
|
||||
snap["guide_id"] = guide["id"] if guide else None
|
||||
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"]}
|
||||
for a in active_agents(prefix)]
|
||||
return snap
|
||||
|
||||
|
||||
@router.post("/guides/board/reset")
|
||||
async def reset_guide_board(req: GuideBoardResetRequest):
|
||||
"""Reset cards from a stage onward — without generation (pendant to blocks reset-stage)."""
|
||||
import guide_board
|
||||
topic = req.topic.strip()
|
||||
guide = next((g for g in await list_guides()
|
||||
if g["topic"] == topic and g["format"] == req.format), None)
|
||||
if guide and guide["status"] in ("queued", "generating"):
|
||||
return {"ok": True, "status": "generating"}
|
||||
moved = await guide_board.reset_from_stage(topic, req.format, req.ab_stage)
|
||||
return {"ok": True, "moved": moved}
|
||||
|
||||
|
||||
@router.get("/guides/{guide_id}", response_model=GuideResponse)
|
||||
@@ -680,6 +755,7 @@ async def remove(guide_id: str, slots: bool = False):
|
||||
rest = [g for g in await list_guides() if g["topic"] == guide["topic"] and g["format"] == guide["format"]]
|
||||
if not rest:
|
||||
await delete_guide_content(guide["topic"], guide["format"])
|
||||
await delete_guide_board(guide["topic"], guide["format"]) # board cards + lernziele
|
||||
content = guide_content_path(guide["topic"], guide["format"])
|
||||
if slots or content.exists():
|
||||
for p in guide_slot_files(content):
|
||||
|
||||
Reference in New Issue
Block a user