Spaces:
Running on Zero
Running on Zero
| import os | |
| import logging | |
| from pathlib import Path | |
| from fastapi import FastAPI, Request, Depends | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse, FileResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from app.database import Base, engine | |
| from app.routers import chat, media, memories, jobs, matcher, ats, authenticity, insights, outreach | |
| logging.basicConfig(level=logging.INFO) | |
| app = FastAPI( | |
| title="malloc()", | |
| description="Voice-enabled personal AI assistant with long-term memory backend API and web interface", | |
| version="0.1.0-stage1", | |
| ) | |
| # Enable CORS for cross-domain access | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Mount static files directory | |
| STATIC_DIR = Path(__file__).parent / "static" | |
| if STATIC_DIR.exists(): | |
| app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") | |
| # Stage 1: create tables directly. Alembic migrations arrive once the | |
| # schema stabilizes (Stage 4+, alongside the memory tables). | |
| Base.metadata.create_all(bind=engine) | |
| app.include_router(chat.router) | |
| app.include_router(media.router) | |
| app.include_router(memories.router) | |
| app.include_router(jobs.router) | |
| app.include_router(matcher.router) | |
| app.include_router(ats.router) | |
| app.include_router(authenticity.router) | |
| app.include_router(insights.router) | |
| app.include_router(outreach.router) | |
| app.include_router(outreach.router, prefix="/api") | |
| # Alias for /api/ats/check | |
| def api_ats_check(payload: ats.ATSCheckRequest, db=Depends(ats.get_db)): | |
| return ats.check_ats(payload, db) | |
| # Aliases for authenticity checker | |
| def api_authenticity_check(payload: authenticity.AuthenticityCheckRequest): | |
| return authenticity.check_authenticity(payload) | |
| # Alias for /api/resume/suggest-edits | |
| def api_suggest_edits(payload: matcher.SuggestEditsRequest, db=Depends(matcher.get_db)): | |
| return matcher.suggest_resume_edits(payload, db) | |
| # Alias for /api/company/insights | |
| def api_company_insights(payload: insights.CompanyInsightsRequest, db=Depends(insights.get_db)): | |
| return insights.analyze_company(payload, db) | |
| def root(request: Request): | |
| """Serves the rich web UI for browsers, or API status for JSON clients.""" | |
| accept_header = request.headers.get("accept", "") | |
| index_file = STATIC_DIR / "index.html" | |
| if "text/html" in accept_header and index_file.exists(): | |
| return FileResponse(index_file) | |
| if index_file.exists(): | |
| return FileResponse(index_file) | |
| return { | |
| "name": "malloc() API", | |
| "status": "online", | |
| "version": "0.1.0-stage1", | |
| "docs": "/docs", | |
| "health": "/health", | |
| } | |
| def api_info(): | |
| """Explicit JSON API info endpoint.""" | |
| return { | |
| "name": "malloc() API", | |
| "status": "online", | |
| "version": "0.1.0-stage1", | |
| "docs": "/docs", | |
| "health": "/health", | |
| } | |
| from app.config import settings | |
| from pydantic import BaseModel | |
| class UpdateSettingsRequest(BaseModel): | |
| llm_provider: str | None = None | |
| groq_api_key: str | None = None | |
| anthropic_api_key: str | None = None | |
| groq_model: str | None = None | |
| def get_settings(): | |
| return { | |
| "llm_provider": settings.llm_provider, | |
| "groq_api_key_configured": bool(settings.groq_api_key and settings.groq_api_key.strip()), | |
| "anthropic_api_key_configured": bool(settings.anthropic_api_key and settings.anthropic_api_key.strip()), | |
| "groq_model": settings.groq_model, | |
| "memora_model": settings.memora_model, | |
| } | |
| def update_settings(payload: UpdateSettingsRequest): | |
| env_file = Path(__file__).parent.parent / ".env" | |
| env_vars = {} | |
| if env_file.exists(): | |
| for line in env_file.read_text(encoding="utf-8").splitlines(): | |
| line = line.strip() | |
| if line and not line.startswith("#") and "=" in line: | |
| k, v = line.split("=", 1) | |
| env_vars[k.strip()] = v.strip() | |
| if payload.llm_provider is not None: | |
| settings.llm_provider = payload.llm_provider | |
| env_vars["LLM_PROVIDER"] = payload.llm_provider | |
| if payload.groq_api_key is not None: | |
| key = payload.groq_api_key.strip() | |
| settings.groq_api_key = key | |
| env_vars["GROQ_API_KEY"] = key | |
| if payload.anthropic_api_key is not None: | |
| key = payload.anthropic_api_key.strip() | |
| settings.anthropic_api_key = key | |
| env_vars["ANTHROPIC_API_KEY"] = key | |
| if payload.groq_model is not None: | |
| settings.groq_model = payload.groq_model | |
| env_vars["GROQ_MODEL"] = payload.groq_model | |
| # Write back to .env | |
| lines = [f"{k}={v}" for k, v in env_vars.items()] | |
| env_file.write_text("\n".join(lines) + "\n", encoding="utf-8") | |
| return { | |
| "status": "updated", | |
| "llm_provider": settings.llm_provider, | |
| "groq_api_key_configured": bool(settings.groq_api_key and settings.groq_api_key.strip()), | |
| "anthropic_api_key_configured": bool(settings.anthropic_api_key and settings.anthropic_api_key.strip()), | |
| } | |
| def health(): | |
| return {"status": "ok", "stage": 1} | |
| async def unhandled_exception_handler(request, exc): | |
| logging.getLogger("memora.main").exception("Unhandled exception") | |
| return JSONResponse(status_code=500, content={"detail": "Internal server error"}) | |