forked from xiaohei/taiji-AI-PAD
1262 lines
43 KiB
Python
1262 lines
43 KiB
Python
"""Frontend integration endpoints required by BACKEND_INTEGRATION_CHECKLIST.
|
||
|
||
All handlers return lightweight, mostly in-memory data so the frontend can
|
||
render flows before the full business services are ready. Where possible, we
|
||
hydrate responses from existing tables (Agents, Tools) to keep values realistic.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
from datetime import datetime, timedelta
|
||
from typing import Any, Dict, List
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||
from sqlalchemy import func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from database import get_db
|
||
from models import (
|
||
Agent,
|
||
Balance,
|
||
Channel,
|
||
ChannelAgentQuota,
|
||
DataTemplate,
|
||
Execution,
|
||
Billing,
|
||
GatewayAPI,
|
||
ProviderModel,
|
||
Tenant,
|
||
Tool,
|
||
User,
|
||
)
|
||
from monitoring import system_monitor
|
||
from ..auth import create_access_token, ensure_user, get_password_hash, verify_password, require_auth
|
||
|
||
router = APIRouter(prefix="/api", tags=["frontend-integration"])
|
||
|
||
|
||
class _Store:
|
||
"""Simple in-memory store backing the checklist endpoints."""
|
||
|
||
def __init__(self) -> None:
|
||
self.gateway_type: str | None = None
|
||
self.gateway_apis: List[Dict[str, Any]] = []
|
||
self.data_templates: List[Dict[str, Any]] = []
|
||
self.deployments: List[Dict[str, Any]] = []
|
||
self.workflows: Dict[str, Dict[str, Any]] = {}
|
||
self.billing_balance: float = 1200.0
|
||
self.billing_history: List[Dict[str, Any]] = []
|
||
self.tenants: Dict[str, Dict[str, Any]] = {}
|
||
self.channels: Dict[str, Dict[str, Any]] = {}
|
||
self.channel_admins: Dict[str, Dict[str, Any]] = {}
|
||
self.resource_applications: Dict[str, Dict[str, Any]] = {}
|
||
self.provider_models: Dict[str, Dict[str, Any]] = {}
|
||
self.provider_data: List[Dict[str, Any]] = []
|
||
|
||
|
||
store = _Store()
|
||
|
||
|
||
def _now() -> str:
|
||
return datetime.utcnow().isoformat()
|
||
|
||
|
||
async def _get_principal_user_id(request: Request, db: AsyncSession) -> uuid.UUID:
|
||
principal = getattr(request.state, "principal", None) or {}
|
||
email = principal.get("email") or "dev@taiji-ai.com"
|
||
user = await ensure_user(email, "temp-pass", db)
|
||
return user.id
|
||
|
||
|
||
async def _get_balance(db: AsyncSession, user_id: uuid.UUID) -> Balance:
|
||
result = await db.execute(select(Balance).where(Balance.user_id == user_id))
|
||
balance = result.scalar_one_or_none()
|
||
if balance is None:
|
||
balance = Balance(user_id=user_id, eu_balance=0.0)
|
||
db.add(balance)
|
||
await db.commit()
|
||
await db.refresh(balance)
|
||
return balance
|
||
|
||
|
||
# ----- User Dashboard -----
|
||
|
||
|
||
@router.get("/user/dashboard/stats")
|
||
async def user_dashboard_stats(request: Request, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
agent_count = (await db.execute(select(func.count(Agent.id)))).scalar() or 0
|
||
total_requests = (
|
||
await db.execute(select(func.coalesce(func.sum(Agent.total_executions), 0)))
|
||
).scalar() or 0
|
||
health = await system_monitor.get_system_health()
|
||
balance = await _get_balance(db, await _get_principal_user_id(request, db))
|
||
return {
|
||
"activeAgents": agent_count,
|
||
"totalRequests": total_requests,
|
||
"euBalance": round(balance.eu_balance, 2),
|
||
"systemHealth": health.get("score", 100),
|
||
}
|
||
|
||
|
||
@router.get("/user/agents/activity")
|
||
async def user_agents_activity(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
query = (
|
||
select(Execution)
|
||
.order_by(Execution.started_at.desc())
|
||
.limit(20)
|
||
)
|
||
rows = (await db.execute(query)).scalars().all()
|
||
activity = [
|
||
{
|
||
"executionId": row.execution_id,
|
||
"agentId": str(row.agent_id),
|
||
"status": row.status,
|
||
"startedAt": row.started_at.isoformat(),
|
||
"duration": row.execution_time or 0.0,
|
||
"eu": row.eu_consumed or 0.0,
|
||
}
|
||
for row in rows
|
||
]
|
||
return {"items": activity, "count": len(activity)}
|
||
|
||
|
||
@router.get("/user/resources/usage")
|
||
async def user_resources_usage(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
agent_count = (await db.execute(select(func.count(Agent.id)))).scalar() or 0
|
||
execution_stats = (
|
||
await db.execute(
|
||
select(
|
||
func.coalesce(func.sum(Execution.eu_consumed), 0),
|
||
func.coalesce(func.sum(Execution.cpu_usage), 0),
|
||
func.coalesce(func.sum(Execution.memory_usage), 0),
|
||
)
|
||
)
|
||
).one()
|
||
return {
|
||
"agents": agent_count,
|
||
"euConsumed": float(execution_stats[0] or 0),
|
||
"cpuSeconds": float(execution_stats[1] or 0),
|
||
"memoryMb": float(execution_stats[2] or 0),
|
||
}
|
||
|
||
|
||
# ----- Service Gateway -----
|
||
|
||
|
||
@router.post("/gateway/select")
|
||
async def select_gateway(payload: Dict[str, str]) -> Dict[str, Any]:
|
||
gateway_type = payload.get("gatewayType")
|
||
if gateway_type not in {"MCP", "A2A", "API"}:
|
||
raise HTTPException(status_code=400, detail="gatewayType must be MCP | A2A | API")
|
||
store.gateway_type = gateway_type
|
||
return {"selected": gateway_type, "updatedAt": _now()}
|
||
|
||
|
||
@router.post("/gateway/api/create")
|
||
async def create_gateway_api(
|
||
request: Request, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)
|
||
) -> Dict[str, Any]:
|
||
if not payload.get("name") or not payload.get("content"):
|
||
raise HTTPException(status_code=400, detail="name and content are required")
|
||
owner_id = await _get_principal_user_id(request, db)
|
||
gateway_api = GatewayAPI(
|
||
name=payload["name"],
|
||
method=payload.get("method", "json"),
|
||
content=payload.get("content", ""),
|
||
owner_id=owner_id,
|
||
)
|
||
db.add(gateway_api)
|
||
await db.commit()
|
||
await db.refresh(gateway_api)
|
||
item = {
|
||
"id": str(gateway_api.id),
|
||
"name": gateway_api.name,
|
||
"method": gateway_api.method,
|
||
"content": gateway_api.content,
|
||
"createdAt": gateway_api.created_at.isoformat(),
|
||
}
|
||
return item
|
||
|
||
|
||
@router.get("/gateway/apis")
|
||
async def list_gateway_apis(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
apis = (await db.execute(select(GatewayAPI).order_by(GatewayAPI.created_at.desc()))).scalars().all()
|
||
items = [
|
||
{
|
||
"id": str(api.id),
|
||
"name": api.name,
|
||
"method": api.method,
|
||
"content": api.content,
|
||
"createdAt": api.created_at.isoformat() if api.created_at else None,
|
||
}
|
||
for api in apis
|
||
]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
|
||
@router.get("/gateway/monitoring")
|
||
async def gateway_monitoring(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
total = (await db.execute(select(func.count(GatewayAPI.id)))).scalar() or 0
|
||
return {
|
||
"selected": store.gateway_type,
|
||
"totalApis": total,
|
||
"lastUpdated": _now(),
|
||
"throughput": {
|
||
"rpm": 120,
|
||
"errorRate": 0.01,
|
||
},
|
||
}
|
||
|
||
|
||
# ----- Data & Tools -----
|
||
|
||
|
||
@router.post("/tools/generate")
|
||
async def generate_tool(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
name = payload.get("name")
|
||
description = payload.get("description", "")
|
||
framework_template = payload.get("frameworkTemplate", "API")
|
||
if not name:
|
||
raise HTTPException(status_code=400, detail="name is required")
|
||
|
||
tool = Tool(
|
||
name=name,
|
||
description=description,
|
||
category=framework_template.lower(),
|
||
schema={
|
||
"type": "object",
|
||
"properties": payload.get("config", {}),
|
||
},
|
||
method="POST",
|
||
endpoint=payload.get("gateway", ""),
|
||
rate_limit=payload.get("maxScale", 100),
|
||
cost_per_call=0.0,
|
||
is_public=True,
|
||
)
|
||
db.add(tool)
|
||
await db.commit()
|
||
await db.refresh(tool)
|
||
return {
|
||
"id": str(tool.id),
|
||
"name": tool.name,
|
||
"description": tool.description,
|
||
"category": tool.category,
|
||
"endpoint": tool.endpoint,
|
||
"createdAt": _now(),
|
||
}
|
||
|
||
|
||
@router.get("/tools/list")
|
||
async def list_tools(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
tools = (await db.execute(select(Tool).order_by(Tool.created_at.desc()).limit(100))).scalars().all()
|
||
items = [
|
||
{
|
||
"id": str(tool.id),
|
||
"name": tool.name,
|
||
"description": tool.description,
|
||
"category": tool.category,
|
||
"endpoint": tool.endpoint,
|
||
"method": tool.method,
|
||
"isActive": tool.is_active,
|
||
}
|
||
for tool in tools
|
||
]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
|
||
@router.post("/data-templates/create")
|
||
async def create_data_template(request: Request, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
if not payload.get("name") or not payload.get("type"):
|
||
raise HTTPException(status_code=400, detail="name and type are required")
|
||
template = DataTemplate(
|
||
name=payload["name"],
|
||
type=payload.get("type", "json_api"),
|
||
config=payload.get("config", {}),
|
||
owner_id=await _get_principal_user_id(request, db),
|
||
)
|
||
db.add(template)
|
||
await db.commit()
|
||
await db.refresh(template)
|
||
return {"id": str(template.id), "status": "created", "createdAt": template.created_at.isoformat()}
|
||
|
||
|
||
# ----- Agent Factory -----
|
||
|
||
|
||
@router.get("/agents/platform")
|
||
async def platform_agents(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
agents = (await db.execute(select(Agent).order_by(Agent.created_at.desc()).limit(50))).scalars().all()
|
||
items = [
|
||
{
|
||
"id": str(agent.id),
|
||
"name": agent.name,
|
||
"description": agent.description,
|
||
"status": agent.status,
|
||
"version": agent.version,
|
||
}
|
||
for agent in agents
|
||
]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
|
||
@router.post("/agents/deploy")
|
||
async def deploy_agent(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
if not payload.get("agentId"):
|
||
raise HTTPException(status_code=400, detail="agentId is required")
|
||
deployment = {
|
||
"deploymentId": str(uuid.uuid4()),
|
||
"agentId": payload["agentId"],
|
||
"instances": payload.get("instances", 1),
|
||
"model": payload.get("model", ""),
|
||
"gateway": payload.get("gateway", "MCP"),
|
||
"createdAt": _now(),
|
||
}
|
||
store.deployments.append(deployment)
|
||
return deployment
|
||
|
||
|
||
@router.get("/agents/deployed")
|
||
async def deployed_agents() -> Dict[str, Any]:
|
||
return {"items": store.deployments, "count": len(store.deployments)}
|
||
|
||
|
||
# ----- Workflows -----
|
||
|
||
|
||
@router.post("/workflows/create")
|
||
async def create_workflow(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
if not payload.get("name"):
|
||
raise HTTPException(status_code=400, detail="name is required")
|
||
workflow_id = str(uuid.uuid4())
|
||
workflow = {"id": workflow_id, **payload, "createdAt": _now()}
|
||
store.workflows[workflow_id] = workflow
|
||
return workflow
|
||
|
||
|
||
@router.get("/workflows/list")
|
||
async def list_workflows() -> Dict[str, Any]:
|
||
items = list(store.workflows.values())
|
||
return {"items": items, "count": len(items)}
|
||
|
||
|
||
@router.put("/workflows/{workflow_id}")
|
||
async def update_workflow(workflow_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
if workflow_id not in store.workflows:
|
||
raise HTTPException(status_code=404, detail="workflow not found")
|
||
store.workflows[workflow_id].update(payload)
|
||
store.workflows[workflow_id]["updatedAt"] = _now()
|
||
return store.workflows[workflow_id]
|
||
|
||
|
||
@router.delete("/workflows/{workflow_id}")
|
||
async def delete_workflow(workflow_id: str) -> Dict[str, Any]:
|
||
if workflow_id not in store.workflows:
|
||
raise HTTPException(status_code=404, detail="workflow not found")
|
||
store.workflows.pop(workflow_id)
|
||
return {"deleted": True, "id": workflow_id}
|
||
|
||
|
||
# ----- Billing & Resources -----
|
||
|
||
|
||
@router.get("/billing/balance")
|
||
async def billing_balance(request: Request, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
balance = await _get_balance(db, await _get_principal_user_id(request, db))
|
||
return {"balance": round(balance.eu_balance, 2), "currency": balance.currency}
|
||
|
||
|
||
@router.get("/billing/history")
|
||
async def billing_history(request: Request, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
user_id = await _get_principal_user_id(request, db)
|
||
result = await db.execute(
|
||
select(Execution.execution_id, Execution.agent_id, Billing.eu_consumed, Billing.cost, Billing.created_at)
|
||
.select_from(Billing)
|
||
.join(Execution, Billing.execution_id == Execution.id, isouter=True)
|
||
.where(Billing.user_id == user_id)
|
||
.order_by(Billing.created_at.desc())
|
||
.limit(100)
|
||
)
|
||
records = [
|
||
{
|
||
"executionId": row.execution_id,
|
||
"agentId": str(row.agent_id) if row.agent_id else None,
|
||
"eu": float(row.eu_consumed or 0),
|
||
"cost": float(row.cost or 0),
|
||
"timestamp": row.created_at.isoformat() if row.created_at else _now(),
|
||
}
|
||
for row in result.all()
|
||
]
|
||
return {"records": records, "count": len(records)}
|
||
|
||
|
||
@router.post("/billing/recharge")
|
||
async def billing_recharge(request: Request, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
amount = float(payload.get("amount", 0))
|
||
if amount <= 0:
|
||
raise HTTPException(status_code=400, detail="amount must be positive")
|
||
user_id = await _get_principal_user_id(request, db)
|
||
balance = await _get_balance(db, user_id)
|
||
balance.eu_balance += amount
|
||
db.add(balance)
|
||
# 记录充值交易
|
||
billing = Billing(
|
||
execution_id=None,
|
||
eu_consumed=0.0,
|
||
cost=amount,
|
||
currency="EU",
|
||
cpu_time=0.0,
|
||
memory_max=0.0,
|
||
network_io=0.0,
|
||
storage_io=0.0,
|
||
user_id=user_id,
|
||
)
|
||
db.add(billing)
|
||
await db.commit()
|
||
return {"balance": round(balance.eu_balance, 2)}
|
||
|
||
|
||
# ----- Channel Partner -----
|
||
|
||
|
||
@router.post("/channel/auth/login")
|
||
async def channel_login(payload: Dict[str, str], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
email = payload.get("email")
|
||
password = payload.get("password") or "temp-pass"
|
||
if not email:
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
|
||
|
||
# 查找或创建渠道用户
|
||
user = await ensure_user(email, password, db)
|
||
if user.hashed_password and not verify_password(password, user.hashed_password):
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
|
||
|
||
# 如果用户没有channel_id,尝试查找或创建对应的渠道
|
||
channel_id = user.channel_id
|
||
if not channel_id:
|
||
# 查找是否有对应的渠道
|
||
from models import Channel
|
||
result = await db.execute(select(Channel).where(Channel.email == email))
|
||
channel = result.scalar_one_or_none()
|
||
|
||
if not channel:
|
||
# 创建一个默认渠道
|
||
channel = Channel(
|
||
name=f"渠道-{email.split('@')[0]}",
|
||
email=email,
|
||
password_hash=user.password_hash,
|
||
commission_rate=10.0,
|
||
channel_credit=0.0,
|
||
custom_agent_cpu=2.0,
|
||
custom_agent_memory=4.0,
|
||
status="active"
|
||
)
|
||
db.add(channel)
|
||
await db.flush()
|
||
|
||
# 更新用户的channel_id
|
||
user.channel_id = channel.id
|
||
await db.commit()
|
||
await db.refresh(user)
|
||
channel_id = channel.id
|
||
|
||
token = create_access_token({
|
||
"sub": str(user.id),
|
||
"email": email,
|
||
"role": "channel_admin",
|
||
"channelId": str(channel_id)
|
||
})
|
||
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
|
||
|
||
|
||
@router.get("/channel/dashboard/stats")
|
||
async def channel_dashboard_stats(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
tenants_count = (await db.execute(select(func.count(Tenant.id)))).scalar() or 0
|
||
deployments = len(store.deployments)
|
||
balances = (await db.execute(select(func.coalesce(func.sum(Balance.eu_balance), 0)))).scalar() or 0
|
||
return {
|
||
"tenants": tenants_count,
|
||
"agents": deployments,
|
||
"eu": round(float(balances), 2),
|
||
}
|
||
|
||
|
||
@router.get("/channel/agents/available")
|
||
async def channel_agents_available(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
agents = (await db.execute(select(Agent).limit(50))).scalars().all()
|
||
return {
|
||
"items": [
|
||
{"agentId": str(a.id), "name": a.name, "quantity": 10, "status": a.status}
|
||
for a in agents
|
||
],
|
||
"count": len(agents),
|
||
}
|
||
|
||
|
||
@router.get("/channel/tenants")
|
||
async def channel_tenants(
|
||
principal: dict = Depends(require_auth),
|
||
db: AsyncSession = Depends(get_db)
|
||
) -> Dict[str, Any]:
|
||
"""获取渠道下的租户列表(需要认证)"""
|
||
# 从token中获取渠道ID
|
||
role = principal.get("claims", {}).get("role", "")
|
||
channel_id_str = principal.get("claims", {}).get("channelId")
|
||
|
||
if not channel_id_str:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="无法获取渠道ID"
|
||
)
|
||
|
||
try:
|
||
channel_id = uuid.UUID(channel_id_str)
|
||
except ValueError:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="无效的渠道ID"
|
||
)
|
||
|
||
# 查询该渠道下的所有租户(role="user"的用户)
|
||
result = await db.execute(
|
||
select(User).where(
|
||
User.channel_id == channel_id,
|
||
User.role == "user"
|
||
)
|
||
)
|
||
tenants = result.scalars().all()
|
||
|
||
items = [
|
||
{
|
||
"id": str(t.id),
|
||
"name": t.name or t.full_name,
|
||
"email": t.email,
|
||
"subscriptionTier": getattr(t, "subscription_tier", "free"),
|
||
"balance": float(getattr(t, "balance", 0)),
|
||
"creditLimit": float(getattr(t, "credit_limit", 0)),
|
||
"status": getattr(t, "status", "active"),
|
||
"channelId": str(t.channel_id) if t.channel_id else None,
|
||
}
|
||
for t in tenants
|
||
]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
|
||
@router.post("/channel/tenants/create")
|
||
async def channel_create_tenant(
|
||
payload: Dict[str, Any],
|
||
principal: dict = Depends(require_auth),
|
||
db: AsyncSession = Depends(get_db)
|
||
) -> Dict[str, Any]:
|
||
"""创建租户(需要认证,自动关联到当前渠道)"""
|
||
# 从token中获取渠道ID
|
||
role = principal.get("claims", {}).get("role", "")
|
||
channel_id_str = principal.get("claims", {}).get("channelId")
|
||
|
||
if not channel_id_str:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="无法获取渠道ID"
|
||
)
|
||
|
||
try:
|
||
channel_id = uuid.UUID(channel_id_str)
|
||
except ValueError:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="无效的渠道ID"
|
||
)
|
||
|
||
# 验证必需字段
|
||
if not payload.get("name"):
|
||
raise HTTPException(status_code=400, detail="name is required")
|
||
if not payload.get("email"):
|
||
raise HTTPException(status_code=400, detail="email is required")
|
||
if not payload.get("password"):
|
||
raise HTTPException(status_code=400, detail="password is required")
|
||
|
||
# 检查邮箱是否已存在
|
||
result = await db.execute(
|
||
select(User).where(User.email == payload["email"])
|
||
)
|
||
if result.scalar_one_or_none():
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="邮箱已被使用"
|
||
)
|
||
|
||
# 创建租户(User with role="user")
|
||
password_hash = get_password_hash(payload["password"])
|
||
tenant = User(
|
||
name=payload["name"],
|
||
email=payload["email"],
|
||
password_hash=password_hash,
|
||
hashed_password=password_hash,
|
||
username=payload["email"].split("@")[0],
|
||
full_name=payload["name"],
|
||
role="user",
|
||
channel_id=channel_id,
|
||
subscription_tier=payload.get("subscriptionTier", "free"),
|
||
status="active",
|
||
balance=0,
|
||
credit_limit=0,
|
||
)
|
||
db.add(tenant)
|
||
await db.commit()
|
||
await db.refresh(tenant)
|
||
return {
|
||
"id": str(tenant.id),
|
||
"name": tenant.name or tenant.full_name,
|
||
"email": tenant.email,
|
||
"subscriptionTier": getattr(tenant, "subscription_tier", "free"),
|
||
}
|
||
|
||
|
||
@router.put("/channel/tenants/{tenant_id}/resources")
|
||
async def channel_update_tenant_resources(tenant_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="tenant not found")
|
||
tenant.subscription_tier = payload.get("subscriptionTier", tenant.subscription_tier)
|
||
tenant.discount = payload.get("discount", tenant.discount)
|
||
db.add(tenant)
|
||
await db.commit()
|
||
await db.refresh(tenant)
|
||
return {
|
||
"id": str(tenant.id),
|
||
"name": tenant.name,
|
||
"subscriptionTier": tenant.subscription_tier,
|
||
"discount": tenant.discount,
|
||
}
|
||
|
||
|
||
@router.put("/channel/tenants/{tenant_id}/billing")
|
||
async def channel_update_tenant_billing(tenant_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="tenant not found")
|
||
tenant.subscription_tier = payload.get("subscriptionTier", tenant.subscription_tier)
|
||
tenant.discount = payload.get("discount", tenant.discount)
|
||
db.add(tenant)
|
||
await db.commit()
|
||
await db.refresh(tenant)
|
||
return {
|
||
"id": str(tenant.id),
|
||
"subscriptionTier": tenant.subscription_tier,
|
||
"discount": tenant.discount,
|
||
}
|
||
|
||
|
||
@router.delete("/channel/tenants/{tenant_id}")
|
||
async def channel_delete_tenant(tenant_id: str, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
"""删除租户(软删除)"""
|
||
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="tenant not found")
|
||
|
||
# 软删除:标记为不活跃
|
||
tenant.status = "inactive"
|
||
db.add(tenant)
|
||
await db.commit()
|
||
|
||
return {
|
||
"id": str(tenant.id),
|
||
"name": tenant.name,
|
||
"deleted": True,
|
||
"message": "租户已删除"
|
||
}
|
||
|
||
|
||
@router.put("/channel/tenants/{tenant_id}/status")
|
||
async def channel_update_tenant_status(tenant_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
"""更新租户状态"""
|
||
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="tenant not found")
|
||
|
||
new_status = payload.get("status")
|
||
if new_status not in ["active", "inactive", "suspended"]:
|
||
raise HTTPException(status_code=400, detail="status must be active, inactive, or suspended")
|
||
|
||
old_status = tenant.status
|
||
tenant.status = new_status
|
||
db.add(tenant)
|
||
await db.commit()
|
||
await db.refresh(tenant)
|
||
|
||
return {
|
||
"tenantId": str(tenant.id),
|
||
"name": tenant.name,
|
||
"oldStatus": old_status,
|
||
"newStatus": new_status,
|
||
}
|
||
|
||
|
||
@router.put("/channel/tenants/{tenant_id}/permissions")
|
||
async def channel_update_tenant_permissions(tenant_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
"""更新租户权限"""
|
||
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="tenant not found")
|
||
|
||
permissions = payload.get("permissions", [])
|
||
|
||
# 有效权限列表
|
||
valid_permissions = {
|
||
"use:platform_agents",
|
||
"use:custom_agents",
|
||
"create:agents",
|
||
"read:billing",
|
||
"export:data",
|
||
}
|
||
|
||
# 验证权限
|
||
invalid_permissions = set(permissions) - valid_permissions
|
||
if invalid_permissions:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"无效的权限: {', '.join(invalid_permissions)}"
|
||
)
|
||
|
||
# 更新权限(存储在metadata中如果没有专门的permissions字段)
|
||
if hasattr(tenant, 'permissions'):
|
||
tenant.permissions = permissions
|
||
|
||
db.add(tenant)
|
||
await db.commit()
|
||
|
||
return {
|
||
"tenantId": str(tenant.id),
|
||
"name": tenant.name,
|
||
"permissions": permissions,
|
||
}
|
||
|
||
|
||
@router.get("/channel/resources/agents")
|
||
async def channel_resources_agents(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
quotas = (await db.execute(select(ChannelAgentQuota))).scalars().all()
|
||
if quotas:
|
||
items = [
|
||
{
|
||
"agentId": str(q.agent_id),
|
||
"channelId": str(q.channel_id),
|
||
"quota": q.quantity,
|
||
"cpu": q.cpu,
|
||
"memory": q.memory,
|
||
}
|
||
for q in quotas
|
||
]
|
||
else:
|
||
agents = (await db.execute(select(Agent))).scalars().all()
|
||
items = [{"agentId": str(a.id), "quota": 10} for a in agents]
|
||
return {"items": items}
|
||
|
||
|
||
@router.get("/channel/resources/models")
|
||
async def channel_resources_models(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
models = (await db.execute(select(ProviderModel))).scalars().all()
|
||
return {
|
||
"items": [
|
||
{
|
||
"modelId": str(m.id),
|
||
"name": m.name,
|
||
"supportedModels": m.supported_models,
|
||
"rpm": m.rpm,
|
||
"tpm": m.tpm,
|
||
}
|
||
for m in models
|
||
]
|
||
}
|
||
|
||
|
||
@router.post("/channel/resources/apply")
|
||
async def channel_resources_apply(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
request_id = str(uuid.uuid4())
|
||
store.resource_applications[request_id] = {"id": request_id, **payload, "status": "pending"}
|
||
return store.resource_applications[request_id]
|
||
|
||
|
||
@router.get("/channel/billing/stats")
|
||
async def channel_billing_stats() -> Dict[str, Any]:
|
||
return {
|
||
"totalEU": sum(rec.get("eu", 0) for rec in store.billing_history),
|
||
"totalCost": sum(rec.get("cost", 0) for rec in store.billing_history),
|
||
"records": store.billing_history,
|
||
}
|
||
|
||
|
||
@router.get("/channel/admins")
|
||
async def channel_admins() -> Dict[str, Any]:
|
||
return {"items": list(store.channel_admins.values())}
|
||
|
||
|
||
@router.post("/channel/admins/create")
|
||
async def channel_admins_create(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
admin_id = str(uuid.uuid4())
|
||
admin = {"id": admin_id, **payload, "createdAt": _now()}
|
||
store.channel_admins[admin_id] = admin
|
||
return admin
|
||
|
||
|
||
@router.put("/channel/admins/{admin_id}/permissions")
|
||
async def channel_admins_permissions(admin_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
if admin_id not in store.channel_admins:
|
||
raise HTTPException(status_code=404, detail="admin not found")
|
||
store.channel_admins[admin_id]["permissions"] = payload.get("permissions", [])
|
||
return store.channel_admins[admin_id]
|
||
|
||
|
||
# ----- Super Admin -----
|
||
|
||
|
||
@router.post("/admin/auth/login")
|
||
async def admin_login(payload: Dict[str, str], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
email = payload.get("email")
|
||
password = payload.get("password") or "temp-pass"
|
||
if not email:
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
|
||
user = await ensure_user(email, password, db)
|
||
if user.hashed_password and not verify_password(password, user.hashed_password):
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
|
||
user.is_admin = True
|
||
db.add(user)
|
||
await db.commit()
|
||
# 使用用户的实际角色,并在token中包含channelId(如果存在)
|
||
token_data = {
|
||
"sub": str(user.id),
|
||
"email": email,
|
||
"role": user.role
|
||
}
|
||
if user.channel_id:
|
||
token_data["channelId"] = str(user.channel_id)
|
||
token = create_access_token(token_data)
|
||
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
|
||
|
||
|
||
@router.get("/admin/dashboard/stats")
|
||
async def admin_dashboard_stats(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
agent_count = (await db.execute(select(func.count(Agent.id)))).scalar() or 0
|
||
channels_count = (await db.execute(select(func.count(Channel.id)))).scalar() or 0
|
||
balances = (await db.execute(select(func.coalesce(func.sum(Balance.eu_balance), 0)))).scalar() or 0
|
||
return {
|
||
"channels": channels_count,
|
||
"agents": agent_count,
|
||
"eu": round(float(balances), 2),
|
||
}
|
||
|
||
|
||
@router.get("/admin/channels")
|
||
async def admin_channels(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
channels = (await db.execute(select(Channel))).scalars().all()
|
||
items = [
|
||
{
|
||
"id": str(ch.id),
|
||
"name": ch.name,
|
||
"email": ch.email,
|
||
"commissionRate": ch.commission_rate,
|
||
"monthlyQuota": ch.monthly_quota,
|
||
"monthlyBudget": ch.monthly_budget,
|
||
}
|
||
for ch in channels
|
||
]
|
||
return {"items": items, "count": len(items)}
|
||
|
||
|
||
@router.post("/admin/channels/create")
|
||
async def admin_create_channel(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
required = {"name", "email"}
|
||
missing = [k for k in required if not payload.get(k)]
|
||
if missing:
|
||
raise HTTPException(status_code=400, detail=f"Missing fields: {', '.join(missing)}")
|
||
channel = Channel(
|
||
name=payload["name"],
|
||
email=payload["email"],
|
||
commission_rate=payload.get("commissionRate", 0.0),
|
||
monthly_quota=payload.get("monthlyQuota", 0),
|
||
monthly_budget=payload.get("monthlyBudget", 0),
|
||
)
|
||
db.add(channel)
|
||
await db.commit()
|
||
await db.refresh(channel)
|
||
return {
|
||
"id": str(channel.id),
|
||
"name": channel.name,
|
||
"email": channel.email,
|
||
"commissionRate": channel.commission_rate,
|
||
"monthlyQuota": channel.monthly_quota,
|
||
"monthlyBudget": channel.monthly_budget,
|
||
}
|
||
|
||
|
||
@router.put("/admin/channels/{channel_id}/commission")
|
||
async def admin_update_channel_commission(channel_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
channel = await db.get(Channel, uuid.UUID(channel_id)) if channel_id else None
|
||
if not channel:
|
||
raise HTTPException(status_code=404, detail="channel not found")
|
||
channel.commission_rate = payload.get("commissionRate", channel.commission_rate)
|
||
db.add(channel)
|
||
await db.commit()
|
||
await db.refresh(channel)
|
||
return {
|
||
"id": str(channel.id),
|
||
"name": channel.name,
|
||
"commissionRate": channel.commission_rate,
|
||
}
|
||
|
||
|
||
@router.get("/admin/channels/{channel_id}/resources")
|
||
async def admin_get_channel_resources(channel_id: str, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
"""获取渠道的资源分配信息"""
|
||
channel = await db.get(Channel, uuid.UUID(channel_id)) if channel_id else None
|
||
if not channel:
|
||
raise HTTPException(status_code=404, detail="channel not found")
|
||
|
||
# 获取渠道的Agent配额
|
||
quotas = (await db.execute(
|
||
select(ChannelAgentQuota).where(ChannelAgentQuota.channel_id == channel.id)
|
||
)).scalars().all()
|
||
|
||
agents = []
|
||
for q in quotas:
|
||
agent = await db.get(Agent, q.agent_id)
|
||
if agent:
|
||
agents.append({
|
||
"agentId": str(q.agent_id),
|
||
"agentName": agent.name,
|
||
"quantity": q.quantity,
|
||
})
|
||
|
||
# 获取所有可用的模型供应商
|
||
models = (await db.execute(select(ProviderModel).where(ProviderModel.status == "active"))).scalars().all()
|
||
model_ids = [str(m.id) for m in models]
|
||
|
||
return {
|
||
"id": str(channel.id),
|
||
"channelName": channel.name,
|
||
"models": model_ids,
|
||
"agents": agents,
|
||
"customAgentResources": {
|
||
"cpu": float(channel.custom_agent_cpu) if channel.custom_agent_cpu else 2.0,
|
||
"memory": float(channel.custom_agent_memory) if channel.custom_agent_memory else 4.0,
|
||
},
|
||
"channelCredit": float(channel.channel_credit) if channel.channel_credit else 0.0,
|
||
}
|
||
|
||
|
||
@router.put("/admin/channels/{channel_id}/resources")
|
||
async def admin_update_channel_resources(channel_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
channel = await db.get(Channel, uuid.UUID(channel_id)) if channel_id else None
|
||
if not channel:
|
||
raise HTTPException(status_code=404, detail="channel not found")
|
||
# 简化为保存配额到 ChannelAgentQuota
|
||
if payload.get("agents"):
|
||
for agent in payload["agents"]:
|
||
agent_id = uuid.UUID(agent.get("agentId"))
|
||
quota = ChannelAgentQuota(channel_id=channel.id, agent_id=agent_id, quantity=agent.get("quantity", 0))
|
||
db.add(quota)
|
||
db.add(channel)
|
||
await db.commit()
|
||
return {"id": str(channel.id), "updatedAt": _now()}
|
||
|
||
|
||
@router.get("/admin/channels/applications")
|
||
async def admin_channel_applications() -> Dict[str, Any]:
|
||
return {"items": list(store.resource_applications.values())}
|
||
|
||
|
||
@router.put("/admin/channels/applications/{request_id}/approve")
|
||
async def admin_channel_applications_approve(request_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
if request_id not in store.resource_applications:
|
||
raise HTTPException(status_code=404, detail="application not found")
|
||
store.resource_applications[request_id]["status"] = "approved" if payload.get("approved") else "rejected"
|
||
store.resource_applications[request_id]["reason"] = payload.get("reason", "")
|
||
return store.resource_applications[request_id]
|
||
|
||
|
||
@router.get("/admin/resources/models")
|
||
async def admin_resources_models(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
models = (await db.execute(select(ProviderModel).order_by(ProviderModel.created_at.desc()))).scalars().all()
|
||
items = [
|
||
{
|
||
"id": str(m.id),
|
||
"name": m.name,
|
||
"apiUrl": m.api_url,
|
||
"supportedModels": m.supported_models,
|
||
"rpm": m.rpm,
|
||
"tpm": m.tpm,
|
||
"isActive": m.is_active,
|
||
}
|
||
for m in models
|
||
]
|
||
return {"items": items}
|
||
|
||
|
||
@router.post("/admin/resources/models/add")
|
||
async def admin_resources_models_add(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
required = {"name", "apiUrl", "apiKey", "supportedModels"}
|
||
missing = [k for k in required if not payload.get(k)]
|
||
if missing:
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Missing fields: {', '.join(missing)}")
|
||
|
||
provider_model = ProviderModel(
|
||
name=payload["name"],
|
||
api_url=payload["apiUrl"],
|
||
api_key=payload["apiKey"],
|
||
supported_models=payload.get("supportedModels", []),
|
||
rpm=payload.get("rpm", 0),
|
||
tpm=payload.get("tpm", 0),
|
||
)
|
||
db.add(provider_model)
|
||
await db.commit()
|
||
await db.refresh(provider_model)
|
||
return {
|
||
"id": str(provider_model.id),
|
||
"name": provider_model.name,
|
||
"apiUrl": provider_model.api_url,
|
||
"supportedModels": provider_model.supported_models,
|
||
"rpm": provider_model.rpm,
|
||
"tpm": provider_model.tpm,
|
||
}
|
||
|
||
|
||
@router.get("/admin/resources/agents")
|
||
async def admin_resources_agents(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
agents = (await db.execute(select(Agent))).scalars().all()
|
||
return {"items": [{"id": str(a.id), "cpu": 1, "memory": 512, "maxInstances": 5} for a in agents]}
|
||
|
||
|
||
@router.put("/admin/resources/agents/{agent_id}")
|
||
async def admin_resources_agents_update(agent_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
return {"id": agent_id, **payload, "updatedAt": _now()}
|
||
|
||
|
||
@router.get("/admin/monitoring/agents")
|
||
async def admin_monitoring_agents() -> Dict[str, Any]:
|
||
metrics = await system_monitor.get_system_metrics()
|
||
return {"health": metrics, "updatedAt": _now()}
|
||
|
||
|
||
@router.get("/admin/billing/overview")
|
||
async def admin_billing_overview(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
billing_rows = (
|
||
await db.execute(
|
||
select(Billing.cost, Billing.eu_consumed, Billing.created_at, Billing.channel_id, Billing.tenant_id)
|
||
.order_by(Billing.created_at.desc())
|
||
.limit(200)
|
||
)
|
||
).all()
|
||
|
||
total_eu = sum(float(row.eu_consumed or 0) for row in billing_rows)
|
||
total_cost = sum(float(row.cost or 0) for row in billing_rows)
|
||
|
||
channels = [
|
||
{
|
||
"channelName": str(row.channel_id) if row.channel_id else "",
|
||
"calls": 1,
|
||
"totalEU": float(row.eu_consumed or 0),
|
||
"totalCost": float(row.cost or 0),
|
||
}
|
||
for row in billing_rows
|
||
]
|
||
tenants = [
|
||
{
|
||
"tenantName": str(row.tenant_id) if row.tenant_id else "",
|
||
"channelName": str(row.channel_id) if row.channel_id else "",
|
||
"calls": 1,
|
||
"totalEU": float(row.eu_consumed or 0),
|
||
"totalCost": float(row.cost or 0),
|
||
}
|
||
for row in billing_rows
|
||
]
|
||
call_records = [
|
||
{
|
||
"timestamp": row.created_at.isoformat() if row.created_at else _now(),
|
||
"channelName": str(row.channel_id) if row.channel_id else "",
|
||
"tenantName": str(row.tenant_id) if row.tenant_id else "",
|
||
"agentName": "",
|
||
"duration": 0,
|
||
"eu": float(row.eu_consumed or 0),
|
||
"cost": float(row.cost or 0),
|
||
}
|
||
for row in billing_rows
|
||
]
|
||
return {"channels": channels, "tenants": tenants, "callRecords": call_records, "totalEU": total_eu, "totalCost": total_cost}
|
||
|
||
|
||
@router.get("/admin/roles")
|
||
async def admin_roles() -> Dict[str, Any]:
|
||
"""获取可用角色列表"""
|
||
return {
|
||
"items": [
|
||
{
|
||
"id": "super_admin",
|
||
"name": "超级管理员",
|
||
"description": "拥有系统所有权限",
|
||
"permissions": ["*"]
|
||
},
|
||
{
|
||
"id": "billing_admin",
|
||
"name": "计费管理员",
|
||
"description": "完整写入权限,可创建渠道、管理租户、计费操作",
|
||
"permissions": ["read:*", "write:channels", "write:tenants", "write:billing"]
|
||
},
|
||
{
|
||
"id": "operations_admin",
|
||
"name": "运维管理员",
|
||
"description": "只读权限,仅查看和监控",
|
||
"permissions": ["read:*"]
|
||
},
|
||
{
|
||
"id": "channel_admin",
|
||
"name": "渠道管理员",
|
||
"description": "渠道内部管理权限",
|
||
"permissions": ["read:channel", "write:tenants", "read:billing"]
|
||
},
|
||
{
|
||
"id": "user",
|
||
"name": "普通用户",
|
||
"description": "标准用户权限",
|
||
"permissions": ["read:self", "use:agents"]
|
||
}
|
||
]
|
||
}
|
||
|
||
|
||
@router.get("/admin/channels/{channel_id}/admins")
|
||
async def admin_get_channel_admins(channel_id: str, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
"""获取渠道管理员列表"""
|
||
from models import User
|
||
channel = await db.get(Channel, uuid.UUID(channel_id)) if channel_id else None
|
||
if not channel:
|
||
raise HTTPException(status_code=404, detail="channel not found")
|
||
|
||
# 查询渠道下的管理员
|
||
admins = (await db.execute(
|
||
select(User).where(
|
||
User.channel_id == channel.id,
|
||
User.role == "channel_admin",
|
||
User.status == "active"
|
||
)
|
||
)).scalars().all()
|
||
|
||
items = [
|
||
{
|
||
"id": str(admin.id),
|
||
"name": admin.name or admin.full_name,
|
||
"email": admin.email,
|
||
"role": admin.role,
|
||
"status": admin.status,
|
||
"createdAt": admin.created_at.isoformat() if admin.created_at else None,
|
||
}
|
||
for admin in admins
|
||
]
|
||
|
||
return {
|
||
"channelId": str(channel.id),
|
||
"channelName": channel.name,
|
||
"admins": items
|
||
}
|
||
|
||
|
||
@router.post("/admin/admins/create")
|
||
async def admin_admins_create(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||
admin_id = str(uuid.uuid4())
|
||
admin = {"id": admin_id, **payload, "createdAt": _now()}
|
||
store.channel_admins[admin_id] = admin
|
||
return admin
|
||
|
||
|
||
@router.get("/admin/providers/stats")
|
||
async def admin_providers_stats(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
models = (await db.execute(select(ProviderModel))).scalars().all()
|
||
return {
|
||
"providers": len(models),
|
||
"models": [
|
||
{
|
||
"id": str(m.id),
|
||
"name": m.name,
|
||
"apiUrl": m.api_url,
|
||
"supportedModels": m.supported_models,
|
||
}
|
||
for m in models
|
||
],
|
||
}
|
||
|
||
|
||
@router.get("/admin/channels/backend/stats")
|
||
async def admin_channels_backend_stats() -> Dict[str, Any]:
|
||
return {"channels": len(store.channels), "applications": len(store.resource_applications)}
|
||
|
||
|
||
# ----- Provider Management -----
|
||
|
||
|
||
@router.post("/providers/auth/login")
|
||
async def providers_login(payload: Dict[str, str], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
email = payload.get("email")
|
||
password = payload.get("password") or "temp-pass"
|
||
if not email:
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
|
||
user = await ensure_user(email, password, db)
|
||
if user.hashed_password and not verify_password(password, user.hashed_password):
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
|
||
token = create_access_token({"sub": str(user.id), "email": email, "role": "provider_admin"})
|
||
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
|
||
|
||
|
||
@router.get("/providers/models")
|
||
async def providers_models(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
models = (await db.execute(select(ProviderModel))).scalars().all()
|
||
return {
|
||
"items": [
|
||
{
|
||
"id": str(m.id),
|
||
"name": m.name,
|
||
"apiUrl": m.api_url,
|
||
"supportedModels": m.supported_models,
|
||
"rpm": m.rpm,
|
||
"tpm": m.tpm,
|
||
}
|
||
for m in models
|
||
]
|
||
}
|
||
|
||
|
||
@router.post("/providers/models/add")
|
||
async def providers_models_add(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
||
required = {"name", "apiUrl", "apiKey"}
|
||
missing = [k for k in required if not payload.get(k)]
|
||
if missing:
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Missing fields: {', '.join(missing)}")
|
||
provider_model = ProviderModel(
|
||
name=payload["name"],
|
||
api_url=payload["apiUrl"],
|
||
api_key=payload["apiKey"],
|
||
supported_models=payload.get("supportedModels", []),
|
||
rpm=payload.get("rpm", 0),
|
||
tpm=payload.get("tpm", 0),
|
||
)
|
||
db.add(provider_model)
|
||
await db.commit()
|
||
await db.refresh(provider_model)
|
||
return {
|
||
"id": str(provider_model.id),
|
||
"name": provider_model.name,
|
||
"apiUrl": provider_model.api_url,
|
||
"supportedModels": provider_model.supported_models,
|
||
}
|
||
|
||
|
||
@router.get("/providers/data")
|
||
async def providers_data() -> Dict[str, Any]:
|
||
if not store.provider_data:
|
||
store.provider_data.append(
|
||
{
|
||
"id": str(uuid.uuid4()),
|
||
"name": "RapidAPI",
|
||
"category": "api",
|
||
"endpoints": len(store.gateway_apis),
|
||
}
|
||
)
|
||
return {"items": store.provider_data}
|