diff --git a/qwen3-tts-backend/api/auth.py b/qwen3-tts-backend/api/auth.py index 6c0b170..646b4b4 100644 --- a/qwen3-tts-backend/api/auth.py +++ b/qwen3-tts-backend/api/auth.py @@ -14,7 +14,7 @@ from core.security import ( decode_access_token ) from db.database import get_db -from db.crud import get_user_by_username, get_user_by_email, create_user, change_user_password, update_user_aliyun_key, get_user_preferences, update_user_preferences, is_local_model_enabled +from db.crud import get_user_by_username, get_user_by_email, create_user, change_user_password, update_user_aliyun_key, get_user_preferences, update_user_preferences, can_user_use_local_model from schemas.user import User, UserCreate, Token, PasswordChange, AliyunKeyUpdate, AliyunKeyVerifyResponse, UserPreferences, UserPreferencesResponse router = APIRouter(prefix="/auth", tags=["authentication"]) @@ -247,9 +247,8 @@ async def get_preferences( ): prefs = get_user_preferences(db, current_user.id) - local_enabled = is_local_model_enabled(db) available_backends = ["aliyun"] - if local_enabled or current_user.is_superuser: + if can_user_use_local_model(current_user): available_backends.append("local") return { @@ -267,8 +266,7 @@ async def update_preferences( db: Session = Depends(get_db) ): if preferences.default_backend == "local": - local_enabled = is_local_model_enabled(db) - if not local_enabled and not current_user.is_superuser: + if not can_user_use_local_model(current_user): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Local model is not available. Please contact administrator." diff --git a/qwen3-tts-backend/api/tts.py b/qwen3-tts-backend/api/tts.py index b224ad5..ad70ede 100644 --- a/qwen3-tts-backend/api/tts.py +++ b/qwen3-tts-backend/api/tts.py @@ -285,13 +285,12 @@ async def create_custom_voice_job( db: Session = Depends(get_db) ): from core.security import decrypt_api_key - from db.crud import get_user_preferences, is_local_model_enabled + from db.crud import get_user_preferences, can_user_use_local_model user_prefs = get_user_preferences(db, current_user.id) preferred_backend = user_prefs.get("default_backend", "aliyun") - local_enabled = is_local_model_enabled(db) - can_use_local = local_enabled or current_user.is_superuser + can_use_local = can_user_use_local_model(current_user) backend_type = req_data.backend if hasattr(req_data, 'backend') and req_data.backend else preferred_backend @@ -375,13 +374,12 @@ async def create_voice_design_job( db: Session = Depends(get_db) ): from core.security import decrypt_api_key - from db.crud import get_user_preferences, is_local_model_enabled + from db.crud import get_user_preferences, can_user_use_local_model user_prefs = get_user_preferences(db, current_user.id) preferred_backend = user_prefs.get("default_backend", "aliyun") - local_enabled = is_local_model_enabled(db) - can_use_local = local_enabled or current_user.is_superuser + can_use_local = can_user_use_local_model(current_user) backend_type = req_data.backend if hasattr(req_data, 'backend') and req_data.backend else preferred_backend @@ -476,13 +474,12 @@ async def create_voice_clone_job( db: Session = Depends(get_db) ): from core.security import decrypt_api_key - from db.crud import get_user_preferences, is_local_model_enabled + from db.crud import get_user_preferences, can_user_use_local_model user_prefs = get_user_preferences(db, current_user.id) preferred_backend = user_prefs.get("default_backend", "aliyun") - local_enabled = is_local_model_enabled(db) - can_use_local = local_enabled or current_user.is_superuser + can_use_local = can_user_use_local_model(current_user) backend_type = backend if backend else preferred_backend diff --git a/qwen3-tts-backend/api/users.py b/qwen3-tts-backend/api/users.py index e2030a5..66f7f63 100644 --- a/qwen3-tts-backend/api/users.py +++ b/qwen3-tts-backend/api/users.py @@ -15,12 +15,9 @@ from db.crud import ( list_users, create_user_by_admin, update_user, - delete_user, - get_system_setting, - update_system_setting, - is_local_model_enabled + delete_user ) -from schemas.user import User, UserCreateByAdmin, UserUpdate, UserListResponse, SystemSettingsUpdate, SystemSettingsResponse +from schemas.user import User, UserCreateByAdmin, UserUpdate, UserListResponse router = APIRouter(prefix="/users", tags=["users"]) limiter = Limiter(key_func=get_remote_address) @@ -75,7 +72,8 @@ async def create_user( username=user_data.username, email=user_data.email, hashed_password=hashed_password, - is_superuser=user_data.is_superuser + is_superuser=user_data.is_superuser, + can_use_local_model=user_data.can_use_local_model ) return user @@ -139,7 +137,8 @@ async def update_user_info( email=user_data.email, hashed_password=hashed_password, is_active=user_data.is_active, - is_superuser=user_data.is_superuser + is_superuser=user_data.is_superuser, + can_use_local_model=user_data.can_use_local_model ) if not user: @@ -170,42 +169,3 @@ async def delete_user_by_id( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) - -@router.get("/system/settings", response_model=SystemSettingsResponse) -async def get_system_settings( - current_user: Annotated[User, Depends(require_superuser)], - db: Session = Depends(get_db) -): - local_enabled = is_local_model_enabled(db) - return {"local_model_enabled": local_enabled} - -@router.put("/system/settings") -async def update_system_settings( - settings: SystemSettingsUpdate, - current_user: Annotated[User, Depends(require_superuser)], - db: Session = Depends(get_db) -): - from db.models import User - from datetime import datetime - - update_system_setting(db, "local_model_enabled", {"enabled": settings.local_model_enabled}) - - if not settings.local_model_enabled: - users = db.query(User).filter(User.is_superuser == False).all() - migrated_count = 0 - - for user in users: - prefs = user.user_preferences or {} - if prefs.get("default_backend") == "local": - prefs["default_backend"] = "aliyun" - user.user_preferences = prefs - user.updated_at = datetime.utcnow() - migrated_count += 1 - - db.commit() - return { - "message": "System settings updated", - "users_migrated": migrated_count - } - - return {"message": "System settings updated", "users_migrated": 0} diff --git a/qwen3-tts-backend/db/crud.py b/qwen3-tts-backend/db/crud.py index 6e67b91..e5bc284 100644 --- a/qwen3-tts-backend/db/crud.py +++ b/qwen3-tts-backend/db/crud.py @@ -30,13 +30,15 @@ def create_user_by_admin( username: str, email: str, hashed_password: str, - is_superuser: bool = False + is_superuser: bool = False, + can_use_local_model: bool = False ) -> User: user = User( username=username, email=email, hashed_password=hashed_password, - is_superuser=is_superuser + is_superuser=is_superuser, + can_use_local_model=can_use_local_model ) db.add(user) db.commit() @@ -58,7 +60,8 @@ def update_user( email: Optional[str] = None, hashed_password: Optional[str] = None, is_active: Optional[bool] = None, - is_superuser: Optional[bool] = None + is_superuser: Optional[bool] = None, + can_use_local_model: Optional[bool] = None ) -> Optional[User]: user = get_user_by_id(db, user_id) if not user: @@ -74,6 +77,8 @@ def update_user( user.is_active = is_active if is_superuser is not None: user.is_superuser = is_superuser + if can_use_local_model is not None: + user.can_use_local_model = can_use_local_model user.updated_at = datetime.utcnow() db.commit() @@ -264,8 +269,5 @@ def update_system_setting(db: Session, key: str, value: dict) -> SystemSettings: db.refresh(setting) return setting -def is_local_model_enabled(db: Session) -> bool: - setting = get_system_setting(db, "local_model_enabled") - if not setting: - return False - return setting.get("enabled", False) +def can_user_use_local_model(user: User) -> bool: + return user.is_superuser or user.can_use_local_model diff --git a/qwen3-tts-backend/db/migrate_add_local_permission.py b/qwen3-tts-backend/db/migrate_add_local_permission.py new file mode 100644 index 0000000..a8a1b05 --- /dev/null +++ b/qwen3-tts-backend/db/migrate_add_local_permission.py @@ -0,0 +1,17 @@ +from sqlalchemy import create_engine, text +from core.config import settings + +def migrate(): + engine = create_engine(settings.DATABASE_URL) + with engine.connect() as conn: + conn.execute(text( + "ALTER TABLE users ADD COLUMN can_use_local_model BOOLEAN DEFAULT 0 NOT NULL" + )) + conn.execute(text( + "UPDATE users SET can_use_local_model = 1 WHERE is_superuser = 1" + )) + conn.commit() + print("Migration completed: Added can_use_local_model column") + +if __name__ == "__main__": + migrate() diff --git a/qwen3-tts-backend/db/models.py b/qwen3-tts-backend/db/models.py index 60c883c..35c6417 100644 --- a/qwen3-tts-backend/db/models.py +++ b/qwen3-tts-backend/db/models.py @@ -21,6 +21,7 @@ class User(Base): is_active = Column(Boolean, default=True, nullable=False) is_superuser = Column(Boolean, default=False, nullable=False) aliyun_api_key = Column(Text, nullable=True) + can_use_local_model = Column(Boolean, default=False, nullable=False) user_preferences = Column(JSON, nullable=True, default=lambda: {"default_backend": "aliyun", "onboarding_completed": False}) created_at = Column(DateTime, default=datetime.utcnow, nullable=False) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) diff --git a/qwen3-tts-backend/schemas/user.py b/qwen3-tts-backend/schemas/user.py index 1a6f666..585e79c 100644 --- a/qwen3-tts-backend/schemas/user.py +++ b/qwen3-tts-backend/schemas/user.py @@ -32,6 +32,7 @@ class User(UserBase): id: int is_active: bool is_superuser: bool + can_use_local_model: bool created_at: datetime model_config = ConfigDict(from_attributes=True) @@ -39,6 +40,7 @@ class User(UserBase): class UserCreateByAdmin(UserBase): password: str = Field(..., min_length=8, max_length=128) is_superuser: bool = False + can_use_local_model: bool = False @field_validator('password') @classmethod @@ -57,6 +59,7 @@ class UserUpdate(BaseModel): password: Optional[str] = Field(None, min_length=8, max_length=128) is_active: Optional[bool] = None is_superuser: Optional[bool] = None + can_use_local_model: Optional[bool] = None @field_validator('username') @classmethod @@ -127,9 +130,3 @@ class UserPreferencesResponse(BaseModel): default_backend: str = Field(default="aliyun", pattern="^(local|aliyun)$") onboarding_completed: bool = Field(default=False) available_backends: list[str] = Field(default=["aliyun"]) - -class SystemSettingsUpdate(BaseModel): - local_model_enabled: bool - -class SystemSettingsResponse(BaseModel): - local_model_enabled: bool diff --git a/qwen3-tts-frontend/src/components/users/UserDialog.tsx b/qwen3-tts-frontend/src/components/users/UserDialog.tsx index e296397..cb10546 100644 --- a/qwen3-tts-frontend/src/components/users/UserDialog.tsx +++ b/qwen3-tts-frontend/src/components/users/UserDialog.tsx @@ -29,6 +29,7 @@ const userFormSchema = z.object({ password: z.string().optional(), is_active: z.boolean(), is_superuser: z.boolean(), + can_use_local_model: z.boolean(), }) type UserFormValues = z.infer @@ -58,6 +59,7 @@ export function UserDialog({ password: '', is_active: true, is_superuser: false, + can_use_local_model: false, }, }) @@ -69,6 +71,7 @@ export function UserDialog({ password: '', is_active: user.is_active, is_superuser: user.is_superuser, + can_use_local_model: user.can_use_local_model, }) } else { form.reset({ @@ -77,6 +80,7 @@ export function UserDialog({ password: '', is_active: true, is_superuser: false, + can_use_local_model: false, }) } }, [user, form]) @@ -178,6 +182,27 @@ export function UserDialog({ )} /> + ( + + + + +
+ 本地模型权限 +

+ 允许用户使用本地 TTS 模型 +

+
+
+ )} + /> +