Update voice libary key form

This commit is contained in:
Xin Wang
2026-02-08 23:16:21 +08:00
parent 8ec91a7fa8
commit 97e3236e76
7 changed files with 503 additions and 702 deletions

View File

@@ -1,7 +1,6 @@
import base64
import os
import uuid
from datetime import datetime
from typing import Optional
import httpx
@@ -9,16 +8,8 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from ..db import get_db
from ..models import VendorCredential, Voice
from ..schemas import (
VendorCredentialOut,
VendorCredentialUpsert,
VoiceCreate,
VoiceOut,
VoicePreviewRequest,
VoicePreviewResponse,
VoiceUpdate,
)
from ..models import Voice
from ..schemas import VoiceCreate, VoiceOut, VoicePreviewRequest, VoicePreviewResponse, VoiceUpdate
router = APIRouter(prefix="/voices", tags=["Voices"])
@@ -29,28 +20,10 @@ def _is_siliconflow_vendor(vendor: str) -> bool:
return vendor.strip().lower() in {"siliconflow", "硅基流动"}
def _canonical_vendor_key(vendor: str) -> str:
normalized = vendor.strip().lower()
alias_map = {
"硅基流动": "siliconflow",
"siliconflow": "siliconflow",
"ali": "ali",
"volcano": "volcano",
"minimax": "minimax",
}
return alias_map.get(normalized, normalized)
def _default_tts_base_url(vendor_key: str) -> Optional[str]:
defaults = {
"siliconflow": "https://api.siliconflow.cn/v1",
}
return defaults.get(vendor_key)
def _resolve_vendor_credential(db: Session, vendor: str) -> Optional[VendorCredential]:
vendor_key = _canonical_vendor_key(vendor)
return db.query(VendorCredential).filter(VendorCredential.vendor_key == vendor_key).first()
def _default_base_url(vendor: str) -> Optional[str]:
if _is_siliconflow_vendor(vendor):
return "https://api.siliconflow.cn/v1"
return None
def _build_siliconflow_voice_key(voice: Voice, model: str) -> str:
@@ -108,6 +81,8 @@ def create_voice(data: VoiceCreate, db: Session = Depends(get_db)):
description=data.description,
model=model,
voice_key=voice_key,
api_key=data.api_key,
base_url=data.base_url,
speed=data.speed,
gain=data.gain,
pitch=data.pitch,
@@ -165,56 +140,6 @@ def delete_voice(id: str, db: Session = Depends(get_db)):
return {"message": "Deleted successfully"}
@router.get("/vendors/credentials")
def list_vendor_credentials(db: Session = Depends(get_db)):
items = db.query(VendorCredential).order_by(VendorCredential.updated_at.desc()).all()
return {"list": items, "total": len(items)}
@router.get("/vendors/credentials/{vendor_key}", response_model=VendorCredentialOut)
def get_vendor_credential(vendor_key: str, db: Session = Depends(get_db)):
key = _canonical_vendor_key(vendor_key)
item = db.query(VendorCredential).filter(VendorCredential.vendor_key == key).first()
if not item:
raise HTTPException(status_code=404, detail="Vendor credential not found")
return item
@router.put("/vendors/credentials/{vendor_key}", response_model=VendorCredentialOut)
def upsert_vendor_credential(vendor_key: str, data: VendorCredentialUpsert, db: Session = Depends(get_db)):
key = _canonical_vendor_key(vendor_key)
item = db.query(VendorCredential).filter(VendorCredential.vendor_key == key).first()
if item:
item.vendor_name = data.vendor_name or item.vendor_name
item.api_key = data.api_key
item.base_url = data.base_url
item.updated_at = datetime.utcnow()
else:
item = VendorCredential(
vendor_key=key,
vendor_name=data.vendor_name or vendor_key,
api_key=data.api_key,
base_url=data.base_url,
)
db.add(item)
db.commit()
db.refresh(item)
return item
@router.delete("/vendors/credentials/{vendor_key}")
def delete_vendor_credential(vendor_key: str, db: Session = Depends(get_db)):
key = _canonical_vendor_key(vendor_key)
item = db.query(VendorCredential).filter(VendorCredential.vendor_key == key).first()
if not item:
raise HTTPException(status_code=404, detail="Vendor credential not found")
db.delete(item)
db.commit()
return {"message": "Deleted successfully"}
@router.post("/{id}/preview", response_model=VoicePreviewResponse)
def preview_voice(id: str, data: VoicePreviewRequest, db: Session = Depends(get_db)):
"""试听指定声音,基于 OpenAI-compatible /audio/speech 接口。"""
@@ -226,22 +151,17 @@ def preview_voice(id: str, data: VoicePreviewRequest, db: Session = Depends(get_
if not text:
raise HTTPException(status_code=400, detail="Preview text cannot be empty")
credential = _resolve_vendor_credential(db, voice.vendor)
api_key = (data.api_key or "").strip()
if not api_key and credential:
api_key = credential.api_key
api_key = (data.api_key or "").strip() or (voice.api_key or "").strip()
if not api_key and _is_siliconflow_vendor(voice.vendor):
api_key = os.getenv("SILICONFLOW_API_KEY", "").strip()
if not api_key:
api_key = os.getenv("SILICONFLOW_API_KEY") if _is_siliconflow_vendor(voice.vendor) else ""
if not api_key:
raise HTTPException(status_code=400, detail=f"Vendor API key is required for {voice.vendor}")
raise HTTPException(status_code=400, detail=f"API key is required for voice: {voice.name}")
base_url = (voice.base_url or "").strip() or (_default_base_url(voice.vendor) or "")
if not base_url:
raise HTTPException(status_code=400, detail=f"Base URL is required for voice: {voice.name}")
model = voice.model or SILICONFLOW_DEFAULT_MODEL
vendor_key = _canonical_vendor_key(voice.vendor)
base_url = (credential.base_url.strip() if credential and credential.base_url else "") or _default_tts_base_url(vendor_key)
if not base_url:
raise HTTPException(status_code=400, detail=f"Vendor base_url is required for {voice.vendor}")
tts_api_url = f"{base_url.rstrip('/')}/audio/speech"
payload = {
"model": model,
"input": text,
@@ -253,7 +173,7 @@ def preview_voice(id: str, data: VoicePreviewRequest, db: Session = Depends(get_
try:
with httpx.Client(timeout=45.0) as client:
response = client.post(
tts_api_url,
f"{base_url.rstrip('/')}/audio/speech",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json=payload,
)