- Introduce new Xfyun ASR and TTS services, enabling integration with iFlytek's voice recognition and synthesis capabilities. - Update AssistantConfig model to include interface types for STT and TTS. - Enhance credential testing to validate Xfyun credentials. - Modify service factory to create Xfyun services based on configuration. - Update README with new configuration details for Xfyun integration. - Add new frontend components for visualizing audio streams and managing user interactions.
191 lines
5.9 KiB
Python
191 lines
5.9 KiB
Python
"""模型资源凭证 CRUD。前端 ComponentsModelsPage 对接这里。
|
|
|
|
字段对齐前端 ModelResource。读:api_key 打码;写:占位符表示不改(写时哨兵)。
|
|
设默认时清掉同 type 其它默认。
|
|
"""
|
|
|
|
import uuid
|
|
|
|
from db.models import ProviderCredential
|
|
from db.session import get_session
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from schemas import (
|
|
CredentialOut,
|
|
CredentialTestRequest,
|
|
CredentialTestResult,
|
|
CredentialUpsert,
|
|
)
|
|
from services.credential_tester import test_openai_credential, test_xfyun_credential
|
|
from services.masking import mask, resolve_incoming_key
|
|
from sqlalchemy import select, update
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
router = APIRouter(prefix="/api/credentials", tags=["credentials"])
|
|
|
|
|
|
def _to_out(c: ProviderCredential) -> CredentialOut:
|
|
return CredentialOut(
|
|
id=c.id,
|
|
name=c.name,
|
|
model_id=c.model_id,
|
|
type=c.type,
|
|
interface_type=c.interface_type,
|
|
api_url=c.api_url,
|
|
api_key=mask(c.api_key), # 永远打码
|
|
voice=c.voice,
|
|
speed=c.speed,
|
|
language=c.language,
|
|
is_default=c.is_default,
|
|
)
|
|
|
|
|
|
async def _clear_other_defaults(session: AsyncSession, type_: str, keep_id: str):
|
|
await session.execute(
|
|
update(ProviderCredential)
|
|
.where(ProviderCredential.type == type_, ProviderCredential.id != keep_id)
|
|
.values(is_default=False)
|
|
)
|
|
|
|
|
|
@router.get("", response_model=list[CredentialOut])
|
|
async def list_credentials(session: AsyncSession = Depends(get_session)):
|
|
rows = (
|
|
await session.execute(
|
|
select(ProviderCredential).order_by(ProviderCredential.type)
|
|
)
|
|
).scalars().all()
|
|
return [_to_out(c) for c in rows]
|
|
|
|
|
|
@router.post("", response_model=CredentialOut)
|
|
async def create_credential(
|
|
body: CredentialUpsert, session: AsyncSession = Depends(get_session)
|
|
):
|
|
c = ProviderCredential(
|
|
id=f"model_{uuid.uuid4().hex[:12]}",
|
|
name=body.name,
|
|
model_id=body.model_id,
|
|
type=body.type,
|
|
interface_type=body.interface_type,
|
|
api_url=body.api_url,
|
|
api_key=resolve_incoming_key(body.api_key, ""),
|
|
voice=body.voice,
|
|
speed=body.speed,
|
|
language=body.language,
|
|
is_default=body.is_default,
|
|
)
|
|
session.add(c)
|
|
if c.is_default:
|
|
await _clear_other_defaults(session, c.type, c.id)
|
|
await session.commit()
|
|
await session.refresh(c)
|
|
return _to_out(c)
|
|
|
|
|
|
@router.post("/test", response_model=CredentialTestResult)
|
|
async def test_new_credential(body: CredentialTestRequest):
|
|
if body.interface_type == "xfyun":
|
|
return test_xfyun_credential(body)
|
|
if body.interface_type != "openai":
|
|
return CredentialTestResult(
|
|
ok=False,
|
|
message="暂不支持该接口类型",
|
|
detail="当前仅支持 OpenAI 兼容接口测试",
|
|
)
|
|
if not body.api_key:
|
|
return CredentialTestResult(
|
|
ok=False,
|
|
message="缺少 API Key",
|
|
detail="测试新配置时需要输入 API Key",
|
|
)
|
|
return await test_openai_credential(body)
|
|
|
|
|
|
@router.post("/{cred_id}/test", response_model=CredentialTestResult)
|
|
async def test_saved_credential(
|
|
cred_id: str,
|
|
body: CredentialTestRequest,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
c = await session.get(ProviderCredential, cred_id)
|
|
if not c:
|
|
raise HTTPException(404, "凭证不存在")
|
|
config = body.model_copy(
|
|
update={"api_key": resolve_incoming_key(body.api_key, c.api_key)}
|
|
)
|
|
if config.interface_type == "xfyun":
|
|
return test_xfyun_credential(config)
|
|
if config.interface_type != "openai":
|
|
return CredentialTestResult(
|
|
ok=False,
|
|
message="暂不支持该接口类型",
|
|
detail="当前仅支持 OpenAI 兼容接口测试",
|
|
)
|
|
return await test_openai_credential(config)
|
|
|
|
|
|
@router.post("/{cred_id}/duplicate", response_model=CredentialOut)
|
|
async def duplicate_credential(
|
|
cred_id: str, session: AsyncSession = Depends(get_session)
|
|
):
|
|
"""服务端整行复制:含真实 api_key,DB→DB,密钥不经浏览器。副本不继承默认标记。"""
|
|
src = await session.get(ProviderCredential, cred_id)
|
|
if not src:
|
|
raise HTTPException(404, "凭证不存在")
|
|
c = ProviderCredential(
|
|
id=f"model_{uuid.uuid4().hex[:12]}",
|
|
name=f"{src.name} 副本",
|
|
model_id=src.model_id,
|
|
type=src.type,
|
|
interface_type=src.interface_type,
|
|
api_url=src.api_url,
|
|
api_key=src.api_key, # 真 key,DB→DB
|
|
voice=src.voice,
|
|
speed=src.speed,
|
|
language=src.language,
|
|
is_default=False, # 副本不继承默认,避免抢走源的默认标记
|
|
)
|
|
session.add(c)
|
|
await session.commit()
|
|
await session.refresh(c)
|
|
return _to_out(c)
|
|
|
|
|
|
@router.put("/{cred_id}", response_model=CredentialOut)
|
|
async def update_credential(
|
|
cred_id: str,
|
|
body: CredentialUpsert,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
c = await session.get(ProviderCredential, cred_id)
|
|
if not c:
|
|
raise HTTPException(404, "凭证不存在")
|
|
c.name = body.name
|
|
c.model_id = body.model_id
|
|
c.type = body.type
|
|
c.interface_type = body.interface_type
|
|
c.api_url = body.api_url
|
|
c.voice = body.voice
|
|
c.speed = body.speed
|
|
c.language = body.language
|
|
c.is_default = body.is_default
|
|
# 写时哨兵:打码占位符 → 保留旧 key
|
|
c.api_key = resolve_incoming_key(body.api_key, c.api_key)
|
|
if c.is_default:
|
|
await _clear_other_defaults(session, c.type, c.id)
|
|
await session.commit()
|
|
await session.refresh(c)
|
|
return _to_out(c)
|
|
|
|
|
|
@router.delete("/{cred_id}")
|
|
async def delete_credential(
|
|
cred_id: str, session: AsyncSession = Depends(get_session)
|
|
):
|
|
c = await session.get(ProviderCredential, cred_id)
|
|
if not c:
|
|
raise HTTPException(404, "凭证不存在")
|
|
await session.delete(c)
|
|
await session.commit()
|
|
return {"ok": True}
|