Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
"""Users API routes for VoIdea."""
|
||||
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.dependencies import get_current_user, get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.user import SubscriptionInfo, UserResponse, UserUpdate, VoiceSettingsResponse, VoiceSettingsUpdate
|
||||
from app.services.tariff_service import TariffService
|
||||
from app.services.user_service import UserService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _user_to_response(user: User) -> UserResponse:
|
||||
return UserResponse(
|
||||
id=str(user.id),
|
||||
email=user.email,
|
||||
display_name=user.display_name,
|
||||
avatar_url=user.avatar_url,
|
||||
is_active=user.is_active,
|
||||
is_superuser=user.is_superuser,
|
||||
oauth_provider=user.oauth_provider,
|
||||
created_at=user.created_at,
|
||||
updated_at=user.updated_at,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def get_me(user: Annotated[User, Depends(get_current_user)]):
|
||||
return _user_to_response(user)
|
||||
|
||||
|
||||
@router.patch("/me", response_model=UserResponse)
|
||||
async def update_me(
|
||||
body: UserUpdate,
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
service = UserService(db)
|
||||
result = await service.update_profile(
|
||||
str(user.id),
|
||||
display_name=body.display_name,
|
||||
avatar_url=body.avatar_url,
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
return _user_to_response(result)
|
||||
|
||||
|
||||
@router.delete("/me", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_me(
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
service = UserService(db)
|
||||
await service.delete(str(user.id))
|
||||
|
||||
|
||||
VOICE_PRESETS: dict[str, dict[str, Any]] = {
|
||||
"standard": {
|
||||
"vad_noise_threshold": 0.3,
|
||||
"vad_silence_timeout_ms": 1500,
|
||||
"confidence_verified": 80,
|
||||
"confidence_warning": 50,
|
||||
"semantic_mode": "fast",
|
||||
"tts_enabled": True,
|
||||
"continuous_listening": False,
|
||||
},
|
||||
"precise": {
|
||||
"vad_noise_threshold": 0.2,
|
||||
"vad_silence_timeout_ms": 2000,
|
||||
"confidence_verified": 85,
|
||||
"confidence_warning": 60,
|
||||
"semantic_mode": "full",
|
||||
"tts_enabled": True,
|
||||
"continuous_listening": False,
|
||||
},
|
||||
"fast": {
|
||||
"vad_noise_threshold": 0.4,
|
||||
"vad_silence_timeout_ms": 1000,
|
||||
"confidence_verified": 70,
|
||||
"confidence_warning": 40,
|
||||
"semantic_mode": "fast",
|
||||
"tts_enabled": False,
|
||||
"continuous_listening": True,
|
||||
},
|
||||
"quiet": {
|
||||
"vad_noise_threshold": 0.6,
|
||||
"vad_silence_timeout_ms": 3000,
|
||||
"confidence_verified": 80,
|
||||
"confidence_warning": 50,
|
||||
"semantic_mode": "fast",
|
||||
"tts_enabled": True,
|
||||
"continuous_listening": False,
|
||||
},
|
||||
"expert": {
|
||||
"vad_noise_threshold": 0.15,
|
||||
"vad_silence_timeout_ms": 1000,
|
||||
"confidence_verified": 90,
|
||||
"confidence_warning": 70,
|
||||
"semantic_mode": "full",
|
||||
"tts_enabled": True,
|
||||
"continuous_listening": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/me/voice-settings", response_model=VoiceSettingsResponse)
|
||||
async def get_voice_settings(
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
):
|
||||
tuning = user.pipeline_tuning or {}
|
||||
preset = tuning.get("preset", "standard")
|
||||
overrides = tuning.get("overrides", {})
|
||||
return VoiceSettingsResponse(
|
||||
preset=preset,
|
||||
overrides=overrides,
|
||||
available_presets=list(VOICE_PRESETS.keys()),
|
||||
preset_values=VOICE_PRESETS.get(preset, VOICE_PRESETS["standard"]),
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/me/voice-settings", response_model=VoiceSettingsResponse)
|
||||
async def update_voice_settings(
|
||||
body: VoiceSettingsUpdate,
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
from sqlalchemy import select
|
||||
|
||||
tuning = user.pipeline_tuning or {}
|
||||
|
||||
if body.reset:
|
||||
tuning = {"preset": "standard", "overrides": {}}
|
||||
else:
|
||||
if body.preset is not None:
|
||||
if body.preset not in VOICE_PRESETS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unknown preset: {body.preset}. Available: {', '.join(VOICE_PRESETS.keys())}",
|
||||
)
|
||||
tuning["preset"] = body.preset
|
||||
|
||||
if body.overrides is not None:
|
||||
existing_overrides = tuning.get("overrides", {})
|
||||
existing_overrides.update(body.overrides)
|
||||
tuning["overrides"] = existing_overrides
|
||||
|
||||
result = await db.execute(select(User).where(User.id == user.id))
|
||||
db_user = result.scalar_one()
|
||||
db_user.pipeline_tuning = tuning
|
||||
await db.commit()
|
||||
|
||||
preset = tuning.get("preset", "standard")
|
||||
preset_vals = VOICE_PRESETS.get(preset, VOICE_PRESETS["standard"])
|
||||
overrides = tuning.get("overrides", {})
|
||||
merged = {**preset_vals, **overrides}
|
||||
|
||||
return VoiceSettingsResponse(
|
||||
preset=preset,
|
||||
overrides=overrides,
|
||||
available_presets=list(VOICE_PRESETS.keys()),
|
||||
preset_values=merged,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me/subscription", response_model=SubscriptionInfo)
|
||||
async def get_my_subscription(
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
svc = TariffService(db)
|
||||
sub = await svc.get_user_subscription(user.id)
|
||||
if not sub:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Subscription not found"
|
||||
)
|
||||
plan = await svc.get_plan_by_id(sub.plan_id)
|
||||
return SubscriptionInfo(
|
||||
plan_name=plan.name if plan else "Бесплатно",
|
||||
plan_code=plan.code if plan else "free",
|
||||
status=sub.status,
|
||||
expires_at=sub.current_period_end,
|
||||
features=plan.features if plan else None,
|
||||
)
|
||||
Reference in New Issue
Block a user