import json import os from fastapi import APIRouter, HTTPException from pydantic import BaseModel from services import sdbackend as sd_service from services.comfy_models import list_node_types, parse_model_lists from services.llm import ( CHAT_MODEL, LLM_FALLBACK_MODEL, LLMError, SYSTEM_MODEL, send_message, send_message_with_model, ) from services.personas import get_all_personas from services.sd_prompt import ( SD_PROMPT_MODEL, anima_dual_enabled, run_prompt_builder, ) router = APIRouter(prefix="/debug", tags=["debug"]) class ChatMessage(BaseModel): role: str content: str class SdPromptDebugRequest(BaseModel): persona_id: str = "default" chat_excerpt: str = "" messages: list[ChatMessage] | None = None outfit_json: str = "[]" appearance_override: str | None = None use_prose: bool = False class LlmDebugRequest(BaseModel): model: str = "" system: str = "" user: str = "" messages: list[ChatMessage] | None = None class ComfyRawRequest(BaseModel): method: str = "GET" path: str = "/system_stats" params_json: str = "{}" body_json: str = "" class ComfyGenerateRequest(BaseModel): positive: str negative: str = "" unet: str | None = None clip: str | None = None vae: str | None = None checkpoint: str | None = None @router.get("/config") async def debug_config(): base = sd_service.SD_BASE_URL return { "chat_model": CHAT_MODEL, "system_model": SYSTEM_MODEL, "llm_fallback_model": LLM_FALLBACK_MODEL, "sd_prompt_model": SD_PROMPT_MODEL or SYSTEM_MODEL, "sd_base_url": base, "sd_has_token": bool(sd_service.SD_QUERY_PARAMS.get("token")), "sd_anima_dual": anima_dual_enabled(), "sd_unet": sd_service.SD_UNET, "sd_clip": sd_service.SD_CLIP, "sd_vae": sd_service.SD_VAE, "sd_checkpoint": sd_service.SD_CHECKPOINT, "sd_steps": sd_service.SD_STEPS, "sd_cfg": sd_service.SD_CFG, "router_key_set": bool(os.getenv("ROUTER_KEY")), } @router.get("/personas") async def debug_personas(): personas = await get_all_personas() return [ { "persona_id": pid, "name": p.get("name", pid), "appearance_tags": p.get("appearance_tags", ""), } for pid, p in personas.items() ] @router.post("/sd-prompt") async def debug_sd_prompt(req: SdPromptDebugRequest): msgs = None if req.messages: msgs = [m.model_dump() for m in req.messages] return await run_prompt_builder( req.persona_id, messages=msgs, chat_excerpt=req.chat_excerpt, outfit_json=req.outfit_json, appearance_override=req.appearance_override, use_prose=req.use_prose, ) @router.post("/llm") async def debug_llm(req: LlmDebugRequest): if req.messages: messages = [m.model_dump() for m in req.messages] else: messages = [] if req.system.strip(): messages.append({"role": "system", "content": req.system.strip()}) if req.user.strip(): messages.append({"role": "user", "content": req.user.strip()}) if not messages: raise HTTPException(status_code=400, detail="Нужны messages или system/user") model = (req.model or "").strip() or SD_PROMPT_MODEL or SYSTEM_MODEL try: if model in (SYSTEM_MODEL, "") and not req.model: text = await send_message(messages) else: text = await send_message_with_model(messages, model) return {"model": model, "response": text} except LLMError as e: raise HTTPException(status_code=502, detail=str(e)) @router.get("/comfy/ping") async def debug_comfy_ping(): try: status, body, headers = await sd_service.comfy_api_request("GET", "/system_stats") return {"ok": status == 200, "status": status, "body": body, "headers": headers} except Exception as e: return {"ok": False, "error": str(e)} @router.get("/comfy/models") async def debug_comfy_models(): try: info = await sd_service.fetch_object_info() return { "models": parse_model_lists(info), "configured": { "unet": sd_service.SD_UNET, "clip": sd_service.SD_CLIP, "vae": sd_service.SD_VAE, "checkpoint": sd_service.SD_CHECKPOINT, }, "node_type_count": len(list_node_types(info)), } except Exception as e: raise HTTPException(status_code=502, detail=str(e)) @router.get("/comfy/object_info") async def debug_comfy_object_info(node: str | None = None): try: info = await sd_service.fetch_object_info() if node: if node not in info: raise HTTPException(status_code=404, detail=f"Unknown node: {node}") return {node: info[node]} return { "node_types": list_node_types(info), "models": parse_model_lists(info), } except HTTPException: raise except Exception as e: raise HTTPException(status_code=502, detail=str(e)) @router.post("/comfy/raw") async def debug_comfy_raw(req: ComfyRawRequest): path = req.path.strip() if not path.startswith("/"): path = "/" + path try: params = json.loads(req.params_json or "{}") if not isinstance(params, dict): raise ValueError("params_json must be object") except json.JSONDecodeError as e: raise HTTPException(status_code=400, detail=f"params_json: {e}") body = None if req.body_json.strip(): try: body = json.loads(req.body_json) except json.JSONDecodeError as e: raise HTTPException(status_code=400, detail=f"body_json: {e}") method = req.method.upper() if method not in ("GET", "POST", "PUT", "DELETE"): raise HTTPException(status_code=400, detail="method must be GET|POST|PUT|DELETE") try: status, resp_body, headers = await sd_service.comfy_api_request( method, path, params=params or None, json_body=body, timeout=120, ) return {"status": status, "headers": headers, "body": resp_body} except Exception as e: raise HTTPException(status_code=502, detail=str(e)) @router.post("/comfy/generate") async def debug_comfy_generate(req: ComfyGenerateRequest): if not req.positive.strip(): raise HTTPException(status_code=400, detail="positive required") overrides: dict[str, str] = {} if req.unet: overrides["unet"] = req.unet if req.clip: overrides["clip"] = req.clip if req.vae: overrides["vae"] = req.vae if req.checkpoint: overrides["checkpoint"] = req.checkpoint full = req.positive.strip() if req.negative.strip(): full += f"\n\nNegative prompt: {req.negative.strip()}" try: rel, err = await sd_service.generate_from_full_prompt( full, overrides=overrides or None, ) if not rel: raise HTTPException(status_code=502, detail=err or "generation failed") return {"image_path": f"/static/{rel}", "status": "ok"} except HTTPException: raise except Exception as e: raise HTTPException(status_code=502, detail=str(e))