""" API endpoints for LLM-powered naming features. Used by Trends and Forge tabs in the Hunt page. """ from __future__ import annotations from typing import Optional from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, Field from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user from app.database import get_db from app.models.subscription import Subscription, SubscriptionTier from app.models.user import User from app.services.llm_naming import ( expand_trend_keywords, analyze_trend, generate_brandable_names, generate_similar_names, ) router = APIRouter(prefix="/naming", tags=["LLM Naming"]) def _tier_level(tier: str) -> int: t = (tier or "").lower() if t == "tycoon": return 3 if t == "trader": return 2 return 1 async def _get_user_tier(db: AsyncSession, user: User) -> str: res = await db.execute(select(Subscription).where(Subscription.user_id == user.id)) sub = res.scalar_one_or_none() if not sub: return "scout" return sub.tier.value async def _require_trader_or_above(db: AsyncSession, user: User): """Check that user has at least Trader tier.""" tier = await _get_user_tier(db, user) if _tier_level(tier) < 2: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="AI naming features require Trader or Tycoon plan." ) # ============================================================================ # TRENDS TAB ENDPOINTS # ============================================================================ class TrendExpandRequest(BaseModel): trend: str = Field(..., min_length=1, max_length=100) geo: str = Field(default="US", max_length=5) class TrendExpandResponse(BaseModel): keywords: list[str] trend: str @router.post("/trends/expand", response_model=TrendExpandResponse) async def expand_trend( request: TrendExpandRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ Expand a trending topic into related domain-friendly keywords. Requires Trader or Tycoon subscription. """ await _require_trader_or_above(db, current_user) keywords = await expand_trend_keywords(request.trend, request.geo) return TrendExpandResponse(keywords=keywords, trend=request.trend) class TrendAnalyzeRequest(BaseModel): trend: str = Field(..., min_length=1, max_length=100) geo: str = Field(default="US", max_length=5) class TrendAnalyzeResponse(BaseModel): analysis: str trend: str @router.post("/trends/analyze", response_model=TrendAnalyzeResponse) async def analyze_trend_endpoint( request: TrendAnalyzeRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ Get AI analysis of a trending topic for domain investors. Requires Trader or Tycoon subscription. """ await _require_trader_or_above(db, current_user) analysis = await analyze_trend(request.trend, request.geo) return TrendAnalyzeResponse(analysis=analysis, trend=request.trend) # ============================================================================ # FORGE TAB ENDPOINTS # ============================================================================ class BrandableGenerateRequest(BaseModel): concept: str = Field(..., min_length=3, max_length=200) style: Optional[str] = Field(default=None, max_length=50) count: int = Field(default=15, ge=5, le=30) class BrandableGenerateResponse(BaseModel): names: list[str] concept: str @router.post("/forge/generate", response_model=BrandableGenerateResponse) async def generate_brandables( request: BrandableGenerateRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ Generate brandable domain names based on a concept description. Requires Trader or Tycoon subscription. """ await _require_trader_or_above(db, current_user) names = await generate_brandable_names( request.concept, style=request.style, count=request.count ) return BrandableGenerateResponse(names=names, concept=request.concept) class SimilarNamesRequest(BaseModel): brand: str = Field(..., min_length=2, max_length=50) count: int = Field(default=12, ge=5, le=20) class SimilarNamesResponse(BaseModel): names: list[str] brand: str @router.post("/forge/similar", response_model=SimilarNamesResponse) async def generate_similar( request: SimilarNamesRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ Generate names similar to an existing brand. Requires Trader or Tycoon subscription. """ await _require_trader_or_above(db, current_user) names = await generate_similar_names(request.brand, count=request.count) return SimilarNamesResponse(names=names, brand=request.brand)