from __future__ import annotations from typing import Any import uuid from fastapi import APIRouter, Body, File, HTTPException, Query, UploadFile from fastapi.responses import PlainTextResponse from app.db.platform_store import get_platform_store router = APIRouter() def ok(data: Any = None, message: str = "ok") -> dict[str, Any]: return {"code": 0, "message": message, "data": data} def fail(status_code: int, message: str) -> HTTPException: return HTTPException(status_code=status_code, detail={"code": status_code, "message": message, "data": None}) @router.post("/login") async def login(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: user = get_platform_store().login(payload.get("username", ""), payload.get("password", "")) if not user: raise fail(401, "invalid username or password") return ok({"token": f"platform-token-{user['id']}", "user": user}) @router.get("/me") async def me() -> dict[str, Any]: return ok(get_platform_store().users()[0]) @router.get("/dashboard/overview") async def dashboard_overview() -> dict[str, Any]: store = get_platform_store() tasks = store.tasks() return ok( { "models": len(store.models()), "datasets": len(store.datasets()), "fine_tune_tasks": len(tasks), "running_tasks": len([t for t in tasks if t["status"] in {"syncing", "queued", "running"}]), "compute_nodes": len(store.compute_nodes()), "gpus": len(store.gpus()), } ) @router.get("/system-info") async def system_info() -> dict[str, Any]: return ok(get_platform_store().system_info()) @router.get("/users") async def users() -> dict[str, Any]: return ok(get_platform_store().users()) @router.post("/users") async def create_user(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: return ok(get_platform_store().create_user(payload)) @router.put("/users/{user_id}") async def update_user(user_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().update_user(user_id, payload)) except KeyError: raise fail(404, "user not found") @router.delete("/users/{user_id}") async def delete_user(user_id: str, current_username: str | None = Query(default=None)) -> dict[str, Any]: try: get_platform_store().delete_user(user_id) return ok({"deleted": user_id, "current_username": current_username}) except KeyError: raise fail(404, "user not found") except ValueError as exc: raise fail(400, str(exc)) @router.get("/model-manage/local-models") async def local_models() -> dict[str, Any]: models = [{"path": item.get("path") or "", "name": item["name"]} for item in get_platform_store().models()] return ok({"models": models}) @router.get("/model-manage/trained-models") async def trained_models() -> dict[str, Any]: return ok({"models": get_platform_store().trained_models()}) @router.delete("/model-manage/trained-models/{model_id}") async def delete_trained_model(model_id: str, type: str = Query(default="merged")) -> dict[str, Any]: return ok({"deleted": model_id, "type": type}) @router.get("/model-manage/name/{name}") async def model_by_name(name: str) -> dict[str, Any]: try: return ok(get_platform_store().model_by_name(name)) except KeyError: raise fail(404, "model not found") @router.get("/model-manage") async def model_list() -> dict[str, Any]: return ok(get_platform_store().models()) @router.post("/model-manage") async def create_model(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: return ok(get_platform_store().create_model(payload)) @router.get("/model-manage/{model_id}") async def model_detail(model_id: str) -> dict[str, Any]: try: return ok(get_platform_store().model(model_id)) except KeyError: raise fail(404, "model not found") @router.put("/model-manage/{model_id}") async def update_model(model_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().update_model(model_id, payload)) except KeyError: raise fail(404, "model not found") @router.put("/model-manage/{model_id}/purpose") async def update_model_purpose(model_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().update_model(model_id, {"purpose": payload.get("purpose", "training")})) except KeyError: raise fail(404, "model not found") @router.delete("/model-manage/{model_id}") async def delete_model(model_id: str) -> dict[str, Any]: get_platform_store().delete_model(model_id) return ok({"deleted": model_id}) @router.post("/model-manage/merge") async def merge_model(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: return ok({"job_id": f"merge_{uuid.uuid4().hex[:12]}", "status": "queued", **payload}) @router.get("/dataset-manage/preview/{file_id}") async def dataset_preview(file_id: str) -> dict[str, Any]: try: row = get_platform_store().dataset_file(file_id) return ok({"content": row["content"]}) except KeyError: raise fail(404, "dataset file not found") @router.get("/dataset-manage/versions/{file_id}") async def dataset_versions(file_id: str) -> dict[str, Any]: try: return ok(get_platform_store().file_versions(file_id)) except KeyError: raise fail(404, "dataset file not found") @router.get("/dataset-manage/versions/{file_id}/{version_id}") async def dataset_version_content(file_id: str, version_id: str) -> dict[str, Any]: try: row = get_platform_store().dataset_file(file_id) versions = get_platform_store().file_versions(file_id)["versions"] version = next((item for item in versions if item["id"] == version_id), None) if not version: raise KeyError(version_id) return ok({"version": version, "content": row["content"]}) except KeyError: raise fail(404, "dataset version not found") @router.post("/dataset-manage/versions/{file_id}") async def create_dataset_version(file_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().create_file_version(file_id, payload)) except KeyError: raise fail(404, "dataset file not found") @router.put("/dataset-manage/versions/{file_id}/active") async def activate_dataset_version(file_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().activate_file_version(file_id, payload["version_id"])) except KeyError: raise fail(404, "dataset version not found") @router.delete("/dataset-manage/versions/{file_id}/{version_id}") async def delete_dataset_version(file_id: str, version_id: str) -> dict[str, Any]: return ok(get_platform_store().file_versions(file_id)) @router.post("/dataset-manage/upload/{dataset_id}") async def upload_dataset_files(dataset_id: str, files: list[UploadFile] = File(default=[])) -> dict[str, Any]: created: list[dict[str, Any]] = [] store = get_platform_store() try: store.dataset(dataset_id) except KeyError: raise fail(404, "dataset not found") with store.connect() as conn: for file in files: raw = await file.read() content = raw.decode("utf-8", errors="replace") created.append(store.add_dataset_file(conn, dataset_id, file.filename or "upload.jsonl", content)) return ok({"files": created}) @router.get("/dataset-manage/download/{dataset_id}") async def download_dataset(dataset_id: str) -> PlainTextResponse: dataset = get_platform_store().dataset(dataset_id) content = "\n".join([f"{file['name']}" for file in dataset.get("files", [])]) return PlainTextResponse(content, media_type="text/plain") @router.get("/dataset-manage/download/{dataset_id}/{file_id}") async def download_dataset_file(dataset_id: str, file_id: str, version_id: str | None = Query(default=None)) -> PlainTextResponse: row = get_platform_store().dataset_file(file_id) return PlainTextResponse(row["content"], media_type="text/plain") @router.get("/dataset-manage") async def dataset_list() -> dict[str, Any]: return ok(get_platform_store().datasets()) @router.post("/dataset-manage") async def create_dataset(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: dataset = get_platform_store().create_dataset(payload) return ok({"id": dataset["id"]}) @router.get("/dataset-manage/{dataset_id}") async def dataset_detail(dataset_id: str) -> dict[str, Any]: try: return ok(get_platform_store().dataset(dataset_id)) except KeyError: raise fail(404, "dataset not found") @router.put("/dataset-manage/{dataset_id}") async def update_dataset(dataset_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().update_dataset(dataset_id, payload)) except KeyError: raise fail(404, "dataset not found") @router.delete("/dataset-manage/{dataset_id}") async def delete_dataset(dataset_id: str) -> dict[str, Any]: get_platform_store().delete_dataset(dataset_id) return ok({"deleted": dataset_id}) @router.get("/fine-tune/check-name") async def check_fine_tune_name(name: str = Query(...)) -> dict[str, Any]: exists = any(task["name"] == name for task in get_platform_store().tasks()) return ok({"exists": exists}) @router.get("/fine-tune/progress/{task_id}") async def fine_tune_progress(task_id: str) -> dict[str, Any]: try: return ok(get_platform_store().progress(task_id)) except KeyError: raise fail(404, "fine tune task not found") @router.post("/fine-tune/tensorboard/start") async def tensorboard_start() -> dict[str, Any]: return ok({"status": "running", "url": "http://localhost:6006"}) @router.get("/fine-tune") async def fine_tune_list() -> dict[str, Any]: return ok(get_platform_store().tasks()) @router.post("/fine-tune") async def create_fine_tune(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: task = get_platform_store().create_task(payload) return ok({"id": task["id"]}) except ValueError as exc: raise fail(400, str(exc)) @router.post("/fine-tune/start") async def start_fine_tune(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().start_task(payload)) except KeyError: raise fail(404, "fine tune task not found") except RuntimeError as exc: raise fail(409, str(exc)) @router.get("/fine-tune/{task_id}") async def fine_tune_detail(task_id: str) -> dict[str, Any]: try: return ok(get_platform_store().task(task_id)) except KeyError: raise fail(404, "fine tune task not found") @router.put("/fine-tune/{task_id}") async def update_fine_tune(task_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().update_task(task_id, payload)) except KeyError: raise fail(404, "fine tune task not found") @router.post("/fine-tune/stop/{task_id}") async def stop_fine_tune(task_id: str) -> dict[str, Any]: try: return ok(get_platform_store().stop_task(task_id)) except KeyError: raise fail(404, "fine tune task not found") @router.post("/fine-tune/{task_id}/stop") async def stop_fine_tune_alt(task_id: str) -> dict[str, Any]: return await stop_fine_tune(task_id) @router.delete("/fine-tune/{task_id}") async def delete_fine_tune(task_id: str) -> dict[str, Any]: get_platform_store().delete_task(task_id) return ok({"deleted": task_id}) @router.get("/fine-tune/{task_id}/overview") async def fine_tune_overview(task_id: str) -> dict[str, Any]: task = get_platform_store().task(task_id) return ok({"task": task, "progress": get_platform_store().progress(task_id)}) @router.get("/fine-tune/{task_id}/checkpoints") async def fine_tune_checkpoints(task_id: str) -> dict[str, Any]: task = get_platform_store().task(task_id) checkpoints = [] for step in [50, 100, 150]: if task.get("progress", 0) >= min(100, step // 2): checkpoints.append({"step": step, "path": f"/data/yg-ft/outputs/{task['name']}/checkpoint-{step}"}) return ok(checkpoints) @router.get("/compute/nodes") async def compute_nodes() -> dict[str, Any]: return ok(get_platform_store().compute_nodes()) @router.post("/compute/nodes") async def create_compute_node(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().create_compute_node(payload)) except KeyError as exc: raise fail(400, f"missing field: {exc}") @router.put("/compute/nodes/{node_id}") async def update_compute_node(node_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]: try: return ok(get_platform_store().update_compute_node(node_id, payload)) except KeyError: raise fail(404, "compute node not found") @router.post("/compute/nodes/{node_id}/test-connection") async def test_compute_node(node_id: str) -> dict[str, Any]: return ok({"node_id": node_id, "success": True, "latency_ms": 12}) @router.post("/compute/nodes/{node_id}/enable") async def enable_compute_node(node_id: str) -> dict[str, Any]: return ok(get_platform_store().update_compute_node(node_id, {"enabled": True, "scheduler_status": "online"})) @router.post("/compute/nodes/{node_id}/disable") async def disable_compute_node(node_id: str) -> dict[str, Any]: return ok(get_platform_store().update_compute_node(node_id, {"enabled": False, "scheduler_status": "offline"})) @router.post("/compute/nodes/{node_id}/drain") async def drain_compute_node(node_id: str) -> dict[str, Any]: return ok(get_platform_store().update_compute_node(node_id, {"scheduler_status": "draining"})) @router.get("/compute/nodes/{node_id}/replicas") async def compute_node_replicas(node_id: str) -> dict[str, Any]: return ok(get_platform_store().replicas(node_id)) @router.get("/compute/gpus") async def compute_gpus() -> dict[str, Any]: return ok(get_platform_store().gpus()) @router.get("/compute/queue") async def compute_queue() -> dict[str, Any]: return ok(get_platform_store().queue()) @router.post("/internal/compute-sync/resources") async def create_compute_sync(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: sync_id = get_platform_store().create_sync_job(payload.get("target_node_id", "node_01"), payload) return ok(get_platform_store().sync_job(sync_id)) @router.get("/internal/compute-sync/resources/{sync_id}") async def compute_sync_detail(sync_id: str) -> dict[str, Any]: try: return ok(get_platform_store().sync_job(sync_id)) except KeyError: raise fail(404, "sync job not found") @router.get("/training-log-files") async def training_log_files() -> dict[str, Any]: return ok(get_platform_store().training_log_files()) @router.get("/training-log-content") async def training_log_content(file: str = Query(...)) -> dict[str, Any]: try: return ok(get_platform_store().training_log_content(file)) except KeyError: raise fail(404, "training log not found") @router.get("/log-files") async def log_files(date: str | None = Query(default=None)) -> dict[str, Any]: return ok(get_platform_store().log_files(date)) @router.get("/log-content") async def log_content(file: str = Query(...)) -> dict[str, Any]: return ok(get_platform_store().log_content(file)) @router.post("/web-log") async def web_log(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: return ok({"received": True, **payload})