Embedding:
- Embedding model now configurable via Admin UI (More tab) or LITELLM_EMBEDDING_MODEL env
- Calls LiteLLM proxy directly via httpx (bypasses LiteLLM library param validation)
- Passes dimensions=1024 to proxy; Redis setting overrides env var
- Default model: ge-gemini-embedding-001 (Gemini AI Studio, 1024-dim)
- Test button in admin UI to verify model works
- Fixed vector_service to use httpx + Redis model (was broken with non-prefixed model names)
Polly:
- Global enable/disable toggle in Admin → More settings (stored in Redis)
- /tts/voices filters out polly/* when disabled
- /tts/speak rejects polly requests when disabled
Job cancellation:
- POST /quizzes/job/{job_id}/cancel endpoint
- Cancel button on JobsPage for running jobs
- Celery task checks Redis status at each chunk boundary and exits cleanly
- Fixes DB lock on restart caused by cancelled jobs leaving open transactions
Admin UI:
- Settings tab renamed to "More" (heading: More Settings)
- Model row overflow fixed (minWidth: 0 + ellipsis on model_id)
- Embedding model search shows all proxy models (no auto-filter by "embed")
- Navbar correctly excludes cancelled/failed jobs from "extracting" count
README:
- Added Rebuild & Restart section with commands
- Updated embedding model reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session, joinedload
|
|
|
|
from app.database import get_db
|
|
from app.models.user import User
|
|
from app.models.favorite import Favorite
|
|
from app.models.question import Question
|
|
from app.schemas.favorite import FavoriteCreate, FavoriteResponse
|
|
from app.utils.auth import get_current_user
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("", response_model=FavoriteResponse, status_code=201)
|
|
def add_favorite(
|
|
data: FavoriteCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Add a question to user's favorites."""
|
|
# Check if question exists
|
|
question = db.query(Question).filter(Question.id == data.question_id).first()
|
|
if not question:
|
|
raise HTTPException(status_code=404, detail="Question not found")
|
|
|
|
# Check if already favorited
|
|
existing = db.query(Favorite).filter(
|
|
Favorite.user_id == current_user.id,
|
|
Favorite.question_id == data.question_id
|
|
).first()
|
|
if existing:
|
|
return existing # Already favorited, return existing
|
|
|
|
# Create new favorite
|
|
favorite = Favorite(
|
|
user_id=current_user.id,
|
|
question_id=data.question_id
|
|
)
|
|
db.add(favorite)
|
|
db.commit()
|
|
db.refresh(favorite)
|
|
return favorite
|
|
|
|
|
|
@router.delete("/{question_id}", status_code=204)
|
|
def remove_favorite(
|
|
question_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Remove a question from user's favorites."""
|
|
favorite = db.query(Favorite).filter(
|
|
Favorite.user_id == current_user.id,
|
|
Favorite.question_id == question_id
|
|
).first()
|
|
if not favorite:
|
|
raise HTTPException(status_code=404, detail="Favorite not found")
|
|
|
|
db.delete(favorite)
|
|
db.commit()
|
|
return None
|
|
|
|
|
|
@router.get("", response_model=list[int])
|
|
def list_favorites(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Get list of favorited question IDs for current user."""
|
|
favorites = db.query(Favorite.question_id).filter(
|
|
Favorite.user_id == current_user.id
|
|
).all()
|
|
return [f[0] for f in favorites]
|