Refactor to modular app factory architecture

- Replace 6847-line monolithic server.py with 26-line entry point
- All routes now in app/routers/ using Jinja2 templates
- Add plan_node.html template for step details
- Add plan node route to runs router with cache_id lookup
- Backup old server as server_legacy.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
giles
2026-01-11 11:48:24 +00:00
parent 73b7f173c5
commit 29da91e01a
4 changed files with 7058 additions and 6829 deletions

View File

@@ -309,6 +309,110 @@ async def run_artifacts(
)
@router.get("/{run_id}/plan/node/{cache_id}", response_class=HTMLResponse)
async def plan_node_detail(
run_id: str,
cache_id: str,
request: Request,
run_service: RunService = Depends(get_run_service),
):
"""HTMX partial: Get plan node detail by cache_id."""
from ..services.auth_service import AuthService
from artdag_common import render_fragment
auth_service = AuthService(get_redis_client())
ctx = auth_service.get_user_from_cookie(request)
if not ctx:
return HTMLResponse('<p class="text-red-400">Login required</p>', status_code=401)
run = await run_service.get_run(run_id)
if not run:
return HTMLResponse('<p class="text-red-400">Run not found</p>', status_code=404)
plan = await run_service.get_run_plan(run_id)
if not plan:
return HTMLResponse('<p class="text-gray-400">Plan not found</p>')
# Build lookups
steps_by_cache_id = {}
steps_by_step_id = {}
for s in plan.get("steps", []):
if s.get("cache_id"):
steps_by_cache_id[s["cache_id"]] = s
if s.get("step_id"):
steps_by_step_id[s["step_id"]] = s
step = steps_by_cache_id.get(cache_id)
if not step:
return HTMLResponse(f'<p class="text-gray-400">Step not found</p>')
cache_manager = get_cache_manager()
# Node colors
node_colors = {
"SOURCE": "#3b82f6", "EFFECT": "#22c55e", "OUTPUT": "#a855f7",
"ANALYSIS": "#f59e0b", "_LIST": "#6366f1", "default": "#6b7280"
}
node_color = node_colors.get(step.get("node_type", "EFFECT"), node_colors["default"])
# Check cache status
has_cached = cache_manager.has_content(cache_id) if cache_id else False
# Determine output media type
output_media_type = None
output_preview = False
if has_cached:
cache_path = cache_manager.get_content_path(cache_id)
if cache_path:
output_media_type = run_service.detect_media_type(cache_path)
output_preview = output_media_type in ('video', 'image', 'audio')
# Check for IPFS CID
ipfs_cid = None
if run.step_results:
res = run.step_results.get(step.get("step_id"))
if isinstance(res, dict) and res.get("cid"):
ipfs_cid = res["cid"]
# Build input previews
inputs = []
for inp_step_id in step.get("input_steps", []):
inp_step = steps_by_step_id.get(inp_step_id)
if inp_step:
inp_cache_id = inp_step.get("cache_id", "")
inp_has_cached = cache_manager.has_content(inp_cache_id) if inp_cache_id else False
inp_media_type = None
if inp_has_cached:
inp_path = cache_manager.get_content_path(inp_cache_id)
if inp_path:
inp_media_type = run_service.detect_media_type(inp_path)
inputs.append({
"name": inp_step.get("name", inp_step_id[:12]),
"cache_id": inp_cache_id,
"media_type": inp_media_type,
"has_cached": inp_has_cached,
})
status = "cached" if (has_cached or ipfs_cid) else ("completed" if run.status == "completed" else "pending")
templates = get_templates(request)
return HTMLResponse(render_fragment(templates, "runs/plan_node.html",
step=step,
cache_id=cache_id,
node_color=node_color,
status=status,
has_cached=has_cached,
output_preview=output_preview,
output_media_type=output_media_type,
ipfs_cid=ipfs_cid,
ipfs_gateway="https://ipfs.io/ipfs",
inputs=inputs,
config=step.get("config", {}),
))
@router.delete("/{run_id}/ui", response_class=HTMLResponse)
async def ui_discard_run(
run_id: str,