import os import threading from concurrent.futures import ThreadPoolExecutor from bson import ObjectId from fastapi import FastAPI from fastapi.encoders import ENCODERS_BY_TYPE from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from google import genai from pymongo import ASCENDING, MongoClient app = FastAPI(title="Nomus AI Agent Calendar V3 – Streaming + Proposals") # Ensure Mongo ObjectId can be serialized safely in every API response. ENCODERS_BY_TYPE[ObjectId] = str app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) MONGO_URI = os.environ.get("MONGO_URI") mongo_client = MongoClient(MONGO_URI) db = mongo_client.nomus_db tasks_collection = db.tasks chat_collection = db.chat_history memory_collection = db.memory proposals_collection = db.proposal_statuses users_collection = db.users sessions_collection = db.sessions teams_collection = db.teams projects_collection = db.projects issues_collection = db.project_issues image_assets_collection = db.image_assets team_chat_collection = db.team_chat_history team_docs_collection = db.team_documents team_doc_chunks_collection = db.team_document_chunks def _ensure_indexes() -> None: try: team_doc_chunks_collection.create_index( [("doc_id", ASCENDING), ("chunk_index", ASCENDING)], name="idx_team_doc_chunks_doc_id_chunk", background=True, ) team_docs_collection.create_index( [("team_id", ASCENDING), ("project_id", ASCENDING), ("updated_at", ASCENDING)], name="idx_team_docs_team_project_updated", background=True, ) except Exception: # Index creation failure should not block app startup. pass _ensure_indexes() UPLOAD_DIR = os.environ.get("UPLOAD_DIR", os.path.join(os.path.dirname(__file__), "uploads")) os.makedirs(UPLOAD_DIR, exist_ok=True) app.mount("/uploads", StaticFiles(directory=UPLOAD_DIR), name="uploads") GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") model_client = genai.Client(api_key=GOOGLE_API_KEY) MODEL_NAME = os.environ.get("MODEL_NAME", "gemini-flash-lite-latest") QWEN_AGENT_MODEL = os.environ.get("QWEN_AGENT_MODEL", "Qwen/Qwen2.5-7B-Instruct") NVIDIA_KEY = os.environ.get("NVIDIA_KEY", "") NVIDIA_API_BASE = os.environ.get("NVIDIA_API_BASE", "https://integrate.api.nvidia.com/v1") TEAM_AGENT_MODEL = os.environ.get("TEAM_AGENT_MODEL", "google/gemma-4-31b-it") WHISPER_MODEL_NAME = os.environ.get("WHISPER_MODEL", "base") WHISPER_LANGUAGE = os.environ.get("WHISPER_LANGUAGE", "vi") VOICE_MAX_SECONDS = int(os.environ.get("VOICE_MAX_SECONDS", "45")) TTS_MODEL_NAME = os.environ.get("TTS_MODEL", "facebook/mms-tts-vie") TTS_MAX_CHARS = int(os.environ.get("TTS_MAX_CHARS", "500")) TTS_MAX_TOTAL_CHARS = int(os.environ.get("TTS_MAX_TOTAL_CHARS", "4000")) TTS_CHUNK_CHARS = int(os.environ.get("TTS_CHUNK_CHARS", "350")) TTS_DEFAULT_SAMPLE_RATE = int(os.environ.get("TTS_DEFAULT_SAMPLE_RATE", "22050")) COMPACT_HARD_DELETE = os.environ.get("COMPACT_HARD_DELETE", "false").strip().lower() == "true" AUTO_COMPACT_ENABLED = os.environ.get("AUTO_COMPACT_ENABLED", "true").strip().lower() == "true" AUTO_COMPACT_MIN_MESSAGES = int(os.environ.get("AUTO_COMPACT_MIN_MESSAGES", "14")) AUTO_COMPACT_MIN_TOTAL_CHARS = int(os.environ.get("AUTO_COMPACT_MIN_TOTAL_CHARS", "5000")) AUTO_COMPACT_COOLDOWN_SEC = int(os.environ.get("AUTO_COMPACT_COOLDOWN_SEC", "90")) SESSION_TTL_DAYS = int(os.environ.get("SESSION_TTL_DAYS", "14")) PASSWORD_HASH_ITERATIONS = int(os.environ.get("PASSWORD_HASH_ITERATIONS", "120000")) executor = ThreadPoolExecutor(max_workers=4) _whisper_model = None _tts_tokenizer = None _tts_model = None _audio_model_lock = threading.Lock() _auto_compact_lock = threading.Lock() _last_auto_compact_ts = 0.0