from fastapi import APIRouter, HTTPException, Depends, status from app.models import ( ChatbotCreate, ChatbotUpdate, ChatbotResponse, SuccessResponse ) from app.database import get_supabase from app.dependencies import get_current_user, get_user_subscription from app.services.vector_store import vector_store from app.config import PLAN_LIMITS from typing import List import uuid import logging logger = logging.getLogger(__name__) router = APIRouter(prefix="/chatbots", tags=["Chatbots"]) def _get_user_company(user_id: str, supabase) -> dict: result = supabase.table("companies").select("*").eq("owner_id", user_id).execute() if not result.data: raise HTTPException(status_code=404, detail="Company not found") return result.data[0] async def _check_plan_limits(user_id: str, supabase, action: str = "create"): sub = supabase.table("subscriptions").select("*").eq("user_id", user_id).eq("status", "active").execute() plan = sub.data[0]["plan"] if sub.data else "free" limits = PLAN_LIMITS[plan] if action == "publish": published = supabase.table("chatbots").select("id", count="exact") \ .eq("company_id", _get_user_company(user_id, supabase)["id"]) \ .eq("is_published", True).execute() count = published.count or 0 max_pub = limits["max_published"] if max_pub == 0: raise HTTPException(status_code=402, detail="Upgrade to publish chatbots to marketplace") if count >= max_pub: raise HTTPException( status_code=402, detail=f"Publish limit reached ({max_pub}). Upgrade to publish more chatbots." ) return plan @router.post("", response_model=ChatbotResponse, status_code=201) async def create_chatbot(data: ChatbotCreate, user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) # Create Qdrant collection collection_name = f"company_{company['id']}_{uuid.uuid4().hex[:8]}" try: vector_store.create_collection(collection_name) except Exception as e: logger.error(f"Failed to create Qdrant collection: {e}") # Continue without vector store for now collection_name = None chatbot_data = { "id": str(uuid.uuid4()), "company_id": company["id"], "name": data.name, "description": data.description, "system_prompt": data.system_prompt, "model": data.model, "temperature": data.temperature, "max_tokens": data.max_tokens, "primary_color": data.primary_color, "welcome_message": data.welcome_message, "category": data.category, "industry": data.industry, "languages": data.languages, "visibility": "preview", "is_published": False, "qdrant_collection_name": collection_name, } result = supabase.table("chatbots").insert(chatbot_data).execute() if not result.data: raise HTTPException(status_code=500, detail="Failed to create chatbot") return _format_chatbot(result.data[0], supabase) @router.get("", response_model=List[ChatbotResponse]) async def list_chatbots(user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) result = supabase.table("chatbots").select("*") \ .eq("company_id", company["id"]) \ .order("created_at", desc=True) \ .execute() return [_format_chatbot(c, supabase) for c in (result.data or [])] @router.get("/{chatbot_id}", response_model=ChatbotResponse) async def get_chatbot(chatbot_id: str, user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) chatbot = _get_owned_chatbot(chatbot_id, company["id"], supabase) return _format_chatbot(chatbot, supabase) @router.put("/{chatbot_id}", response_model=ChatbotResponse) async def update_chatbot(chatbot_id: str, data: ChatbotUpdate, user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) _get_owned_chatbot(chatbot_id, company["id"], supabase) update_data = {k: v for k, v in data.model_dump().items() if v is not None} if not update_data: raise HTTPException(status_code=400, detail="No fields to update") result = supabase.table("chatbots").update(update_data).eq("id", chatbot_id).execute() if not result.data: raise HTTPException(status_code=500, detail="Update failed") return _format_chatbot(result.data[0], supabase) @router.delete("/{chatbot_id}", response_model=SuccessResponse) async def delete_chatbot(chatbot_id: str, user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) chatbot = _get_owned_chatbot(chatbot_id, company["id"], supabase) # Delete Qdrant collection if chatbot.get("qdrant_collection_name"): try: vector_store.delete_collection(chatbot["qdrant_collection_name"]) except Exception as e: logger.warning(f"Failed to delete collection: {e}") supabase.table("chatbots").delete().eq("id", chatbot_id).execute() return SuccessResponse(success=True, message="Chatbot deleted") @router.post("/{chatbot_id}/publish", response_model=ChatbotResponse) async def publish_chatbot(chatbot_id: str, user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) chatbot = _get_owned_chatbot(chatbot_id, company["id"], supabase) await _check_plan_limits(user.id, supabase, "publish") result = supabase.table("chatbots").update({ "is_published": True, "visibility": "published", }).eq("id", chatbot_id).execute() return _format_chatbot(result.data[0], supabase) @router.post("/{chatbot_id}/unpublish", response_model=ChatbotResponse) async def unpublish_chatbot(chatbot_id: str, user=Depends(get_current_user)): supabase = get_supabase() company = _get_user_company(user.id, supabase) _get_owned_chatbot(chatbot_id, company["id"], supabase) result = supabase.table("chatbots").update({ "is_published": False, "visibility": "preview", }).eq("id", chatbot_id).execute() return _format_chatbot(result.data[0], supabase) @router.post("/{chatbot_id}/export") async def export_chatbot(chatbot_id: str, user=Depends(get_current_user)): from fastapi.responses import StreamingResponse from app.services.code_export import generate_export_package from app.config import settings supabase = get_supabase() company = _get_user_company(user.id, supabase) chatbot = _get_owned_chatbot(chatbot_id, company["id"], supabase) # Check plan sub = supabase.table("subscriptions").select("plan").eq("user_id", user.id).eq("status", "active").execute() plan = sub.data[0]["plan"] if sub.data else "free" if plan not in ("pro", "enterprise"): raise HTTPException(status_code=402, detail="Code export requires Pro plan or higher") zip_bytes = generate_export_package( chatbot=chatbot, company=company, qdrant_url=settings.qdrant_url, qdrant_key=settings.qdrant_api_key or "", ) filename = chatbot["name"].lower().replace(" ", "-") + "-chatbot.zip" return StreamingResponse( iter([zip_bytes]), media_type="application/zip", headers={"Content-Disposition": f"attachment; filename={filename}"}, ) # ── Helpers ─────────────────────────────────────────────────────────────────── def _get_owned_chatbot(chatbot_id: str, company_id: str, supabase) -> dict: result = supabase.table("chatbots").select("*").eq("id", chatbot_id).eq("company_id", company_id).execute() if not result.data: raise HTTPException(status_code=404, detail="Chatbot not found") return result.data[0] def _format_chatbot(chatbot: dict, supabase) -> ChatbotResponse: doc_count = supabase.table("documents").select("id", count="exact") \ .eq("chatbot_id", chatbot["id"]) \ .eq("status", "completed") \ .execute() conv_count = supabase.table("conversations").select("id", count="exact") \ .eq("chatbot_id", chatbot["id"]) \ .execute() return ChatbotResponse( id=chatbot["id"], company_id=chatbot["company_id"], name=chatbot["name"], description=chatbot.get("description"), system_prompt=chatbot.get("system_prompt"), model=chatbot.get("model", "accounts/fireworks/models/kimi-k2-instruct-0905"), temperature=chatbot.get("temperature", 0.7), max_tokens=chatbot.get("max_tokens", 1000), primary_color=chatbot.get("primary_color", "#6366f1"), welcome_message=chatbot.get("welcome_message", "Hello! How can I help?"), category=chatbot.get("category"), industry=chatbot.get("industry"), languages=chatbot.get("languages", ["en"]), visibility=chatbot.get("visibility", "preview"), is_published=chatbot.get("is_published", False), qdrant_collection_name=chatbot.get("qdrant_collection_name"), document_count=doc_count.count or 0, conversation_count=conv_count.count or 0, created_at=chatbot.get("created_at"), published_at=chatbot.get("published_at"), )