diff --git a/.gitignore b/.gitignore
index db7b752..a52f1af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,5 @@ qwen3-tts-frontend/.env.local
CLAUDE.md
样本.mp3
aliyun.md
+nginx.conf
+deploy.md
\ No newline at end of file
diff --git a/qwen3-tts-backend/api/jobs.py b/qwen3-tts-backend/api/jobs.py
index 34319e3..4b61323 100644
--- a/qwen3-tts-backend/api/jobs.py
+++ b/qwen3-tts-backend/api/jobs.py
@@ -9,7 +9,9 @@ from slowapi.util import get_remote_address
from core.database import get_db
from core.config import settings
+from core.security import decode_access_token
from db.models import Job, JobStatus, User
+from db.crud import get_user_by_username
from api.auth import get_current_user
logger = logging.getLogger(__name__)
@@ -18,6 +20,42 @@ router = APIRouter(prefix="/jobs", tags=["jobs"])
limiter = Limiter(key_func=get_remote_address)
+async def get_user_from_token_or_query(
+ request: Request,
+ token: Optional[str] = Query(None),
+ db: Session = Depends(get_db)
+) -> User:
+ auth_token = None
+
+ auth_header = request.headers.get("Authorization")
+ if auth_header and auth_header.startswith("Bearer "):
+ auth_token = auth_header.split(" ")[1]
+ elif token:
+ auth_token = token
+
+ if not auth_token:
+ raise HTTPException(
+ status_code=401,
+ detail="Missing authentication token"
+ )
+
+ username = decode_access_token(auth_token)
+ if username is None:
+ raise HTTPException(
+ status_code=401,
+ detail="Invalid or expired token"
+ )
+
+ user = get_user_by_username(db, username=username)
+ if user is None:
+ raise HTTPException(
+ status_code=401,
+ detail="User not found"
+ )
+
+ return user
+
+
@router.get("/{job_id}")
@limiter.limit("30/minute")
async def get_job(
@@ -140,7 +178,7 @@ async def delete_job(
async def download_job_output(
request: Request,
job_id: int,
- current_user: User = Depends(get_current_user),
+ current_user: User = Depends(get_user_from_token_or_query),
db: Session = Depends(get_db)
):
job = db.query(Job).filter(Job.id == job_id).first()
diff --git a/qwen3-tts-backend/config.py b/qwen3-tts-backend/config.py
index 6110973..8700e75 100644
--- a/qwen3-tts-backend/config.py
+++ b/qwen3-tts-backend/config.py
@@ -12,7 +12,7 @@ class Settings(BaseSettings):
DATABASE_URL: str = Field(default="sqlite:///./qwen_tts.db")
CACHE_DIR: str = Field(default="./voice_cache")
OUTPUT_DIR: str = Field(default="./outputs")
- BASE_URL: str = Field(default="http://localhost:8000")
+ BASE_URL: str = Field(default="")
MODEL_DEVICE: str = Field(default="cuda:0")
MODEL_BASE_PATH: str = Field(default="../Qwen")
diff --git a/qwen3-tts-backend/main.py b/qwen3-tts-backend/main.py
index 7644dac..1d9130a 100644
--- a/qwen3-tts-backend/main.py
+++ b/qwen3-tts-backend/main.py
@@ -119,13 +119,14 @@ app = FastAPI(
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
+if settings.LOG_LEVEL == "debug":
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
app.include_router(auth.router)
app.include_router(jobs.router)
diff --git a/qwen3-tts-frontend/public/vite.svg b/qwen3-tts-frontend/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/qwen3-tts-frontend/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/qwen3-tts-frontend/src/components/AudioPlayer.tsx b/qwen3-tts-frontend/src/components/AudioPlayer.tsx
index e78a6c2..ad630da 100644
--- a/qwen3-tts-frontend/src/components/AudioPlayer.tsx
+++ b/qwen3-tts-frontend/src/components/AudioPlayer.tsx
@@ -11,15 +11,34 @@ interface AudioPlayerProps {
jobId: number
}
+const isMobileDevice = () => {
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
+}
+
const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => {
const [blobUrl, setBlobUrl] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [loadError, setLoadError] = useState(null)
+ const [useMobileMode, setUseMobileMode] = useState(false)
const previousAudioUrlRef = useRef('')
+ useEffect(() => {
+ setUseMobileMode(isMobileDevice())
+ }, [])
+
useEffect(() => {
if (!audioUrl || audioUrl === previousAudioUrlRef.current) return
+ if (useMobileMode) {
+ const token = localStorage.getItem('token')
+ const separator = audioUrl.includes('?') ? '&' : '?'
+ const urlWithToken = token ? `${audioUrl}${separator}token=${token}` : audioUrl
+ setBlobUrl(urlWithToken)
+ previousAudioUrlRef.current = audioUrl
+ setIsLoading(false)
+ return
+ }
+
let active = true
const prevBlobUrl = blobUrl
@@ -55,7 +74,7 @@ const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => {
return () => {
active = false
}
- }, [audioUrl])
+ }, [audioUrl, useMobileMode])
useEffect(() => {
return () => {
diff --git a/qwen3-tts-frontend/src/lib/api.ts b/qwen3-tts-frontend/src/lib/api.ts
index f500364..887a3a9 100644
--- a/qwen3-tts-frontend/src/lib/api.ts
+++ b/qwen3-tts-frontend/src/lib/api.ts
@@ -342,6 +342,10 @@ export const jobApi = {
getAudioUrl: (id: number, audioPath?: string): string => {
if (audioPath) {
if (audioPath.startsWith('http')) {
+ if (audioPath.includes('localhost') || audioPath.includes('127.0.0.1')) {
+ const url = new URL(audioPath)
+ return `${import.meta.env.VITE_API_URL}${url.pathname}`
+ }
return audioPath
} else {
const baseUrl = import.meta.env.VITE_API_URL
diff --git a/qwen3-tts-frontend/src/lib/constants.ts b/qwen3-tts-frontend/src/lib/constants.ts
index f24d919..c5a5ea3 100644
--- a/qwen3-tts-frontend/src/lib/constants.ts
+++ b/qwen3-tts-frontend/src/lib/constants.ts
@@ -93,7 +93,7 @@ export const PRESET_INSTRUCTS = [
},
{
label: '温柔关怀',
- instruct: '温柔体贴的女性声音,语速平缓,音调柔和,充满关怀和安慰',
+ instruct: '温柔体贴,语速平缓,音调柔和,充满关怀和安慰',
text: '别担心,一切都会好起来的。我会一直陪在你身边。',
},
{
@@ -108,17 +108,17 @@ export const PRESET_INSTRUCTS = [
},
{
label: '专业播音员',
- instruct: '专业新闻播音员。性别:女性。音高:中等偏高,音域稳定。语速:标准播音语速,吐字清晰。音量:适中,音色饱满。情绪:沉稳专业,不带个人感情色彩。语调:平直中略有起伏,重点词汇加重。性格特征:严谨、客观、权威。',
+ instruct: '专业新闻播音员。语速:标准播音语速,吐字清晰。情绪:沉稳专业,不带个人感情色彩。语调:平直中略有起伏,重点词汇加重。性格特征:严谨、客观、权威。',
text: '据新华社报道,我国航天事业取得重大突破,神舟系列飞船成功完成载人飞行任务。',
},
{
label: '温暖导师',
- instruct: '温暖的中年女性导师。音色:温和醇厚,带有亲和力。语速:不急不缓,娓娓道来。音调:平稳中带有鼓励性上扬。情绪:关怀、耐心、鼓励。性格:善解人意,循循善诱,充满正能量。适合场景:心理咨询、教育引导。',
+ instruct: '温暖导师。语速:不急不缓,娓娓道来。音调:平稳中带有鼓励性上扬。情绪:关怀、耐心、鼓励。性格:善解人意,循循善诱,充满正能量。',
text: '每个人都有自己的节奏,不要着急。慢慢来,你一定能找到属于自己的那条路。',
},
{
label: '活力少年',
- instruct: '充满活力的青少年男性。音高:略高,富有朝气。语速:偏快,吐字利落。音量:响亮明快。情绪:开朗乐观,精力充沛。语调:跳跃感强,抑扬顿挫。性格:外向、自信、热情,充满青春气息。',
+ instruct: '充满活力。语速:偏快,吐字利落。情绪:开朗乐观,精力充沛。语调:跳跃感强,抑扬顿挫。性格:外向、自信、热情,充满青春气息。',
text: '哇,这个游戏太酷了!咱们组队一起玩吧,我保证带你们飞!',
},
] as const
diff --git a/qwen3-tts-frontend/vite.config.ts b/qwen3-tts-frontend/vite.config.ts
index cbb8a60..3303259 100644
--- a/qwen3-tts-frontend/vite.config.ts
+++ b/qwen3-tts-frontend/vite.config.ts
@@ -14,6 +14,7 @@ export default defineConfig({
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
+ 'icons': ['lucide-react'],
'ui-vendor': [
'@radix-ui/react-tabs',
'@radix-ui/react-label',