from fastapi import FastAPI, APIRouter from dotenv import load_dotenv from starlette.middleware.cors import CORSMiddleware from motor.motor_asyncio import AsyncIOMotorClient import os import logging from pathlib import Path from pydantic import BaseModel, Field, ConfigDict from typing import List, Optional import uuid from datetime import datetime, timezone from emergentintegrations.llm.chat import LlmChat, UserMessage ROOT_DIR = Path(__file__).parent load_dotenv(ROOT_DIR / '.env') # MongoDB connection mongo_url = os.environ['MONGO_URL'] client = AsyncIOMotorClient(mongo_url) db = client[os.environ['DB_NAME']] # Create the main app without a prefix app = FastAPI() # Create a router with the /api prefix api_router = APIRouter(prefix="/api") # Define Models class StatusCheck(BaseModel): model_config = ConfigDict(extra="ignore") # Ignore MongoDB's _id field id: str = Field(default_factory=lambda: str(uuid.uuid4())) client_name: str timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class StatusCheckCreate(BaseModel): client_name: str # Add your routes to the router instead of directly to app @api_router.get("/") async def root(): return {"message": "Hello World"} @api_router.post("/status", response_model=StatusCheck) async def create_status_check(input: StatusCheckCreate): status_dict = input.model_dump() status_obj = StatusCheck(**status_dict) # Convert to dict and serialize datetime to ISO string for MongoDB doc = status_obj.model_dump() doc['timestamp'] = doc['timestamp'].isoformat() _ = await db.status_checks.insert_one(doc) return status_obj @api_router.get("/status", response_model=List[StatusCheck]) async def get_status_checks(): # Exclude MongoDB's _id field from the query results status_checks = await db.status_checks.find({}, {"_id": 0}).to_list(1000) # Convert ISO string timestamps back to datetime objects for check in status_checks: if isinstance(check['timestamp'], str): check['timestamp'] = datetime.fromisoformat(check['timestamp']) return status_checks # Tag Generation Models class GenerateTagsRequest(BaseModel): anchors: List[dict] images: Optional[List[dict]] = [] class GenerateTagsResponse(BaseModel): tags: List[str] @api_router.get("/quick-add") async def quick_add_item(label: str = "", url: str = ""): """ Quick add endpoint for creating items from URL parameters Example: /api/quick-add?label=GitHub&url=https://github.com """ try: if not label and not url: return {"success": False, "error": "Both label and url are required"} # Create a simple item with one link item_data = { "anchors": [ { "id": str(uuid.uuid4()), "type": "link", "label": label or "Quick Add", "url": url or "" } ], "images": [], "attachments": [], "tags": ["quick-add"], "createdAt": datetime.utcnow().isoformat(), "updatedAt": datetime.utcnow().isoformat() } result = await db.list_items.insert_one(item_data) return { "success": True, "id": str(result.inserted_id), "message": f"Added: {label}" } except Exception as e: logger.error(f"Error in quick-add: {e}") return {"success": False, "error": str(e)} @api_router.post("/generate-tags", response_model=GenerateTagsResponse) async def generate_tags(request: GenerateTagsRequest): try: # Build content description content_parts = [] # Add link information links = [a for a in request.anchors if a.get('type') == 'link'] if links: link_descriptions = [f"- {link.get('label', 'Link')}: {link.get('url', '')}" for link in links] content_parts.append("Links:\n" + "\n".join(link_descriptions)) # Add note information notes = [a for a in request.anchors if a.get('type') == 'note'] if notes: note_texts = [note.get('note', '') for note in notes if note.get('note')] if note_texts: content_parts.append("Notes:\n" + "\n".join(note_texts)) # Add image information if request.images: image_alts = [img.get('alt', '') for img in request.images if img.get('alt')] if image_alts: content_parts.append("Images: " + ", ".join(image_alts)) content = "\n\n".join(content_parts) if not content.strip(): return GenerateTagsResponse(tags=[]) # Initialize LLM chat api_key = os.environ.get('EMERGENT_LLM_KEY') chat = LlmChat( api_key=api_key, session_id=str(uuid.uuid4()), system_message="You are a helpful assistant that generates relevant tags/keywords for content. Generate 3-5 short, relevant keywords (1-2 words each). Return ONLY the keywords separated by commas, nothing else." ).with_model("openai", "gpt-4o-mini") # Create user message user_message = UserMessage( text=f"Generate 3-5 relevant tags/keywords for this content:\n\n{content}\n\nReturn only the tags separated by commas." ) # Get AI response response = await chat.send_message(user_message) # Parse tags from response tags_text = response.strip() tags = [tag.strip() for tag in tags_text.split(',') if tag.strip()] # Limit to 5 tags and clean them tags = tags[:5] tags = [tag.lower().replace(' ', '-') for tag in tags] return GenerateTagsResponse(tags=tags) except Exception as e: logger.error(f"Error generating tags: {e}") return GenerateTagsResponse(tags=[]) # Include the router in the main app app.include_router(api_router) app.add_middleware( CORSMiddleware, allow_credentials=True, allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','), allow_methods=["*"], allow_headers=["*"], ) # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) @app.on_event("shutdown") async def shutdown_db_client(): client.close()