Spaces:
Sleeping
Sleeping
| """ | |
| VibeEngineer endpoint — POST /api/enhance. | |
| Takes current files + a natural-language instruction and returns the complete | |
| updated file set via a single LLM call (same single-pass parse pattern as | |
| the coder node). | |
| """ | |
| import logging | |
| import uuid | |
| from datetime import datetime as _dt, timezone as _tz | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from pydantic import BaseModel | |
| from agent.llm import CODER_CHAIN, CODER_CHAIN_PREMIUM, chat, strip_code_fences | |
| from agent.premium_scaffold import is_premium_shaped, preserve_premium_scaffold | |
| from agent.prompts import CODER_SYSTEM_PROMPT, vibe_prompt, vibe_prompt_premium | |
| from agent.scaffold import (detect_framework, normalize_files, | |
| parse_reply_and_files) | |
| from auth.dependencies import get_current_user | |
| from db.database import SessionLocal | |
| from db.models import Project, User | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/enhance", tags=["enhance"]) | |
| class EnhanceRequest(BaseModel): | |
| project_id: str | None = None | |
| files: dict[str, str] | |
| instruction: str | |
| session_id: str | None = None # ponytail: kept for backward compat, ignored for ownership | |
| premium: bool = False # honored ONLY for SUPER_ADMIN (server-side gate below) | |
| class EnhanceResponse(BaseModel): | |
| project_id: str | |
| files: dict[str, str] | |
| reply: str # ponytail: short first-person message from the VibeEngineer (two-way chat) | |
| def enhance( | |
| req: EnhanceRequest, | |
| current_user: User = Depends(get_current_user), | |
| ) -> EnhanceResponse: | |
| if not req.instruction.strip(): | |
| raise HTTPException(status_code=400, detail="instruction must not be empty") | |
| if not req.files: | |
| raise HTTPException(status_code=400, detail="files must not be empty") | |
| # ponytail: never trust the client flag — premium is honored only for SUPER_ADMIN | |
| # (mirrors api.generate._resolve_premium); a non-admin asking for premium gets standard. | |
| premium = bool(req.premium) and getattr(current_user, "role", "") == "SUPER_ADMIN" | |
| chain = CODER_CHAIN_PREMIUM if premium else CODER_CHAIN | |
| if is_premium_shaped(req.files): | |
| # SCOPED edit: send only the editable surface (not the frozen /ui + /lib kit) | |
| # + the primitive cheatsheet, and ask for ONLY the changed files. This keeps | |
| # a premium edit fast + reliable instead of re-writing all ~22 files. | |
| editable = {p: c for p, c in req.files.items() | |
| if not p.startswith(("/ui/", "/lib/"))} | |
| raw = chat( | |
| vibe_prompt_premium(editable, req.instruction), | |
| chain=chain, system=CODER_SYSTEM_PROMPT, max_tokens=16000, agent="enhance", | |
| ) | |
| reply, changed = parse_reply_and_files(raw, strip_code_fences) | |
| reply = reply or "Applied your changes." | |
| # Merge the partial changes over the current app, then re-freeze the kit + deps. | |
| merged = normalize_files({**req.files, **changed}, "react") | |
| updated_files = preserve_premium_scaffold(req.files, merged) | |
| else: | |
| raw = chat( | |
| vibe_prompt(req.files, req.instruction, premium=premium), | |
| chain=chain, system=CODER_SYSTEM_PROMPT, max_tokens=20000 if premium else 8192, agent="enhance", | |
| ) | |
| reply, parsed = parse_reply_and_files(raw, strip_code_fences) | |
| reply = reply or "Applied your changes." | |
| # ponytail: infer framework so static/next apps aren't React-normalized. | |
| updated_files = normalize_files(parsed, detect_framework(req.files)) | |
| project_id = _upsert_project(req.project_id, current_user.id, req.instruction, updated_files, source="enhance") | |
| return EnhanceResponse(project_id=project_id, files=updated_files, reply=reply) | |
| _MAX_VERSIONS = 15 | |
| def snapshot_version(row: Project, source: str, label: str) -> None: | |
| """Append the row's CURRENT files as a recoverable version BEFORE it's | |
| overwritten, capped at the most recent _MAX_VERSIONS. So every agent change | |
| (enhance/UX/OnCall) or revert preserves the prior state for one-click revert.""" | |
| if not getattr(row, "files", None): | |
| return | |
| entry = { | |
| "id": str(uuid.uuid4()), | |
| "at": _dt.now(_tz.utc).isoformat(), | |
| "source": source, | |
| "label": (label or "").strip()[:80], | |
| "files": row.files, | |
| } | |
| prior = list(row.versions or []) | |
| prior.append(entry) | |
| row.versions = prior[-_MAX_VERSIONS:] | |
| def _upsert_project(project_id: str | None, user_id: uuid.UUID, instruction: str, | |
| files: dict, source: str = "edit") -> str: | |
| """Update existing project (owned by user) or create a new one; returns the project UUID string. | |
| Snapshots the PRIOR files as a version before overwriting an existing row, so | |
| a bad change can be reverted.""" | |
| try: | |
| with SessionLocal() as db: | |
| if project_id: | |
| row = db.get(Project, uuid.UUID(project_id)) | |
| if row and row.user_id == user_id: | |
| snapshot_version(row, source, instruction) | |
| row.files = files | |
| db.commit() | |
| db.refresh(row) | |
| return str(row.id) | |
| # ponytail: if not found / user mismatch, fall through to create | |
| row = Project( | |
| session_id="", | |
| user_id=user_id, | |
| title=f"Enhanced: {instruction[:60]}", | |
| brief=instruction, | |
| files=files, | |
| ) | |
| db.add(row) | |
| db.commit() | |
| db.refresh(row) | |
| return str(row.id) | |
| except Exception as exc: # noqa: BLE001 | |
| logger.error("DB upsert failed: %s", exc) | |
| # ponytail: return a stable placeholder so the FE still gets files | |
| return project_id or str(uuid.uuid4()) | |