Implement server-side duplication for both assistants and credentials, allowing users to create copies with unique IDs and modified names. Update the respective API routes and frontend components to handle duplication requests, ensuring sensitive information is securely managed. Enhance the AssistantPage and ComponentsModelsPage to support this new feature, including loading and error handling for the duplication process.
131 lines
4.1 KiB
Python
131 lines
4.1 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.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
|
|
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.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}
|