From 964ebb824ca7049779922793a8690053234e577c Mon Sep 17 00:00:00 2001 From: bdim404 Date: Fri, 6 Mar 2026 14:35:59 +0800 Subject: [PATCH] feat: Add voice management functionality with delete capability and UI integration --- qwen3-tts-backend/api/voice_designs.py | 11 ++ qwen3-tts-backend/db/crud.py | 8 ++ qwen3-tts-frontend/src/App.tsx | 9 ++ qwen3-tts-frontend/src/components/Navbar.tsx | 8 +- qwen3-tts-frontend/src/lib/api.ts | 4 + qwen3-tts-frontend/src/lib/constants.ts | 1 + qwen3-tts-frontend/src/locales/en-US/nav.json | 5 +- .../src/locales/en-US/voice.json | 11 +- qwen3-tts-frontend/src/locales/ja-JP/nav.json | 5 +- .../src/locales/ja-JP/voice.json | 11 +- qwen3-tts-frontend/src/locales/ko-KR/nav.json | 5 +- .../src/locales/ko-KR/voice.json | 11 +- qwen3-tts-frontend/src/locales/zh-CN/nav.json | 5 +- .../src/locales/zh-CN/voice.json | 11 +- qwen3-tts-frontend/src/locales/zh-TW/nav.json | 5 +- .../src/locales/zh-TW/voice.json | 11 +- .../src/pages/VoiceManagement.tsx | 124 ++++++++++++++++++ 17 files changed, 224 insertions(+), 21 deletions(-) create mode 100644 qwen3-tts-frontend/src/pages/VoiceManagement.tsx diff --git a/qwen3-tts-backend/api/voice_designs.py b/qwen3-tts-backend/api/voice_designs.py index 059c440..77e2140 100644 --- a/qwen3-tts-backend/api/voice_designs.py +++ b/qwen3-tts-backend/api/voice_designs.py @@ -170,6 +170,17 @@ async def prepare_and_create_voice_design( raise HTTPException(status_code=500, detail="Failed to prepare voice design") +@router.delete("/{design_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_voice_design( + design_id: int, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + deleted = crud.delete_voice_design(db, design_id, current_user.id) + if not deleted: + raise HTTPException(status_code=404, detail="Voice design not found") + + @router.post("/{design_id}/prepare-clone") @limiter.limit("10/minute") async def prepare_voice_clone_prompt( diff --git a/qwen3-tts-backend/db/crud.py b/qwen3-tts-backend/db/crud.py index 8768a0d..0ae4e3a 100644 --- a/qwen3-tts-backend/db/crud.py +++ b/qwen3-tts-backend/db/crud.py @@ -339,6 +339,14 @@ def count_voice_designs( query = query.filter(VoiceDesign.backend_type == backend_type) return query.count() +def delete_voice_design(db: Session, design_id: int, user_id: int) -> bool: + design = get_voice_design(db, design_id, user_id) + if not design: + return False + db.delete(design) + db.commit() + return True + def update_voice_design_usage(db: Session, design_id: int, user_id: int) -> Optional[VoiceDesign]: design = get_voice_design(db, design_id, user_id) if design: diff --git a/qwen3-tts-frontend/src/App.tsx b/qwen3-tts-frontend/src/App.tsx index 095c5c7..a865214 100644 --- a/qwen3-tts-frontend/src/App.tsx +++ b/qwen3-tts-frontend/src/App.tsx @@ -15,6 +15,7 @@ const Login = lazy(() => import('@/pages/Login')) const Home = lazy(() => import('@/pages/Home')) const Settings = lazy(() => import('@/pages/Settings')) const UserManagement = lazy(() => import('@/pages/UserManagement')) +const VoiceManagement = lazy(() => import('@/pages/VoiceManagement')) function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated, isLoading } = useAuth() @@ -100,6 +101,14 @@ function App() { } /> + + + + } + /> diff --git a/qwen3-tts-frontend/src/components/Navbar.tsx b/qwen3-tts-frontend/src/components/Navbar.tsx index 304ca4c..f7d89ed 100644 --- a/qwen3-tts-frontend/src/components/Navbar.tsx +++ b/qwen3-tts-frontend/src/components/Navbar.tsx @@ -1,4 +1,4 @@ -import { Menu, LogOut, Users, Settings, Globe, Home } from 'lucide-react' +import { Menu, LogOut, Users, Settings, Globe, Home, Mic } from 'lucide-react' import { Link, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' @@ -43,6 +43,12 @@ export function Navbar({ onToggleSidebar }: NavbarProps) { )} + + + + {user?.is_superuser && ( + + ))} + + )} + + + + + !open && setDeleteTarget(null)}> + + + {t('voice:deleteVoice')} + + {t('voice:deleteConfirmDesc', { name: deleteTarget?.name })} + + + + {t('common:cancel')} + + {isDeleting ? t('voice:deleting') : t('common:delete')} + + + + + + ) +}