Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Backend/.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ password=[YOUR-PASSWORD]
host=
port=5432
dbname=postgres
GROQ_API_KEY=
GROQ_API_KEY=your_groq_api_key_here
SUPABASE_URL=
SUPABASE_KEY=
GEMINI_API_KEY=
Expand Down
4 changes: 4 additions & 0 deletions Backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from .routes.post import router as post_router
from .routes.chat import router as chat_router
from .routes.match import router as match_router
from .routes.brand_dashboard import router as brand_dashboard_router
from .routes.ai_query import router as ai_query_router
from sqlalchemy.exc import SQLAlchemyError
import logging
import os
Expand Down Expand Up @@ -54,6 +56,8 @@ async def lifespan(app: FastAPI):
app.include_router(post_router)
app.include_router(chat_router)
app.include_router(match_router)
app.include_router(brand_dashboard_router)
app.include_router(ai_query_router)
app.include_router(ai.router)
app.include_router(ai.youtube_router)

Expand Down
81 changes: 80 additions & 1 deletion Backend/app/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
TIMESTAMP,
)
from sqlalchemy.orm import relationship
from datetime import datetime
from datetime import datetime, timezone
from app.db.db import Base
import uuid

Expand Down Expand Up @@ -160,3 +160,82 @@ class SponsorshipPayment(Base):
brand = relationship(
"User", foreign_keys=[brand_id], back_populates="brand_payments"
)


# ============================================================================
# BRAND DASHBOARD MODELS
# ============================================================================

# Brand Profile Table (Extended brand information)
class BrandProfile(Base):
__tablename__ = "brand_profiles"

id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False)
company_name = Column(String, nullable=True)
website = Column(String, nullable=True)
industry = Column(String, nullable=True)
contact_person = Column(String, nullable=True)
contact_email = Column(String, nullable=True)
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)

# Relationships
user = relationship("User", backref="brand_profile")


# Campaign Metrics Table (Performance tracking)
class CampaignMetrics(Base):
__tablename__ = "campaign_metrics"

id = Column(String, primary_key=True, default=generate_uuid)
campaign_id = Column(String, ForeignKey("sponsorships.id"), nullable=False)
impressions = Column(Integer, nullable=True)
clicks = Column(Integer, nullable=True)
conversions = Column(Integer, nullable=True)
revenue = Column(DECIMAL(10, 2), nullable=True)
engagement_rate = Column(Float, nullable=True)
recorded_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)

# Relationships
campaign = relationship("Sponsorship", backref="metrics")


# Contracts Table (Contract management)
class Contract(Base):
__tablename__ = "contracts"

id = Column(String, primary_key=True, default=generate_uuid)
sponsorship_id = Column(String, ForeignKey("sponsorships.id"), nullable=False)
creator_id = Column(String, ForeignKey("users.id"), nullable=False)
brand_id = Column(String, ForeignKey("users.id"), nullable=False)
contract_url = Column(String, nullable=True)
status = Column(String, default="draft") # draft, signed, completed, cancelled
created_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)

# Relationships
sponsorship = relationship("Sponsorship", backref="contracts")
creator = relationship("User", foreign_keys=[creator_id], backref="creator_contracts")
brand = relationship("User", foreign_keys=[brand_id], backref="brand_contracts")


# Creator Matches Table (AI-powered matching)
class CreatorMatch(Base):
__tablename__ = "creator_matches"

id = Column(String, primary_key=True, default=generate_uuid)
brand_id = Column(String, ForeignKey("users.id"), nullable=False)
creator_id = Column(String, ForeignKey("users.id"), nullable=False)
match_score = Column(Float, nullable=True)
matched_at = Column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)

# Relationships
brand = relationship("User", foreign_keys=[brand_id], backref="creator_matches")
creator = relationship("User", foreign_keys=[creator_id], backref="brand_matches")
122 changes: 122 additions & 0 deletions Backend/app/routes/ai_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from fastapi import APIRouter, HTTPException, Query, Depends
from typing import Dict, Any, Optional
from pydantic import BaseModel
import logging
from ..services.ai_router import ai_router

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Define Router
router = APIRouter(prefix="/api/ai", tags=["AI Query"])

# Pydantic models for request/response
class AIQueryRequest(BaseModel):
query: str
brand_id: Optional[str] = None
context: Optional[Dict[str, Any]] = None

