mirror of
https://github.com/bnair123/MusicAnalyser.git
synced 2026-02-25 11:46:07 +00:00
Rebuild frontend with Tailwind CSS + fix Python 3.14 compatibility
- Upgrade SQLAlchemy 2.0.27→2.0.45, google-genai SDK for Python 3.14 - Replace google-generativeai with google-genai in narrative_service.py - Fix HTTPException handling in main.py (was wrapping as 500) - Rebuild all frontend components with Tailwind CSS v3: - Dashboard, NarrativeSection, StatsGrid, VibeRadar, HeatMap, TopRotation - Custom color palette (background-dark, card-dark, accent-neon, etc.) - Add glass-panel, holographic-badge CSS effects - Docker improvements: - Combined backend container (API + worker in entrypoint.sh) - DATABASE_URL configurable via env var - CI workflow builds both backend and frontend images - Update README with clearer docker-compose instructions
This commit is contained in:
@@ -3,9 +3,20 @@ FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
# Make entrypoint executable
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Expose API port
|
||||
EXPOSE 8000
|
||||
|
||||
# Use entrypoint script to run migrations, worker, and API
|
||||
CMD ["./entrypoint.sh"]
|
||||
|
||||
@@ -10,29 +10,17 @@ from alembic import context
|
||||
# Add app to path to import models
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app.database import Base
|
||||
from app.models import * # Import models to register them
|
||||
from app.database import Base, SQLALCHEMY_DATABASE_URL
|
||||
from app.models import *
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
# Override sqlalchemy.url with our app's URL
|
||||
config.set_main_option("sqlalchemy.url", "sqlite:///./music.db")
|
||||
config.set_main_option("sqlalchemy.url", SQLALCHEMY_DATABASE_URL)
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./music.db"
|
||||
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./music.db")
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
connect_args = {}
|
||||
if SQLALCHEMY_DATABASE_URL.startswith("sqlite"):
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args=connect_args)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
@@ -16,8 +16,18 @@ load_dotenv()
|
||||
# Create tables
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app = FastAPI(title="Music Analyser Backend")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:5173"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"status": "ok", "message": "Music Analyser API is running"}
|
||||
@@ -59,9 +69,8 @@ def trigger_analysis(
|
||||
if stats_json["volume"]["total_plays"] == 0:
|
||||
raise HTTPException(status_code=404, detail="No plays found in the specified period.")
|
||||
|
||||
# 2. Generate Narrative
|
||||
narrative_service = NarrativeService(model_name=model_name)
|
||||
narrative_json = narrative_service.generate_narrative(stats_json)
|
||||
narrative_json = narrative_service.generate_full_narrative(stats_json)
|
||||
|
||||
# 3. Save Snapshot
|
||||
snapshot = AnalysisSnapshot(
|
||||
@@ -84,6 +93,8 @@ def trigger_analysis(
|
||||
"narrative": narrative_json
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise HTTPExceptions as-is (404, etc.)
|
||||
except Exception as e:
|
||||
print(f"Analysis Failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import google.generativeai as genai
|
||||
from google import genai
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
class NarrativeService:
|
||||
def __init__(self, model_name: str = "gemini-2.0-flash-exp"):
|
||||
self.api_key = os.getenv("GEMINI_API_KEY")
|
||||
self.client = genai.Client(api_key=self.api_key) if self.api_key else None
|
||||
if not self.api_key:
|
||||
print("WARNING: GEMINI_API_KEY not found. LLM features will fail.")
|
||||
else:
|
||||
genai.configure(api_key=self.api_key)
|
||||
|
||||
self.model_name = model_name
|
||||
|
||||
@@ -48,11 +47,10 @@ Your goal is to generate a JSON report that acts as a deeper, more honest "Spoti
|
||||
}}
|
||||
"""
|
||||
try:
|
||||
model = genai.GenerativeModel(self.model_name)
|
||||
# Use JSON mode if available, otherwise rely on prompt + cleaning
|
||||
response = model.generate_content(
|
||||
prompt,
|
||||
generation_config={"response_mime_type": "application/json"}
|
||||
response = self.client.models.generate_content(
|
||||
model=self.model_name,
|
||||
contents=prompt,
|
||||
config=genai.types.GenerateContentConfig(response_mime_type="application/json")
|
||||
)
|
||||
|
||||
return self._clean_and_parse_json(response.text)
|
||||
|
||||
17
backend/entrypoint.sh
Normal file
17
backend/entrypoint.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== MusicAnalyser Backend Starting ==="
|
||||
|
||||
# Run Alembic migrations
|
||||
echo "Running database migrations..."
|
||||
alembic upgrade head
|
||||
|
||||
# Start the worker in background (polls Spotify every 60s)
|
||||
echo "Starting Spotify ingestion worker..."
|
||||
python run_worker.py &
|
||||
WORKER_PID=$!
|
||||
|
||||
# Start the API server in foreground
|
||||
echo "Starting API server on port 8000..."
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
@@ -1,14 +1,15 @@
|
||||
fastapi==0.109.2
|
||||
uvicorn==0.27.1
|
||||
sqlalchemy==2.0.27
|
||||
httpx==0.26.0
|
||||
sqlalchemy==2.0.45
|
||||
httpx==0.28.1
|
||||
python-dotenv==1.0.1
|
||||
pydantic==2.6.1
|
||||
pydantic-settings==2.1.0
|
||||
google-generativeai==0.3.2
|
||||
pydantic==2.12.5
|
||||
pydantic-core==2.41.5
|
||||
pydantic-settings==2.12.0
|
||||
tenacity==8.2.3
|
||||
python-dateutil==2.9.0.post0
|
||||
requests==2.31.0
|
||||
alembic==1.13.1
|
||||
scikit-learn==1.4.0
|
||||
lyricsgenius==3.0.1
|
||||
google-genai==1.56.0
|
||||
|
||||
Reference in New Issue
Block a user