mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-12 23:23:21 +00:00
306 lines
12 KiB
Python
306 lines
12 KiB
Python
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}")
|
|
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,
|
|
"logo_url": data.logo_url,
|
|
"category": data.category,
|
|
"industry": data.industry,
|
|
"languages": data.languages,
|
|
"visibility": "preview",
|
|
"is_published": False,
|
|
"qdrant_collection_name": collection_name,
|
|
"show_branding": data.show_branding,
|
|
"lead_capture_enabled": data.lead_capture_enabled,
|
|
"lead_capture_fields": data.lead_capture_fields,
|
|
"lead_capture_trigger": data.lead_capture_trigger,
|
|
"handoff_enabled": data.handoff_enabled,
|
|
"handoff_message": data.handoff_message,
|
|
"handoff_email": data.handoff_email,
|
|
"handoff_keywords": data.handoff_keywords,
|
|
}
|
|
|
|
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 ("agency", "enterprise"):
|
|
raise HTTPException(status_code=402, detail="Code export requires Agency 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}"},
|
|
)
|
|
|
|
|
|
@router.get("/{chatbot_id}/public")
|
|
async def get_chatbot_public(chatbot_id: str):
|
|
"""Public endpoint - returns basic info for a published chatbot (used by PublicChatPage)."""
|
|
supabase = get_supabase()
|
|
result = supabase.table("chatbots").select("id, name, welcome_message, primary_color, logo_url, show_branding, is_published, description").eq("id", chatbot_id).execute()
|
|
if not result.data:
|
|
raise HTTPException(status_code=404, detail="Chatbot not found")
|
|
chatbot = result.data[0]
|
|
if not chatbot.get("is_published"):
|
|
raise HTTPException(status_code=404, detail="Chatbot not found or not published")
|
|
return {
|
|
"id": chatbot["id"],
|
|
"name": chatbot["name"],
|
|
"welcome_message": chatbot.get("welcome_message", "Hello! How can I help?"),
|
|
"primary_color": chatbot.get("primary_color", "#6366f1"),
|
|
"logo_url": chatbot.get("logo_url"),
|
|
"show_branding": chatbot.get("show_branding", True),
|
|
"is_published": chatbot.get("is_published", False),
|
|
"description": chatbot.get("description"),
|
|
}
|
|
|
|
|
|
@router.get("/{chatbot_id}/embed")
|
|
async def get_chatbot_embed(chatbot_id: str, user=Depends(get_current_user)):
|
|
"""Returns embed info including the widget script tag for a chatbot."""
|
|
from app.config import settings
|
|
supabase = get_supabase()
|
|
company = _get_user_company(user.id, supabase)
|
|
chatbot = _get_owned_chatbot(chatbot_id, company["id"], supabase)
|
|
|
|
api_url = "http://localhost:8000" # In production, read from settings
|
|
app_url = settings.app_url
|
|
|
|
embed_script = f'<script src="{api_url}/widget.js" data-chatbot="{chatbot_id}"></script>'
|
|
chat_url = f"{app_url}/chat/{chatbot_id}"
|
|
|
|
return {
|
|
"chatbot_id": chatbot_id,
|
|
"name": chatbot.get("name"),
|
|
"primary_color": chatbot.get("primary_color", "#6366f1"),
|
|
"welcome_message": chatbot.get("welcome_message"),
|
|
"logo_url": chatbot.get("logo_url"),
|
|
"show_branding": chatbot.get("show_branding", True),
|
|
"embed_script": embed_script,
|
|
"chat_url": chat_url,
|
|
"is_published": chatbot.get("is_published", False),
|
|
}
|
|
|
|
|
|
# ── 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?"),
|
|
logo_url=chatbot.get("logo_url"),
|
|
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"),
|
|
show_branding=chatbot.get("show_branding", True),
|
|
lead_capture_enabled=chatbot.get("lead_capture_enabled", False),
|
|
lead_capture_fields=chatbot.get("lead_capture_fields") or ["email"],
|
|
lead_capture_trigger=chatbot.get("lead_capture_trigger", "after_first_message"),
|
|
handoff_enabled=chatbot.get("handoff_enabled", False),
|
|
handoff_message=chatbot.get("handoff_message", "I'll connect you with our team. Please wait."),
|
|
handoff_email=chatbot.get("handoff_email"),
|
|
handoff_keywords=chatbot.get("handoff_keywords") or ["human", "agent", "speak to someone", "talk to a person", "real person"],
|
|
) |