diff --git a/agent-office/app/service_proxy.py b/agent-office/app/service_proxy.py index 15af394..cace022 100644 --- a/agent-office/app/service_proxy.py +++ b/agent-office/app/service_proxy.py @@ -352,6 +352,25 @@ async def list_active_pipelines() -> list[dict]: return resp.json().get("pipelines", []) +async def list_failed_pipelines() -> list[dict]: + async with httpx.AsyncClient(timeout=10) as client: + resp = await client.get(f"{MUSIC_LAB_URL}/api/music/pipeline?status=failed") + resp.raise_for_status() + data = resp.json() + return data if isinstance(data, list) else data.get("items", data.get("pipelines", [])) + + +async def pipeline_retry(pid: int) -> dict: + async with httpx.AsyncClient(timeout=15) as client: + resp = await client.post(f"{MUSIC_LAB_URL}/api/music/pipeline/{pid}/retry") + out = {"status_code": resp.status_code} + try: + out.update(resp.json()) + except Exception: + pass + return out + + async def get_pipeline(pid: int) -> dict: async with httpx.AsyncClient(timeout=15) as client: resp = await client.get(f"{MUSIC_LAB_URL}/api/music/pipeline/{pid}") diff --git a/music-lab/app/db.py b/music-lab/app/db.py index e1d9ea1..fe71a2f 100644 --- a/music-lab/app/db.py +++ b/music-lab/app/db.py @@ -1135,6 +1135,21 @@ def list_pipelines(active_only: bool = False) -> List[Dict[str, Any]]: return [_parse_pipeline_row(r) for r in rows] +def list_pipelines_by_state(state: str) -> List[Dict[str, Any]]: + """특정 state의 파이프라인만 조회 (예: 'failed').""" + sql = """ + SELECT vp.*, ml.title AS track_title, cj.title AS compile_title + FROM video_pipelines vp + LEFT JOIN music_library ml ON ml.id = vp.track_id + LEFT JOIN compile_jobs cj ON cj.id = vp.compile_job_id + WHERE vp.state = ? + ORDER BY vp.created_at DESC + """ + with _conn() as conn: + rows = conn.execute(sql, (state,)).fetchall() + return [_parse_pipeline_row(r) for r in rows] + + def increment_feedback_count(pid: int, step: str) -> int: """원자적으로 feedback_count_per_step.를 +1 한 뒤 새 값을 반환. diff --git a/music-lab/app/main.py b/music-lab/app/main.py index 0574030..7747579 100644 --- a/music-lab/app/main.py +++ b/music-lab/app/main.py @@ -1030,7 +1030,12 @@ def create_pipeline(req: PipelineCreate): @app.get("/api/music/pipeline") def list_pipelines_endpoint(status: str = "all"): - pipelines = _db_module.list_pipelines(active_only=(status == "active")) + if status == "active": + pipelines = _db_module.list_pipelines(active_only=True) + elif status == "failed": + pipelines = _db_module.list_pipelines_by_state("failed") + else: + pipelines = _db_module.list_pipelines(active_only=False) return {"pipelines": pipelines} diff --git a/music-lab/tests/test_pipeline_endpoints.py b/music-lab/tests/test_pipeline_endpoints.py index abbcad7..208319b 100644 --- a/music-lab/tests/test_pipeline_endpoints.py +++ b/music-lab/tests/test_pipeline_endpoints.py @@ -52,6 +52,19 @@ def test_list_pipelines_active_filter(client): assert all(p["state"] != "published" for p in r.json()["pipelines"]) +def test_list_pipelines_failed_filter(client): + """status=failed 필터는 state='failed' 파이프라인만 반환한다.""" + # failed 파이프라인 생성 + pid_f = client.post("/api/music/pipeline", json={"track_id": 1}).json()["id"] + db.update_pipeline_state(pid_f, "failed", failed_reason="cover: oops") + r = client.get("/api/music/pipeline?status=failed") + assert r.status_code == 200 + pipelines = r.json()["pipelines"] + assert len(pipelines) == 1 + assert pipelines[0]["state"] == "failed" + assert pipelines[0]["id"] == pid_f + + def test_feedback_reject_records_feedback_and_increments_count(client): pid = client.post("/api/music/pipeline", json={"track_id": 1}).json()["id"] db.update_pipeline_state(pid, "cover_pending")