Add pipecat-based backend with WebRTC/WS voice routes, Next.js frontend, and Docker Compose orchestration. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.2 KiB
Python
107 lines
3.2 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, CredentialUpsert
|
|
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), # 永远打码
|
|
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, ""),
|
|
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.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.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}
|