forked from xiaohei/taiji-AI-PAD
761 lines
26 KiB
Python
761 lines
26 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,
|
|
Execution,
|
|
Billing,
|
|
GatewayAPI,
|
|
ProviderModel,
|
|
Tenant,
|
|
Tool,
|
|
User,
|
|
ModelBillingRecord,
|
|
)
|
|
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.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]:
|
|
from datetime import datetime, timedelta
|
|
|
|
user_id = await _get_principal_user_id(request, db)
|
|
|
|
# 活跃Agent数量(属于该用户的)
|
|
agent_count = (
|
|
await db.execute(
|
|
select(func.count(Agent.id))
|
|
.where(Agent.owner_id == user_id)
|
|
.where(Agent.status == "active")
|
|
)
|
|
).scalar() or 0
|
|
|
|
# 总请求数(统计该用户的模型调用次数)
|
|
total_requests = (
|
|
await db.execute(
|
|
select(func.count(ModelBillingRecord.id))
|
|
.where(ModelBillingRecord.tenant_id == user_id)
|
|
)
|
|
).scalar() or 0
|
|
|
|
# 计算24小时内的请求数(统计该用户的模型调用次数)
|
|
time_24h_ago = datetime.utcnow() - timedelta(hours=24)
|
|
requests_24h = (
|
|
await db.execute(
|
|
select(func.count(ModelBillingRecord.id))
|
|
.where(ModelBillingRecord.tenant_id == user_id)
|
|
.where(ModelBillingRecord.created_at >= time_24h_ago)
|
|
)
|
|
).scalar() or 0
|
|
|
|
health = await system_monitor.get_system_health()
|
|
balance = await _get_balance(db, user_id)
|
|
return {
|
|
"activeAgents": agent_count,
|
|
"totalRequests": total_requests,
|
|
"requests24h": requests_24h,
|
|
"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)}
|
|
|
|
|
|
# ----- 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}
|
|
|
|
|
|
@router.post("/workflows/{workflow_id}/run")
|
|
async def run_workflow(workflow_id: str, payload: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
"""
|
|
运行工作流
|
|
|
|
执行指定的工作流,按顺序调用工作流中的各个Agent节点
|
|
"""
|
|
if workflow_id not in store.workflows:
|
|
raise HTTPException(status_code=404, detail="workflow not found")
|
|
|
|
workflow = store.workflows[workflow_id]
|
|
|
|
# 创建执行记录
|
|
execution_id = str(uuid.uuid4())
|
|
started_at = _now()
|
|
|
|
# 模拟执行工作流中的节点
|
|
nodes = workflow.get("nodes", [])
|
|
node_results = []
|
|
|
|
for node in nodes:
|
|
node_result = {
|
|
"nodeId": node.get("agentId"),
|
|
"agentName": node.get("agentName"),
|
|
"order": node.get("order"),
|
|
"status": "completed",
|
|
"startedAt": _now(),
|
|
"completedAt": _now(),
|
|
"output": {"message": f"Node {node.get('order')} executed successfully"}
|
|
}
|
|
node_results.append(node_result)
|
|
|
|
# 更新工作流状态
|
|
workflow["lastRunAt"] = started_at
|
|
workflow["lastRunStatus"] = "completed"
|
|
|
|
return {
|
|
"success": True,
|
|
"data": {
|
|
"executionId": execution_id,
|
|
"workflowId": workflow_id,
|
|
"workflowName": workflow.get("name"),
|
|
"status": "completed",
|
|
"startedAt": started_at,
|
|
"completedAt": _now(),
|
|
"nodeResults": node_results,
|
|
"totalNodes": len(nodes),
|
|
"completedNodes": len(nodes),
|
|
},
|
|
"message": f"工作流 {workflow.get('name')} 执行完成"
|
|
}
|
|
|
|
|
|
# ----- 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/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
|
|
]
|
|
}
|
|
|
|
|
|
# ----- 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}
|
|
|
|
|
|
# =====================================================
|
|
# 注意: 以下 /admin/* 接口已移至 admin.py,避免重复定义
|
|
# 已删除的接口包括:
|
|
# - /admin/dashboard/stats (从 AgentBillingRecord 和 ModelBillingRecord 统计)
|
|
# - /admin/channels (使用 admin.py 中的完整实现)
|
|
# - /admin/channels/create (使用 admin.py 中带 LiteLLM 集成的实现)
|
|
# - /admin/channels/{channel_id}/commission (使用 admin.py 中的实现)
|
|
# - /admin/channels/{channel_id}/resources GET/PUT (使用 admin.py 中的实现)
|
|
# - /admin/channels/applications (使用 admin.py 中的数据库实现)
|
|
# - /admin/resources/models (使用 admin.py 中的 ModelProvider 实现)
|
|
# - /admin/resources/agents (使用 admin.py 中的完整实现)
|
|
# - /admin/monitoring/agents (使用 admin.py 中的 K8s+DB 实现)
|
|
# - /admin/billing/overview (使用 admin.py 中的正确计费表实现)
|
|
# - /admin/roles (使用 admin.py 中的实现)
|
|
# - /admin/channels/{channel_id}/admins (使用 admin.py 中的实现)
|
|
# - /admin/admins/create (使用 admin.py 中的实现)
|
|
# =====================================================
|
|
|
|
|
|
# 保留: /admin/resources/models/add - 独立功能,admin.py 中没有对应实现
|
|
@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,
|
|
}
|
|
|
|
|
|
# 保留: /admin/providers/stats - 独立功能,admin.py 中没有对应实现
|
|
@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
|
|
],
|
|
}
|
|
|
|
|
|
# ----- 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}
|