class AIQueryResponse(BaseModel):
intent: str
route: Optional[str] = None
parameters: Dict[str, Any] = {}
follow_up_needed: bool = False
follow_up_question: Optional[str] = None
explanation: str
original_query: str
timestamp: str

@router.post("/query", response_model=AIQueryResponse)
async def process_ai_query(request: AIQueryRequest):
"""
Process a natural language query through AI and return routing information
"""
try:
# Validate input
if not request.query or len(request.query.strip()) == 0:
raise HTTPException(status_code=400, detail="Query cannot be empty")

# Process query through AI router
result = await ai_router.process_query(
query=request.query.strip(),
brand_id=request.brand_id
)

# Convert to response model
response = AIQueryResponse(
intent=result.get("intent", "unknown"),
route=result.get("route"),
parameters=result.get("parameters", {}),
follow_up_needed=result.get("follow_up_needed", False),
follow_up_question=result.get("follow_up_question"),
explanation=result.get("explanation", ""),
original_query=result.get("original_query", request.query),
timestamp=result.get("timestamp", "")
)

logger.info(f"AI Query processed successfully: '{request.query}' -> {response.intent}")
return response

except HTTPException:
raise
except Exception as e:
logger.error(f"Error processing AI query: {e}")
raise HTTPException(status_code=500, detail="Failed to process AI query")

@router.get("/routes")
async def get_available_routes():
"""
Get list of available routes that the AI can route to
"""
try:
routes = ai_router.list_available_routes()
return {
"available_routes": routes,
"total_routes": len(routes)
}
Comment on lines +73 to +77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return the route list in the shape the client expects

ai_router.list_available_routes() yields a dict, but the frontend’s getAvailableRoutes() expects available_routes: string[]. Emit the keys (and optionally pass details separately) so the contract matches.

-        return {
-            "available_routes": routes,
-            "total_routes": len(routes)
-        }
+        return {
+            "available_routes": list(routes.keys()),
+            "total_routes": len(routes),
+            "route_details": routes,
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
routes = ai_router.list_available_routes()
return {
"available_routes": routes,
"total_routes": len(routes)
}
routes = ai_router.list_available_routes()
return {
"available_routes": list(routes.keys()),
"total_routes": len(routes),
"route_details": routes,
}
🤖 Prompt for AI Agents
In Backend/app/routes/ai_query.py around lines 73 to 77, the handler currently
returns the dict from ai_router.list_available_routes(), but the frontend
expects available_routes to be a list of strings; change the response to emit
available_routes as a list of the dict's keys (e.g., list(routes.keys())) and,
if you need to keep the full dict, return it under a separate field like
route_details so the API contract matches available_routes: string[].

except Exception as e:
logger.error(f"Error fetching available routes: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch routes")

@router.get("/route/{route_name}")
async def get_route_info(route_name: str):
"""
Get detailed information about a specific route
"""
try:
route_info = ai_router.get_route_info(route_name)
if not route_info:
raise HTTPException(status_code=404, detail=f"Route '{route_name}' not found")

return {
"route_name": route_name,
"info": route_info
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching route info: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch route info")

@router.post("/test")
async def test_ai_query(query: str = Query(..., description="Test query")):
"""
Test endpoint for AI query processing (for development)
"""
try:
# Process test query
result = await ai_router.process_query(query=query)

return {
"test_query": query,
"result": result,
"status": "success"
}
Comment on lines +102 to +115
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Let the test endpoint accept GET (or update the client)

The frontend already issues GET /api/ai/test?query=…; with the current @router.post decorator the call will 405. Either change this route to @router.get or adjust the client before merging—just make sure both sides use the same verb.

🧰 Tools
🪛 Ruff (0.13.3)

111-115: Consider moving this statement to an else block

(TRY300)

🤖 Prompt for AI Agents
In Backend/app/routes/ai_query.py around lines 102 to 115, the test endpoint is
currently decorated with @router.post which causes a 405 when the frontend sends
GET; change the route decorator to @router.get("/test") so it accepts query
string GET requests (no other code changes required since the query param is
already declared), or alternatively coordinate with the frontend to call
POST—prefer updating the decorator to @router.get to match existing client
behavior.

except Exception as e:
logger.error(f"Error in test AI query: {e}")
return {
"test_query": query,
"error": str(e),
"status": "error"
}
Loading