forked from xiaohei/taiji-AI-PAD
1016 lines
36 KiB
Python
1016 lines
36 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,
|
|
)
|
|
from monitoring import system_monitor
|
|
from ..auth import create_access_token, ensure_user, get_password_hash, verify_password
|
|
|
|
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(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
|
tenants = (await db.execute(select(Tenant))).scalars().all()
|
|
items = [
|
|
{
|
|
"id": str(t.id),
|
|
"name": t.name,
|
|
"subscriptionTier": t.subscription_tier,
|
|
"discount": t.discount,
|
|
"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], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
|
|
if not payload.get("name"):
|
|
raise HTTPException(status_code=400, detail="name is required")
|
|
tenant = Tenant(
|
|
name=payload["name"],
|
|
subscription_tier=payload.get("subscriptionTier", "free"),
|
|
discount=payload.get("discount", 0),
|
|
channel_id=uuid.UUID(payload["channelId"]) if payload.get("channelId") else None,
|
|
)
|
|
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}/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.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 = create_access_token({"sub": str(user.id), "email": email, "role": "super_admin"})
|
|
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": ["billing_admin", "operations_admin", "super_admin"]}
|
|
|
|
|
|
@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}
|