feat: 更新多个页面的API集成和功能优化
- 更新admin/dashboard页面:完善API集成,删除假数据 - 更新admin/login和channel/login:优化登录流程 - 更新agent-factory、billing、data-tools、model-gateway、orchestration页面:API集成优化 - 更新channel/dashboard:渠道管理功能完善 - 更新lib/api-client.ts:API客户端功能增强 - 更新lib/auth.ts:认证功能优化 - 删除过时的API文档文件
This commit is contained in:
-879
@@ -1,879 +0,0 @@
|
||||
# taiji-AI-PAD API 接口文档
|
||||
|
||||
**版本**: v1.2.1
|
||||
**更新时间**: 2025年12月22日
|
||||
**最后更新**: 2025年12月22日
|
||||
**基础URL**:
|
||||
- Data Ingestion 服务: `http://localhost:8001`
|
||||
- MCP Server 服务: `http://localhost:8002`
|
||||
- API Gateway: `http://localhost:80`
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [Data Ingestion 服务 API](#data-ingestion-服务-api)
|
||||
2. [MCP Server 服务 API](#mcp-server-服务-api)
|
||||
3. [通用响应格式](#通用响应格式)
|
||||
4. [错误码说明](#错误码说明)
|
||||
|
||||
---
|
||||
|
||||
## Data Ingestion 服务 API
|
||||
|
||||
**基础URL**: `http://localhost:8001`
|
||||
|
||||
### 1. 健康检查
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
检查服务健康状态。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-12-22T05:04:23.211960",
|
||||
"services": {
|
||||
"data_ingestion": "healthy",
|
||||
"redis": "healthy",
|
||||
"nats": "healthy",
|
||||
"rapidapi": "healthy",
|
||||
"apillama": "healthy"
|
||||
},
|
||||
"stats": {
|
||||
"total_apis": 0,
|
||||
"processed_apis": 0,
|
||||
"generated_tools": 20,
|
||||
"cache_size": 44
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 同步 RapidAPI 端点
|
||||
|
||||
**POST** `/rapidapi/sync`
|
||||
|
||||
同步 RapidAPI 端点列表。
|
||||
|
||||
**查询参数**:
|
||||
- `category` (string, 可选): API 分类
|
||||
- `limit` (int, 可选, 默认: 100): 同步数量限制
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
POST /rapidapi/sync?category=weather&limit=50
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"message": "RapidAPI端点同步已启动",
|
||||
"category": "weather",
|
||||
"limit": 50
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 测试 RapidAPI 端点
|
||||
|
||||
**POST** `/rapidapi/test`
|
||||
|
||||
测试 RapidAPI 端点调用。
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"endpoint": "https://rapidapi.com/api/weather/v1/current",
|
||||
"method": "GET",
|
||||
"params": {
|
||||
"location": "Beijing"
|
||||
},
|
||||
"headers": {
|
||||
"X-Custom-Header": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status_code": 200,
|
||||
"data": {
|
||||
"temperature": 25,
|
||||
"condition": "sunny"
|
||||
},
|
||||
"response_time": 123.45,
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 解析 OpenAPI 规范
|
||||
|
||||
**POST** `/openapi/parse`
|
||||
|
||||
解析 OpenAPI/Swagger 规范文档。
|
||||
|
||||
**查询参数**:
|
||||
- `url` (string, 必需): OpenAPI 文档 URL
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
POST /openapi/parse?url=https://api.example.com/openapi.json
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"url": "https://api.example.com/openapi.json",
|
||||
"title": "Example API",
|
||||
"version": "1.0.0",
|
||||
"endpoints_count": 15,
|
||||
"schemas_count": 8,
|
||||
"parsed_data": {
|
||||
"info": {
|
||||
"title": "Example API",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"summary": "Get users",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parsing_time": 0.234
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. APILLAMA 处理 API 文档
|
||||
|
||||
**POST** `/apillama/process`
|
||||
|
||||
使用 APILLAMA 处理 API 文档,生成结构化 Schema。
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"api_doc": {
|
||||
"title": "Weather API",
|
||||
"description": "Get weather information",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"type": "string",
|
||||
"description": "City name",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"service": "Weather service",
|
||||
"version": "1.0"
|
||||
},
|
||||
"output_format": "json_schema"
|
||||
}
|
||||
```
|
||||
|
||||
**请求参数说明**:
|
||||
- `api_doc` (string | object, 必需): API 文档,可以是字符串或对象
|
||||
- `context` (object, 可选): 上下文信息
|
||||
- `output_format` (string, 可选): 输出格式,可选值: `json_schema`, `pydantic`, `openapi` (默认: `json_schema`)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"processed": true,
|
||||
"output_format": "json_schema",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "City name"
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
},
|
||||
"description": "Weather API for getting current weather information",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"type": "string",
|
||||
"description": "City name",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"location": "Beijing",
|
||||
"temperature": 25
|
||||
}
|
||||
],
|
||||
"processing_time": 1.234,
|
||||
"confidence_score": 0.95,
|
||||
"completeness_score": 0.88
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. 生成工具定义
|
||||
|
||||
**POST** `/tools/generate`
|
||||
|
||||
从 API 端点生成工具定义。
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"url": "https://api.example.com/users",
|
||||
"method": "GET",
|
||||
"name": "get_users",
|
||||
"description": "Get list of users",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"type": "integer",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"headers": {
|
||||
"Authorization": "Bearer token"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"message": "工具生成任务已启动",
|
||||
"endpoint": "https://api.example.com/users",
|
||||
"method": "GET"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 获取工具列表
|
||||
|
||||
**GET** `/tools`
|
||||
|
||||
获取已生成的工具列表。
|
||||
|
||||
**查询参数**:
|
||||
- `category` (string, 可选): 工具分类
|
||||
- `limit` (int, 可选, 默认: 100): 返回数量限制
|
||||
- `offset` (int, 可选, 默认: 0): 偏移量
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
GET /tools?category=weather&limit=20&offset=0
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get weather information",
|
||||
"category": "weather",
|
||||
"url": "https://api.example.com/weather",
|
||||
"method": "GET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"created_at": "2025-12-22T05:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. 获取特定工具定义
|
||||
|
||||
**GET** `/tools/{tool_name}`
|
||||
|
||||
获取特定工具的定义。
|
||||
|
||||
**路径参数**:
|
||||
- `tool_name` (string, 必需): 工具名称
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get weather information",
|
||||
"category": "weather",
|
||||
"url": "https://api.example.com/weather",
|
||||
"method": "GET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"created_at": "2025-12-22T05:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. 删除工具
|
||||
|
||||
**DELETE** `/tools/{tool_name}`
|
||||
|
||||
删除指定的工具定义。
|
||||
|
||||
**路径参数**:
|
||||
- `tool_name` (string, 必需): 工具名称
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"message": "工具已删除",
|
||||
"tool_name": "get_weather"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. 获取统计信息
|
||||
|
||||
**GET** `/stats`
|
||||
|
||||
获取服务统计信息。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"total_apis": 100,
|
||||
"processed_apis": 85,
|
||||
"generated_tools": 20,
|
||||
"failed_processes": 2,
|
||||
"cache_size": 44,
|
||||
"last_sync": "2025-12-22T05:00:00Z",
|
||||
"categories": {
|
||||
"weather": 15,
|
||||
"finance": 10,
|
||||
"general": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11. 清除缓存
|
||||
|
||||
**POST** `/cache/clear`
|
||||
|
||||
清除所有缓存数据。
|
||||
|
||||
**查询参数**:
|
||||
- `pattern` (string, 可选): 缓存键模式,如 `rapidapi:*`
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
POST /cache/clear?pattern=rapidapi:*
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"message": "缓存已清除",
|
||||
"cleared_keys": 150
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 12. Prometheus Metrics
|
||||
|
||||
**GET** `/metrics`
|
||||
|
||||
获取 Prometheus 格式的监控指标。
|
||||
|
||||
**响应格式**: Prometheus 文本格式
|
||||
|
||||
**示例**:
|
||||
```
|
||||
# HELP http_requests_total Total number of HTTP requests
|
||||
# TYPE http_requests_total counter
|
||||
http_requests_total{method="GET",status="200"} 1500
|
||||
http_requests_total{method="POST",status="200"} 800
|
||||
|
||||
# HELP apillama_processing_duration_seconds APILLAMA processing duration
|
||||
# TYPE apillama_processing_duration_seconds histogram
|
||||
apillama_processing_duration_seconds_bucket{le="0.5"} 100
|
||||
apillama_processing_duration_seconds_bucket{le="1.0"} 200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Server 服务 API
|
||||
|
||||
**基础URL**: `http://localhost:8002`
|
||||
|
||||
### 1. 健康检查
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
检查 MCP Server 健康状态。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-12-22T05:04:23.211960",
|
||||
"services": {
|
||||
"database": "healthy",
|
||||
"redis": "healthy",
|
||||
"nats": "healthy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 注册 Agent
|
||||
|
||||
**POST** `/agents`
|
||||
|
||||
注册新的 Agent。
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"name": "weather_agent",
|
||||
"description": "Weather information agent",
|
||||
"capabilities": ["weather_query", "location_search"],
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"author": "taiji-team"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"agent_id": "agent_123456",
|
||||
"name": "weather_agent",
|
||||
"description": "Weather information agent",
|
||||
"status": "active",
|
||||
"created_at": "2025-12-22T05:00:00Z",
|
||||
"capabilities": ["weather_query", "location_search"],
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"author": "taiji-team"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 获取 Agent 列表
|
||||
|
||||
**GET** `/agents`
|
||||
|
||||
获取所有注册的 Agent 列表。
|
||||
|
||||
**查询参数**:
|
||||
- `status` (string, 可选): 过滤状态,如 `active`, `inactive`
|
||||
- `limit` (int, 可选, 默认: 100): 返回数量限制
|
||||
- `offset` (int, 可选, 默认: 0): 偏移量
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"agent_id": "agent_123456",
|
||||
"name": "weather_agent",
|
||||
"description": "Weather information agent",
|
||||
"status": "active",
|
||||
"created_at": "2025-12-22T05:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 获取特定 Agent
|
||||
|
||||
**GET** `/agents/{agent_id}`
|
||||
|
||||
获取特定 Agent 的详细信息。
|
||||
|
||||
**路径参数**:
|
||||
- `agent_id` (string, 必需): Agent ID
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"agent_id": "agent_123456",
|
||||
"name": "weather_agent",
|
||||
"description": "Weather information agent",
|
||||
"status": "active",
|
||||
"created_at": "2025-12-22T05:00:00Z",
|
||||
"capabilities": ["weather_query", "location_search"],
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"author": "taiji-team"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 执行 Agent 工具
|
||||
|
||||
**POST** `/agents/{agent_id}/execute`
|
||||
|
||||
执行 Agent 的工具调用。支持三种工具类型:
|
||||
- **API 工具**: 调用外部 API
|
||||
- **函数工具**: 执行本地 Python 函数(新增)
|
||||
- **LLM 工具**: 调用 LLM 模型
|
||||
|
||||
**路径参数**:
|
||||
- `agent_id` (string, 必需): Agent ID
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"tool_name": "math_add",
|
||||
"parameters": {
|
||||
"a": 10,
|
||||
"b": 20
|
||||
},
|
||||
"context": {
|
||||
"session_id": "session_123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**函数工具示例**:
|
||||
```json
|
||||
{
|
||||
"tool_name": "math_add",
|
||||
"parameters": {
|
||||
"a": 10,
|
||||
"b": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": 30.0,
|
||||
"execution_time": 0.001,
|
||||
"tool_name": "math_add"
|
||||
}
|
||||
```
|
||||
|
||||
**可用的函数工具**:
|
||||
- 数学函数: `math_add`, `math_subtract`, `math_multiply`, `math_divide`, `math_power`
|
||||
- 字符串函数: `string_upper`, `string_lower`, `string_length`, `string_replace`
|
||||
- 日期时间: `datetime_now`
|
||||
- JSON: `json_parse`, `json_stringify`
|
||||
- 哈希: `hash_md5`, `hash_sha256`
|
||||
- Base64: `base64_encode`, `base64_decode`
|
||||
|
||||
**安全特性**:
|
||||
- ✅ 函数白名单验证
|
||||
- ✅ 沙箱执行环境
|
||||
- ✅ 超时控制(默认 5 秒)
|
||||
- ✅ 参数验证和类型检查
|
||||
|
||||
---
|
||||
|
||||
### 6. 获取工具列表
|
||||
|
||||
**GET** `/tools`
|
||||
|
||||
获取所有可用工具列表。
|
||||
|
||||
**查询参数**:
|
||||
- `category` (string, 可选): 工具分类
|
||||
- `limit` (int, 可选, 默认: 100): 返回数量限制
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get weather information",
|
||||
"category": "weather",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Prometheus Metrics
|
||||
|
||||
**GET** `/metrics`
|
||||
|
||||
获取 Prometheus 格式的监控指标。
|
||||
|
||||
**注意**: 当前返回 TODO 消息,待实现。
|
||||
|
||||
---
|
||||
|
||||
## WebSocket API
|
||||
|
||||
### MCP Protocol WebSocket
|
||||
|
||||
**WebSocket URL**: `ws://localhost:8002/ws/{agent_id}`
|
||||
|
||||
**连接示例**:
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:8002/ws/agent_123456');
|
||||
```
|
||||
|
||||
**消息格式**:
|
||||
```json
|
||||
{
|
||||
"type": "mcp_request",
|
||||
"payload": {
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"type": "mcp_response",
|
||||
"payload": {
|
||||
"result": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
所有成功响应都遵循以下格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {...},
|
||||
"message": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
|
||||
所有错误响应都遵循以下格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "错误描述",
|
||||
"details": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| HTTP 状态码 | 错误码 | 说明 |
|
||||
|------------|--------|------|
|
||||
| 400 | `BAD_REQUEST` | 请求参数错误 |
|
||||
| 401 | `UNAUTHORIZED` | 未授权 |
|
||||
| 403 | `FORBIDDEN` | 禁止访问 |
|
||||
| 404 | `NOT_FOUND` | 资源不存在 |
|
||||
| 500 | `INTERNAL_ERROR` | 服务器内部错误 |
|
||||
| 503 | `SERVICE_UNAVAILABLE` | 服务不可用 |
|
||||
|
||||
---
|
||||
|
||||
## 认证说明
|
||||
|
||||
当前版本暂未实现认证机制,所有 API 均可直接访问。
|
||||
|
||||
**未来版本将支持**:
|
||||
- API Key 认证
|
||||
- JWT Token 认证
|
||||
- OAuth 2.0
|
||||
|
||||
---
|
||||
|
||||
## 限流说明
|
||||
|
||||
当前版本暂未实现限流机制。
|
||||
|
||||
**未来版本将支持**:
|
||||
- 基于 IP 的限流
|
||||
- 基于 API Key 的限流
|
||||
- 基于用户的限流
|
||||
|
||||
---
|
||||
|
||||
## 交互式 API 文档
|
||||
|
||||
### Swagger UI
|
||||
|
||||
- Data Ingestion: `http://localhost:8001/docs`
|
||||
- MCP Server: `http://localhost:8002/docs`
|
||||
|
||||
### ReDoc
|
||||
|
||||
- Data Ingestion: `http://localhost:8001/redoc`
|
||||
- MCP Server: `http://localhost:8002/redoc`
|
||||
|
||||
### OpenAPI JSON
|
||||
|
||||
- Data Ingestion: `http://localhost:8001/openapi.json`
|
||||
- MCP Server: `http://localhost:8002/openapi.json`
|
||||
|
||||
---
|
||||
|
||||
## 前端集成示例
|
||||
|
||||
### JavaScript/TypeScript
|
||||
|
||||
```typescript
|
||||
// 健康检查
|
||||
const healthCheck = async () => {
|
||||
const response = await fetch('http://localhost:8001/health');
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
};
|
||||
|
||||
// APILLAMA 处理
|
||||
const processAPI = async (apiDoc: any) => {
|
||||
const response = await fetch('http://localhost:8001/apillama/process', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
api_doc: apiDoc,
|
||||
context: { service: 'example' },
|
||||
output_format: 'json_schema'
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
// 获取工具列表
|
||||
const getTools = async (category?: string) => {
|
||||
const url = category
|
||||
? `http://localhost:8001/tools?category=${category}`
|
||||
: 'http://localhost:8001/tools';
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 健康检查
|
||||
def health_check():
|
||||
response = requests.get('http://localhost:8001/health')
|
||||
return response.json()
|
||||
|
||||
# APILLAMA 处理
|
||||
def process_api(api_doc, context=None, output_format='json_schema'):
|
||||
response = requests.post(
|
||||
'http://localhost:8001/apillama/process',
|
||||
json={
|
||||
'api_doc': api_doc,
|
||||
'context': context or {},
|
||||
'output_format': output_format
|
||||
}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
# 获取工具列表
|
||||
def get_tools(category=None):
|
||||
params = {'category': category} if category else {}
|
||||
response = requests.get('http://localhost:8001/tools', params=params)
|
||||
return response.json()
|
||||
```
|
||||
|
||||
### cURL
|
||||
|
||||
```bash
|
||||
# 健康检查
|
||||
curl http://localhost:8001/health
|
||||
|
||||
# APILLAMA 处理
|
||||
curl -X POST http://localhost:8001/apillama/process \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"api_doc": {"title": "Test API"},
|
||||
"context": {"service": "test"},
|
||||
"output_format": "json_schema"
|
||||
}'
|
||||
|
||||
# 获取工具列表
|
||||
curl http://localhost:8001/tools?category=weather
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **CORS**: 当前配置允许所有来源,生产环境需要限制
|
||||
2. **认证**: 当前版本未实现认证,生产环境需要添加
|
||||
3. **限流**: 当前版本未实现限流,生产环境需要添加
|
||||
4. **错误处理**: 所有 API 调用都应该处理错误情况
|
||||
5. **超时设置**: 建议设置合理的请求超时时间
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.2.1
|
||||
**最后更新**: 2025年12月22日
|
||||
**维护者**: taiji-AI-PAD 项目组
|
||||
|
||||
## 更新日志
|
||||
|
||||
- **v1.2.1** (2025-12-22): 添加 MCP Server 函数工具调用说明
|
||||
- **v1.2.0** (2025-12-22): 初始版本,包含所有 API 端点文档
|
||||
|
||||
+692
-31
@@ -75,6 +75,21 @@ export default function AdminDashboard() {
|
||||
const [commissionRate, setCommissionRate] = useState<string>("")
|
||||
const [selectedTenant, setSelectedTenant] = useState<string | null>(null) // Declare selectedTenant variable
|
||||
const [computeConfigOpen, setComputeConfigOpen] = useState(false) // State for compute configuration dialog
|
||||
|
||||
// 新增:渠道管理对话框状态
|
||||
const [isChannelDetailsDialogOpen, setIsChannelDetailsDialogOpen] = useState(false)
|
||||
const [isChannelEditDialogOpen, setIsChannelEditDialogOpen] = useState(false)
|
||||
const [isViewTenantsDialogOpen, setIsViewTenantsDialogOpen] = useState(false)
|
||||
const [channelTenantsLoading, setChannelTenantsLoading] = useState(false)
|
||||
const [channelTenants, setChannelTenants] = useState<any[]>([])
|
||||
const [selectedTenantForEdit, setSelectedTenantForEdit] = useState<any>(null)
|
||||
const [channelEditForm, setChannelEditForm] = useState({
|
||||
name: "",
|
||||
contactName: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
})
|
||||
const [channelAdmins, setChannelAdmins] = useState<any[]>([])
|
||||
const [newChannelForm, setNewChannelForm] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
@@ -506,7 +521,7 @@ export default function AdminDashboard() {
|
||||
setAgents(agentMonitoringData)
|
||||
}
|
||||
|
||||
// 加载模型提供商
|
||||
// 加载模型提供商(用于渠道资源分配)
|
||||
const modelProvidersData = await TaijiAPIClient.getAdminModelProviders()
|
||||
if (modelProvidersData?.data?.providers && Array.isArray(modelProvidersData.data.providers)) {
|
||||
setAvailableModels(modelProvidersData.data.providers)
|
||||
@@ -516,6 +531,18 @@ export default function AdminDashboard() {
|
||||
setModelProviders(modelProvidersData)
|
||||
}
|
||||
|
||||
// 加载供应商列表(用于供应商管理)
|
||||
try {
|
||||
const providersData = await TaijiAPIClient.getModelProviders()
|
||||
if (providersData?.data?.providers && Array.isArray(providersData.data.providers)) {
|
||||
// 如果providers不为空,则使用该列表
|
||||
setModelProviders(providersData.data.providers)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Failed to load providers list:", error)
|
||||
// 如果获取失败,继续使用admin API的数据
|
||||
}
|
||||
|
||||
// 加载Agent资源
|
||||
const agentResourcesData = await TaijiAPIClient.getAdminAgentResources()
|
||||
if (agentResourcesData?.data?.agents && Array.isArray(agentResourcesData.data.agents)) {
|
||||
@@ -538,9 +565,23 @@ export default function AdminDashboard() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("admin_token")
|
||||
router.push("/admin/login")
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
// 调用后端logout API
|
||||
await TaijiAPIClient.logout()
|
||||
} catch (error) {
|
||||
console.error("Logout API call failed:", error)
|
||||
} finally {
|
||||
// 清除所有本地存储的token和用户信息
|
||||
localStorage.removeItem("admin_token")
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("channel_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
|
||||
router.push("/admin/login")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -617,18 +658,70 @@ export default function AdminDashboard() {
|
||||
setIsConfigProviderOpen(true)
|
||||
}
|
||||
|
||||
const handleSaveProvider = () => {
|
||||
console.log("Saving provider:", providerForm)
|
||||
setIsAddProviderOpen(false)
|
||||
setIsConfigProviderOpen(false)
|
||||
setProviderForm({
|
||||
name: "",
|
||||
url: "",
|
||||
apiKey: "",
|
||||
models: "",
|
||||
rpm: "",
|
||||
tpm: "",
|
||||
})
|
||||
const handleSaveProvider = async () => {
|
||||
try {
|
||||
// 验证必填字段
|
||||
if (!providerForm.name || !providerForm.url || !providerForm.apiKey || !providerForm.models) {
|
||||
alert(language === "zh" ? "请填写所有必填字段" : "Please fill in all required fields")
|
||||
return
|
||||
}
|
||||
|
||||
const supportedModels = providerForm.models
|
||||
.split(",")
|
||||
.map((m) => m.trim())
|
||||
.filter((m) => m.length > 0)
|
||||
|
||||
if (supportedModels.length === 0) {
|
||||
alert(language === "zh" ? "请至少输入一个模型" : "Please enter at least one model")
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是添加新的还是更新现有的供应商
|
||||
if (selectedProvider) {
|
||||
// 更新现有供应商
|
||||
await TaijiAPIClient.updateProvider(selectedProvider.id, {
|
||||
name: providerForm.name,
|
||||
provider: providerType === "model" ? "openai" : "anthropic",
|
||||
apiUrl: providerForm.url,
|
||||
apiKey: providerForm.apiKey,
|
||||
supportedModels,
|
||||
rpm: parseInt(providerForm.rpm) || 3500,
|
||||
tpm: parseInt(providerForm.tpm) || 90000,
|
||||
})
|
||||
alert(language === "zh" ? "供应商更新成功" : "Provider updated successfully")
|
||||
} else {
|
||||
// 创建新供应商
|
||||
await TaijiAPIClient.createModelProvider({
|
||||
name: providerForm.name,
|
||||
provider: "openai", // 默认为 openai,实际应该根据供应商类型选择
|
||||
apiUrl: providerForm.url,
|
||||
apiKey: providerForm.apiKey,
|
||||
supportedModels,
|
||||
rpm: parseInt(providerForm.rpm) || 3500,
|
||||
tpm: parseInt(providerForm.tpm) || 90000,
|
||||
})
|
||||
alert(language === "zh" ? "供应商创建成功" : "Provider created successfully")
|
||||
}
|
||||
|
||||
// 重新加载数据
|
||||
await loadDashboardData()
|
||||
|
||||
// 清空表单
|
||||
setIsAddProviderOpen(false)
|
||||
setIsConfigProviderOpen(false)
|
||||
setProviderForm({
|
||||
name: "",
|
||||
url: "",
|
||||
apiKey: "",
|
||||
models: "",
|
||||
rpm: "",
|
||||
tpm: "",
|
||||
})
|
||||
setSelectedProvider(null)
|
||||
} catch (error) {
|
||||
console.error("Failed to save provider:", error)
|
||||
alert(language === "zh" ? "保存失败,请检查输入" : "Failed to save provider. Please check your input")
|
||||
}
|
||||
}
|
||||
|
||||
// const handleAgentAllocation = (channel: (typeof channels)[0]) => { // REMOVED
|
||||
@@ -648,15 +741,55 @@ export default function AdminDashboard() {
|
||||
// }
|
||||
|
||||
// ADDED: Unified resource management handler
|
||||
const handleResourceManagement = (channel: (typeof channels)[0]) => {
|
||||
const handleResourceManagement = async (channel: (typeof channels)[0]) => {
|
||||
setSelectedChannel(channel)
|
||||
// 重置选择状态
|
||||
setSelectedModels([])
|
||||
setSelectedAgents([])
|
||||
setAgentQuantities({})
|
||||
// setMonthlyQuota("") // REMOVED
|
||||
setCreditLimit("")
|
||||
setCustomAgentCpu("2")
|
||||
setCustomAgentMemory("4")
|
||||
|
||||
// 从API加载当前渠道的资源配置
|
||||
try {
|
||||
const token = localStorage.getItem('admin_token') || localStorage.getItem('auth_token')
|
||||
const response = await fetch(`http://localhost:8002/api/admin/channels/${channel.id}/resources`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success && data.data) {
|
||||
// 加载已选择的模型
|
||||
if (data.data.models && Array.isArray(data.data.models)) {
|
||||
setSelectedModels(data.data.models)
|
||||
}
|
||||
// 加载已分配的Agent
|
||||
if (data.data.agents && Array.isArray(data.data.agents)) {
|
||||
const agentIds = data.data.agents.map((a: any) => a.agentId)
|
||||
setSelectedAgents(agentIds)
|
||||
const quantities: { [key: string]: number } = {}
|
||||
data.data.agents.forEach((a: any) => {
|
||||
quantities[a.agentId] = a.quantity || 1
|
||||
})
|
||||
setAgentQuantities(quantities)
|
||||
}
|
||||
// 加载自定义Agent资源配置
|
||||
if (data.data.customAgentResources) {
|
||||
setCustomAgentCpu(String(data.data.customAgentResources.cpu || 2))
|
||||
setCustomAgentMemory(String(data.data.customAgentResources.memory || 4))
|
||||
}
|
||||
// 加载授信额度
|
||||
if (data.data.channelCredit) {
|
||||
setCreditLimit(String(data.data.channelCredit))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load channel resources:", error)
|
||||
// 如果加载失败,保持默认值
|
||||
}
|
||||
|
||||
setIsResourceManagementOpen(true)
|
||||
}
|
||||
|
||||
@@ -690,6 +823,203 @@ export default function AdminDashboard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:查看渠道详情
|
||||
const handleViewChannelDetails = async (channel: (typeof channels)[0]) => {
|
||||
setSelectedChannel(channel)
|
||||
setIsChannelDetailsDialogOpen(true)
|
||||
// 可选:从API获取最新的渠道详情
|
||||
try {
|
||||
const response = await fetch(`/api/admin/channels/${channel.id}`, {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('admin_token') || localStorage.getItem('auth_token')}` }
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setSelectedChannel(data.data)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch channel details:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:编辑渠道信息
|
||||
const handleEditChannel = async (channel: (typeof channels)[0]) => {
|
||||
setSelectedChannel(channel)
|
||||
setChannelEditForm({
|
||||
name: channel.name,
|
||||
contactName: channel.contact || "",
|
||||
email: channel.email,
|
||||
phone: channel.phone || "",
|
||||
})
|
||||
// 获取渠道管理员列表
|
||||
try {
|
||||
const token = localStorage.getItem('admin_token') || localStorage.getItem('auth_token')
|
||||
const response = await fetch(`/api/admin/channels/${channel.id}/admins`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success && data.data) {
|
||||
setChannelAdmins(data.data.admins || [])
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch channel admins:', error)
|
||||
setChannelAdmins([])
|
||||
}
|
||||
setIsChannelEditDialogOpen(true)
|
||||
}
|
||||
|
||||
// 新增:保存渠道编辑
|
||||
const handleSaveChannelEdit = async () => {
|
||||
if (!selectedChannel) return
|
||||
try {
|
||||
const token = localStorage.getItem('admin_token') || localStorage.getItem('auth_token')
|
||||
const response = await fetch(`/api/admin/channels/${selectedChannel.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: channelEditForm.name,
|
||||
email: channelEditForm.email,
|
||||
contactName: channelEditForm.contactName,
|
||||
phone: channelEditForm.phone
|
||||
})
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setSelectedChannel({
|
||||
...selectedChannel,
|
||||
name: channelEditForm.name,
|
||||
email: channelEditForm.email,
|
||||
contact: channelEditForm.contactName,
|
||||
phone: channelEditForm.phone
|
||||
})
|
||||
setIsChannelEditDialogOpen(false)
|
||||
console.log('Channel updated successfully')
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to update channel:', response.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save channel edit:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:查看租户列表
|
||||
const handleViewTenants = async (channel: (typeof channels)[0]) => {
|
||||
setSelectedChannel(channel)
|
||||
setIsViewTenantsDialogOpen(true)
|
||||
setChannelTenantsLoading(true)
|
||||
try {
|
||||
const token = localStorage.getItem('channel_token') || localStorage.getItem('auth_token')
|
||||
const response = await fetch('http://localhost:8002/api/channel/tenants', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success && data.data.tenants) {
|
||||
setChannelTenants(data.data.tenants)
|
||||
} else {
|
||||
setChannelTenants([])
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to fetch tenants:', response.statusText)
|
||||
setChannelTenants([])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load tenants:", error)
|
||||
setChannelTenants([])
|
||||
} finally {
|
||||
setChannelTenantsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:修改租户密码
|
||||
const handleTenantChangePassword = async (tenantId: string) => {
|
||||
const tenant = channelTenants.find(t => t.id === tenantId)
|
||||
if (!tenant) return
|
||||
setSelectedTenantForEdit(tenant)
|
||||
// TODO: 打开修改密码对话框并调用 /api/channel/tenants/{tenant_id}/password
|
||||
console.log("Change password for tenant:", tenantId)
|
||||
}
|
||||
|
||||
// 新增:删除租户
|
||||
const handleDeleteTenant = async (tenantId: string) => {
|
||||
const tenant = channelTenants.find(t => t.id === tenantId)
|
||||
if (!tenant) return
|
||||
if (!window.confirm(language === "zh" ? `确定删除租户 ${tenant.name}?` : `Delete tenant ${tenant.name}?`)) return
|
||||
try {
|
||||
const token = localStorage.getItem('channel_token') || localStorage.getItem('auth_token')
|
||||
const response = await fetch(`/api/channel/tenants/${tenantId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setChannelTenants(channelTenants.filter(t => t.id !== tenantId))
|
||||
console.log('Tenant deleted successfully')
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to delete tenant:', response.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete tenant:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:禁用租户
|
||||
const handleDisableTenant = async (tenantId: string) => {
|
||||
try {
|
||||
const token = localStorage.getItem('channel_token') || localStorage.getItem('auth_token')
|
||||
const response = await fetch(`/api/channel/tenants/${tenantId}/status`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ status: 'disabled' })
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setChannelTenants(channelTenants.map(t =>
|
||||
t.id === tenantId ? { ...t, status: "disabled" } : t
|
||||
))
|
||||
console.log('Tenant disabled successfully')
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to disable tenant:', response.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to disable tenant:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:删除管理员
|
||||
const handleRemoveAdmin = async (adminType: "billing" | "operations" | "admin") => {
|
||||
if (!selectedChannel) return
|
||||
try {
|
||||
// TODO: 调用API删除管理员
|
||||
console.log(`Removing ${adminType} admin from channel:`, selectedChannel.id)
|
||||
setChannelAdmins({
|
||||
...channelAdmins,
|
||||
[adminType === "billing" ? "billingAdmin" : adminType === "operations" ? "operationsAdmin" : "admin"]: "",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to remove admin:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const subscriptionLevels = [
|
||||
{
|
||||
id: "free",
|
||||
@@ -1087,15 +1417,15 @@ export default function AdminDashboard() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-card border-border">
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleViewChannelDetails(channel)}>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
{text.viewDetails}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleEditChannel(channel)}>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
{text.edit}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleViewTenants(channel)}>
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
{language === "zh" ? "查看租户" : "View Tenants"}
|
||||
</DropdownMenuItem>
|
||||
@@ -1412,6 +1742,336 @@ export default function AdminDashboard() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Channel Details Dialog */}
|
||||
<Dialog open={isChannelDetailsDialogOpen} onOpenChange={setIsChannelDetailsDialogOpen}>
|
||||
<DialogContent className="bg-card border-border max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{language === "zh" ? "渠道详情" : "Channel Details"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{language === "zh" ? `查看渠道 "${selectedChannel?.name}" 的详细信息` : `View details for channel "${selectedChannel?.name}"`}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Basic Information */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-muted-foreground">
|
||||
{language === "zh" ? "渠道名称" : "Channel Name"}
|
||||
</label>
|
||||
<p className="text-base font-medium text-foreground">{selectedChannel?.name}</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-muted-foreground">
|
||||
{language === "zh" ? "状态" : "Status"}
|
||||
</label>
|
||||
<Badge variant="secondary" className="bg-green-500/10 text-green-600 border-green-500/20">
|
||||
{language === "zh" ? "活跃" : "Active"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-muted-foreground">
|
||||
{language === "zh" ? "联系人邮箱" : "Contact Email"}
|
||||
</label>
|
||||
<p className="text-base font-medium text-foreground">{selectedChannel?.email}</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-muted-foreground">
|
||||
{language === "zh" ? "创建日期" : "Created Date"}
|
||||
</label>
|
||||
<p className="text-base font-medium text-foreground">{selectedChannel?.createdAt || "2024-01-15"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resource Allocation */}
|
||||
<div className="border-t border-border pt-6">
|
||||
<h4 className="text-base font-semibold text-foreground mb-4">
|
||||
{language === "zh" ? "资源配置" : "Resource Configuration"}
|
||||
</h4>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="bg-muted/50 rounded-lg p-4 text-center">
|
||||
<p className="text-3xl font-bold text-primary mb-2">8</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "CPU核心" : "CPU Cores"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-muted/50 rounded-lg p-4 text-center">
|
||||
<p className="text-3xl font-bold text-primary mb-2">32GB</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "内存" : "Memory"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-muted/50 rounded-lg p-4 text-center">
|
||||
<p className="text-3xl font-bold text-primary mb-2">500GB</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "存储空间" : "Storage"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quota Information */}
|
||||
<div className="border-t border-border pt-6">
|
||||
<h4 className="text-base font-semibold text-foreground mb-4">
|
||||
{language === "zh" ? "配额信息" : "Quota Information"}
|
||||
</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "租户总数" : "Total Tenants"}
|
||||
</span>
|
||||
<span className="text-lg font-semibold text-foreground">{selectedChannel?.tenantCount || 3}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "授信额度" : "Credit Limit"}
|
||||
</span>
|
||||
<span className="text-lg font-semibold text-foreground">
|
||||
${selectedChannel?.creditLimit || "50,000"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "已用授信" : "Used Credit"}
|
||||
</span>
|
||||
<span className="text-lg font-semibold text-foreground">
|
||||
${selectedChannel?.usedCredit || "12,500"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-t border-border pt-3 mt-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-semibold text-foreground">
|
||||
{language === "zh" ? "剩余授信" : "Remaining Credit"}
|
||||
</span>
|
||||
<span className="text-lg font-bold text-green-600">
|
||||
${selectedChannel?.remainingCredit || "37,500"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsChannelDetailsDialogOpen(false)}>
|
||||
{language === "zh" ? "关闭" : "Close"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Channel Edit Dialog */}
|
||||
<Dialog open={isChannelEditDialogOpen} onOpenChange={setIsChannelEditDialogOpen}>
|
||||
<DialogContent className="bg-card border-border max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{language === "zh" ? "编辑渠道信息" : "Edit Channel"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{language === "zh" ? `修改渠道 "${selectedChannel?.name}" 的基本信息` : `Update basic information for channel "${selectedChannel?.name}"`}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Basic Information Form */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-foreground block mb-2">
|
||||
{language === "zh" ? "渠道名称" : "Channel Name"}
|
||||
</label>
|
||||
<Input
|
||||
value={channelEditForm.name}
|
||||
onChange={(e) => setChannelEditForm({ ...channelEditForm, name: e.target.value })}
|
||||
placeholder={language === "zh" ? "例: 阿里云渠道" : "e.g., Alibaba Cloud Partner"}
|
||||
className="bg-background border-border text-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-foreground block mb-2">
|
||||
{language === "zh" ? "联系人名称" : "Contact Person"}
|
||||
</label>
|
||||
<Input
|
||||
value={channelEditForm.contactName}
|
||||
onChange={(e) => setChannelEditForm({ ...channelEditForm, contactName: e.target.value })}
|
||||
placeholder={language === "zh" ? "例: 张三" : "e.g., John Doe"}
|
||||
className="bg-background border-border text-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-foreground block mb-2">
|
||||
{language === "zh" ? "联系邮箱" : "Email"}
|
||||
</label>
|
||||
<Input
|
||||
type="email"
|
||||
value={channelEditForm.email}
|
||||
onChange={(e) => setChannelEditForm({ ...channelEditForm, email: e.target.value })}
|
||||
placeholder={language === "zh" ? "例: contact@example.com" : "e.g., contact@example.com"}
|
||||
className="bg-background border-border text-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-foreground block mb-2">
|
||||
{language === "zh" ? "联系电话" : "Phone Number"}
|
||||
</label>
|
||||
<Input
|
||||
value={channelEditForm.phone}
|
||||
onChange={(e) => setChannelEditForm({ ...channelEditForm, phone: e.target.value })}
|
||||
placeholder={language === "zh" ? "例: +86-10-XXXXXX" : "e.g., +1-555-0000"}
|
||||
className="bg-background border-border text-foreground"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Channel Admins Management */}
|
||||
<div className="border-t border-border pt-6">
|
||||
<h4 className="text-base font-semibold text-foreground mb-4">
|
||||
{language === "zh" ? "管理员管理" : "Administrator Management"}
|
||||
</h4>
|
||||
<div className="space-y-3">
|
||||
{channelAdmins && channelAdmins.length > 0 ? (
|
||||
channelAdmins.map((admin: any, index: number) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-muted/50 rounded-lg">
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-foreground">{admin.name}</p>
|
||||
<p className="text-sm text-muted-foreground">{admin.email}</p>
|
||||
<Badge variant="outline" className="mt-2">
|
||||
{language === "zh"
|
||||
? admin.role === "billing"
|
||||
? "计费管理员"
|
||||
: admin.role === "operations"
|
||||
? "运营管理员"
|
||||
: "管理员"
|
||||
: admin.role === "billing"
|
||||
? "Billing Admin"
|
||||
: admin.role === "operations"
|
||||
? "Operations Admin"
|
||||
: "Administrator"}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemoveAdmin(admin.id)}
|
||||
className="text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{language === "zh" ? "暂无管理员" : "No administrators"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsChannelEditDialogOpen(false)}>
|
||||
{language === "zh" ? "取消" : "Cancel"}
|
||||
</Button>
|
||||
<Button className="bg-primary text-primary-foreground" onClick={handleSaveChannelEdit}>
|
||||
{language === "zh" ? "保存更改" : "Save Changes"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* View Tenants Dialog */}
|
||||
<Dialog open={isViewTenantsDialogOpen} onOpenChange={setIsViewTenantsDialogOpen}>
|
||||
<DialogContent className="bg-card border-border max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{language === "zh" ? "渠道租户管理" : "Channel Tenants"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{language === "zh"
|
||||
? `查看和管理渠道 "${selectedChannel?.name}" 下的租户信息`
|
||||
: `View and manage tenants under channel "${selectedChannel?.name}"`}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{channelTenants && channelTenants.length > 0 ? (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{channelTenants.map((tenant: any) => (
|
||||
<div key={tenant.id} className="border border-border rounded-lg p-4 hover:bg-muted/50 transition-colors">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-semibold text-foreground">{tenant.name}</h4>
|
||||
<p className="text-sm text-muted-foreground">{tenant.email}</p>
|
||||
<p className="text-sm text-muted-foreground">{tenant.phone}</p>
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`${
|
||||
tenant.status === "active"
|
||||
? "bg-green-500/10 text-green-600 border-green-500/20"
|
||||
: "bg-red-500/10 text-red-600 border-red-500/20"
|
||||
}`}
|
||||
>
|
||||
{language === "zh"
|
||||
? tenant.status === "active"
|
||||
? "活跃"
|
||||
: "禁用"
|
||||
: tenant.status === "active"
|
||||
? "Active"
|
||||
: "Disabled"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground mb-3">
|
||||
{language === "zh" ? "创建时间" : "Created"}: {tenant.createdAt || "2024-01-15"}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleTenantChangePassword(tenant.id)}
|
||||
>
|
||||
{language === "zh" ? "修改密码" : "Change Password"}
|
||||
</Button>
|
||||
{tenant.status === "active" && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDisableTenant(tenant.id)}
|
||||
className="text-yellow-600 border-yellow-600/20 hover:bg-yellow-600/10"
|
||||
>
|
||||
{language === "zh" ? "禁用" : "Disable"}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDeleteTenant(tenant.id)}
|
||||
className="text-destructive border-destructive/20 hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 mr-1" />
|
||||
{language === "zh" ? "删除" : "Delete"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-muted-foreground">
|
||||
{language === "zh" ? "该渠道暂无租户" : "No tenants under this channel"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsViewTenantsDialogOpen(false)}>
|
||||
{language === "zh" ? "关闭" : "Close"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<div className="mt-8">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{language === "zh" ? "渠道申请审批" : "Channel Application Approvals"}
|
||||
@@ -1816,21 +2476,21 @@ export default function AdminDashboard() {
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-muted-foreground">CPU {language === "zh" ? "使用率" : "Usage"}</span>
|
||||
<span className="text-foreground">{agent.usage.cpu}%</span>
|
||||
<span className="text-foreground">{agent.usage?.cpu ?? 0}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-muted rounded-full h-1.5">
|
||||
<div className="bg-blue-500 h-1.5 rounded-full" style={{ width: `${agent.usage.cpu}%` }} />
|
||||
<div className="bg-blue-500 h-1.5 rounded-full" style={{ width: `${agent.usage?.cpu ?? 0}%` }} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-muted-foreground">
|
||||
{language === "zh" ? "内存使用率" : "Memory Usage"}
|
||||
</span>
|
||||
<span className="text-foreground">{agent.usage.memory}%</span>
|
||||
<span className="text-foreground">{agent.usage?.memory ?? 0}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-muted rounded-full h-1.5">
|
||||
<div
|
||||
className="bg-green-500 h-1.5 rounded-full"
|
||||
style={{ width: `${agent.usage.memory}%` }}
|
||||
style={{ width: `${agent.usage?.memory ?? 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1854,8 +2514,9 @@ export default function AdminDashboard() {
|
||||
if (confirm(language === "zh" ? `确定要删除 ${agent.name} 吗?` : `Are you sure you want to delete ${agent.name}?`)) {
|
||||
try {
|
||||
setResourcesLoading(true)
|
||||
await TaijiAPIClient.deleteTool(agent.id || agent.name)
|
||||
await TaijiAPIClient.deleteAgentResource(agent.id)
|
||||
await loadDashboardData()
|
||||
alert(language === "zh" ? "Agent已删除" : "Agent deleted successfully")
|
||||
} catch (error) {
|
||||
console.error("Failed to delete agent:", error)
|
||||
alert(language === "zh" ? "删除失败" : "Failed to delete")
|
||||
@@ -1942,8 +2603,8 @@ export default function AdminDashboard() {
|
||||
<div className="p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg">
|
||||
<p className="text-sm text-foreground">
|
||||
{language === "zh" ? "当前使用率: " : "Current Usage: "}
|
||||
CPU {selectedAgent.usage.cpu}% · {language === "zh" ? "内存" : "Memory"}{" "}
|
||||
{selectedAgent.usage.memory}%
|
||||
CPU {selectedAgent?.usage?.cpu ?? 0}% · {language === "zh" ? "内存" : "Memory"}{" "}
|
||||
{selectedAgent?.usage?.memory ?? 0}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2017,15 +2678,15 @@ export default function AdminDashboard() {
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">{language === "zh" ? "支持模型" : "Models"}</span>
|
||||
<span className="text-foreground font-medium">{provider.models.length}</span>
|
||||
<span className="text-foreground font-medium">{(provider.supportedModels || provider.models || []).length}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">RPM:</span>
|
||||
<span className="text-foreground font-medium">{provider.rpm.toLocaleString()}</span>
|
||||
<span className="text-foreground font-medium">{(provider.rpm || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">TPM:</span>
|
||||
<span className="text-foreground font-medium">{provider.tpm.toLocaleString()}</span>
|
||||
<span className="text-foreground font-medium">{(provider.tpm || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
|
||||
@@ -11,10 +11,13 @@ import { Card } from "@/components/ui/card"
|
||||
import { Eye, EyeOff, Shield, Globe } from "lucide-react"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { useLanguage } from "@/contexts/language-context"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
|
||||
export default function AdminLoginPage() {
|
||||
const router = useRouter()
|
||||
const { language, setLanguage, t } = useLanguage()
|
||||
const { toast } = useToast()
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [credentials, setCredentials] = useState({
|
||||
@@ -55,13 +58,51 @@ export default function AdminLoginPage() {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
|
||||
// Simulate admin authentication
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||||
try {
|
||||
// 清除所有旧的token(防止多用户登录时token重复)
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("channel_token")
|
||||
localStorage.removeItem("admin_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
|
||||
// Store admin auth token
|
||||
localStorage.setItem("admin_token", "admin_authenticated")
|
||||
|
||||
router.push("/admin/dashboard")
|
||||
// 调用真实的登录API
|
||||
const result = await TaijiAPIClient.login(credentials.email, credentials.password, "admin")
|
||||
|
||||
if (result && result.success && result.data?.token) {
|
||||
// 存储admin token
|
||||
localStorage.setItem("admin_token", result.data.token)
|
||||
if (result.data.refreshToken) {
|
||||
localStorage.setItem("refresh_token", result.data.refreshToken)
|
||||
}
|
||||
if (result.data.user) {
|
||||
localStorage.setItem("user", JSON.stringify(result.data.user))
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t("登录成功", "Login successful"),
|
||||
description: t("欢迎回来", "Welcome back"),
|
||||
})
|
||||
|
||||
router.push("/admin/dashboard")
|
||||
} else {
|
||||
toast({
|
||||
title: t("登录失败", "Login failed"),
|
||||
description: (result as any)?.message || t("请检查您的邮箱和密码", "Please check your email and password"),
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Admin login error:", error)
|
||||
toast({
|
||||
title: t("登录失败", "Login failed"),
|
||||
description: error.message || t("网络错误,请稍后重试", "Network error, please try again"),
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { DashboardLayout } from "@/components/dashboard-layout"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -9,6 +10,7 @@ import { Bot, Cpu, MemoryStick, Zap } from "lucide-react"
|
||||
import { useLanguage } from "@/hooks/useLanguage"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -24,6 +26,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
export default function AgentFactoryPage() {
|
||||
const { t } = useLanguage()
|
||||
const { toast } = useToast()
|
||||
const router = useRouter()
|
||||
const [selectedAgent, setSelectedAgent] = useState<any>(null)
|
||||
const [showDeployDialog, setShowDeployDialog] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -37,8 +40,14 @@ export default function AgentFactoryPage() {
|
||||
const [stats, setStats] = useState({ cpu: 0, memory: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
// 检查认证
|
||||
if (!isAuthenticated()) {
|
||||
router.push("/login")
|
||||
return
|
||||
}
|
||||
|
||||
loadAgents()
|
||||
}, [])
|
||||
}, [router])
|
||||
|
||||
const loadAgents = async () => {
|
||||
try {
|
||||
|
||||
+10
-1
@@ -8,8 +8,10 @@ import { Progress } from "@/components/ui/progress"
|
||||
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, LineChart, Line } from "recharts"
|
||||
import { useLanguage } from "@/hooks/useLanguage"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
@@ -17,6 +19,7 @@ import { Label } from "@/components/ui/label"
|
||||
export default function BillingPage() {
|
||||
const { t } = useLanguage()
|
||||
const { toast } = useToast()
|
||||
const router = useRouter()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showFilterDialog, setShowFilterDialog] = useState(false)
|
||||
const [showDateDialog, setShowDateDialog] = useState(false)
|
||||
@@ -34,8 +37,14 @@ export default function BillingPage() {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// 检查认证
|
||||
if (!isAuthenticated()) {
|
||||
router.push("/login")
|
||||
return
|
||||
}
|
||||
|
||||
loadBillingData()
|
||||
}, [])
|
||||
}, [router])
|
||||
|
||||
const loadBillingData = async () => {
|
||||
try {
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
} from "@/components/ui/dialog"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default function ChannelDashboard() {
|
||||
@@ -253,9 +254,23 @@ export default function ChannelDashboard() {
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("channel_token")
|
||||
router.push("/channel/login")
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
// 调用后端logout API
|
||||
await TaijiAPIClient.logout()
|
||||
} catch (error) {
|
||||
console.error("Logout API call failed:", error)
|
||||
} finally {
|
||||
// 清除所有本地存储的token和用户信息
|
||||
localStorage.removeItem("channel_token")
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("admin_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
|
||||
router.push("/channel/login")
|
||||
}
|
||||
}
|
||||
|
||||
const stats = [
|
||||
|
||||
@@ -10,10 +10,13 @@ import { Card } from "@/components/ui/card"
|
||||
import { Building2, Globe, Eye, EyeOff } from "lucide-react"
|
||||
import { useLanguage } from "@/contexts/language-context"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
|
||||
export default function ChannelLogin() {
|
||||
const router = useRouter()
|
||||
const { language, setLanguage } = useLanguage()
|
||||
const { language, setLanguage, t } = useLanguage()
|
||||
const { toast } = useToast()
|
||||
const [email, setEmail] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
@@ -52,11 +55,51 @@ export default function ChannelLogin() {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
localStorage.setItem("channel_token", "mock_channel_token")
|
||||
router.push("/channel/dashboard")
|
||||
}, 1500)
|
||||
try {
|
||||
// 清除所有旧的token(防止多用户登录时token重复)
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("channel_token")
|
||||
localStorage.removeItem("admin_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
|
||||
// 调用真实的登录API
|
||||
const result = await TaijiAPIClient.login(email, password, "channel")
|
||||
|
||||
if (result && result.success && result.data?.token) {
|
||||
// 存储channel token
|
||||
localStorage.setItem("channel_token", result.data.token)
|
||||
if (result.data.refreshToken) {
|
||||
localStorage.setItem("refresh_token", result.data.refreshToken)
|
||||
}
|
||||
if (result.data.user) {
|
||||
localStorage.setItem("user", JSON.stringify(result.data.user))
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t("登录成功", "Login successful"),
|
||||
description: t("欢迎回来", "Welcome back"),
|
||||
})
|
||||
|
||||
router.push("/channel/dashboard")
|
||||
} else {
|
||||
toast({
|
||||
title: t("登录失败", "Login failed"),
|
||||
description: (result as any)?.message || t("请检查您的邮箱和密码", "Please check your email and password"),
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Channel login error:", error)
|
||||
toast({
|
||||
title: t("登录失败", "Login failed"),
|
||||
description: error.message || t("网络错误,请稍后重试", "Network error, please try again"),
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+10
-1
@@ -1,8 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useLanguage } from "@/hooks/useLanguage"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import { DashboardLayout } from "@/components/dashboard-layout"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -24,6 +26,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
|
||||
export default function DataToolsPage() {
|
||||
const { t } = useLanguage()
|
||||
const router = useRouter()
|
||||
const [tools, setTools] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [stats, setStats] = useState({ totalApis: 0, generatedTools: 0, activePods: 0 })
|
||||
@@ -42,9 +45,15 @@ export default function DataToolsPage() {
|
||||
const [selectedServiceGateway, setSelectedServiceGateway] = useState<string>("")
|
||||
|
||||
useEffect(() => {
|
||||
// 检查认证
|
||||
if (!isAuthenticated()) {
|
||||
router.push("/login")
|
||||
return
|
||||
}
|
||||
|
||||
loadTools()
|
||||
loadStats()
|
||||
}, [])
|
||||
}, [router])
|
||||
|
||||
const loadTools = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { DashboardLayout } from "@/components/dashboard-layout"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
@@ -8,6 +9,7 @@ import { Progress } from "@/components/ui/progress"
|
||||
import { useLanguage } from "@/hooks/useLanguage"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { useState, useEffect } from "react"
|
||||
import {
|
||||
@@ -27,6 +29,7 @@ import { Textarea } from "@/components/ui/textarea"
|
||||
export default function ModelGatewayPage() {
|
||||
const { t } = useLanguage()
|
||||
const { toast } = useToast()
|
||||
const router = useRouter()
|
||||
const [selectedGateway, setSelectedGateway] = useState<string>("")
|
||||
const [showGatewayDialog, setShowGatewayDialog] = useState(false)
|
||||
const [showApiDialog, setShowApiDialog] = useState(false)
|
||||
@@ -40,8 +43,14 @@ export default function ModelGatewayPage() {
|
||||
const [stats, setStats] = useState({ availableGateways: 0, endpoints: 0, avgLatency: 0, requestsToday: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
// 检查认证
|
||||
if (!isAuthenticated()) {
|
||||
router.push("/login")
|
||||
return
|
||||
}
|
||||
|
||||
loadGatewayData()
|
||||
}, [])
|
||||
}, [router])
|
||||
|
||||
const loadGatewayData = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { DashboardLayout } from "@/components/dashboard-layout"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
@@ -9,6 +10,7 @@ import { Network, Plus, Play, Save, Trash2, ArrowRight } from "lucide-react"
|
||||
import { useLanguage } from "@/hooks/useLanguage"
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -23,6 +25,7 @@ import { Input } from "@/components/ui/input"
|
||||
export default function OrchestrationPage() {
|
||||
const { t } = useLanguage()
|
||||
const { toast } = useToast()
|
||||
const router = useRouter()
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
const [workflowNodes, setWorkflowNodes] = useState<string[]>([])
|
||||
const [workflowName, setWorkflowName] = useState("")
|
||||
@@ -32,8 +35,14 @@ export default function OrchestrationPage() {
|
||||
const [workflows, setWorkflows] = useState<any[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
// 检查认证
|
||||
if (!isAuthenticated()) {
|
||||
router.push("/login")
|
||||
return
|
||||
}
|
||||
|
||||
loadData()
|
||||
}, [])
|
||||
}, [router])
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
# API 客户端对接完成总结
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. API 客户端完整实现 (`lib/api-client.ts`)
|
||||
|
||||
已根据后端 API 接口文档完整实现了所有接口方法,包括:
|
||||
|
||||
#### 认证模块 API (6个方法)
|
||||
- ✅ `login()` - 用户登录
|
||||
- ✅ `logout()` - 用户登出
|
||||
- ✅ `refreshToken()` - 刷新Token
|
||||
- ✅ `changePassword()` - 修改密码
|
||||
- ✅ `getApiKeyInfo()` - 获取API密钥信息
|
||||
- ✅ `regenerateApiKey()` - 重新生成API密钥
|
||||
|
||||
#### 用户侧平台 API (14个方法)
|
||||
- ✅ `getUserDashboardStats()` - 获取仪表板统计
|
||||
- ✅ `getUserAgentActivity()` - 获取Agent活动数据
|
||||
- ✅ `selectGateway()` - 选择网关类型
|
||||
- ✅ `createGatewayAPI()` - 创建网关API
|
||||
- ✅ `getGatewayAPIs()` - 获取网关API列表
|
||||
- ✅ `getGatewayMonitoring()` - 获取网关监控数据
|
||||
- ✅ `generateTool()` - 生成工具
|
||||
- ✅ `createDataTemplate()` - 创建数据模板
|
||||
- ✅ `getPlatformAgents()` - 获取平台Agent列表
|
||||
- ✅ `deployAgent()` - 部署Agent
|
||||
- ✅ `createWorkflow()` - 创建工作流
|
||||
- ✅ `getBillingBalance()` - 获取余额信息
|
||||
- ✅ `rechargeBalance()` - 充值余额
|
||||
- ✅ `getBillingHistory()` - 获取计费历史
|
||||
|
||||
#### 渠道合作伙伴 API (8个方法)
|
||||
- ✅ `getChannelTenants()` - 获取租户列表
|
||||
- ✅ `createChannelTenant()` - 创建租户
|
||||
- ✅ `allocateTenantResources()` - 分配租户资源
|
||||
- ✅ `updateTenantBilling()` - 更新租户计费设置
|
||||
- ✅ `rechargeTenant()` - 为租户充值
|
||||
- ✅ `setTenantCreditLimit()` - 设置租户授信额度
|
||||
- ✅ `applyForResources()` - 申请资源
|
||||
- ✅ `getChannelBillingStats()` - 获取渠道计费统计
|
||||
|
||||
#### 超级管理员 API (10个方法)
|
||||
- ✅ `getAdminDashboardStats()` - 获取平台统计
|
||||
- ✅ `getAdminChannels()` - 获取渠道列表
|
||||
- ✅ `createAdminChannel()` - 创建渠道
|
||||
- ✅ `manageChannelResources()` - 统一管理渠道资源
|
||||
- ✅ `getAdminApplications()` - 获取所有申请
|
||||
- ✅ `reviewApplication()` - 审批申请
|
||||
- ✅ `getAdminModelProviders()` - 获取所有模型供应商
|
||||
- ✅ `getAdminAgentResources()` - 获取所有Agent资源
|
||||
- ✅ `getAdminAgentMonitoring()` - 监控Agent健康状态
|
||||
- ✅ `getAdminBillingOverview()` - 获取三维度计费统计
|
||||
|
||||
#### 供应商管理 API (6个方法)
|
||||
- ✅ `getModelProviders()` - 获取模型供应商列表
|
||||
- ✅ `createModelProvider()` - 创建模型供应商
|
||||
- ✅ `getProviderDetails()` - 获取供应商详情
|
||||
- ✅ `updateProvider()` - 更新供应商配置
|
||||
- ✅ `deleteProvider()` - 删除供应商
|
||||
- ✅ `testProviderConnection()` - 测试供应商连接
|
||||
|
||||
#### Data Ingestion 服务 API (12个方法)
|
||||
- ✅ `getHealth()` - 健康检查
|
||||
- ✅ `syncRapidAPI()` - 同步RapidAPI
|
||||
- ✅ `testRapidAPI()` - 测试RapidAPI端点
|
||||
- ✅ `parseOpenAPI()` - 解析OpenAPI规范
|
||||
- ✅ `processAPILLAMA()` - APILLAMA处理API文档
|
||||
- ✅ `generateTool()` - 生成工具定义
|
||||
- ✅ `getTools()` - 获取工具列表
|
||||
- ✅ `getTool()` - 获取特定工具定义
|
||||
- ✅ `deleteTool()` - 删除工具
|
||||
- ✅ `getStats()` - 获取统计信息
|
||||
- ✅ `clearCache()` - 清除缓存
|
||||
- ✅ `getMetrics()` - 获取Prometheus Metrics
|
||||
|
||||
#### MCP Server 服务 API (11个方法)
|
||||
- ✅ `getMCPHealth()` - MCP Server健康检查
|
||||
- ✅ `registerAgent()` - 注册Agent
|
||||
- ✅ `getAgents()` - 获取Agent列表
|
||||
- ✅ `getAgent()` - 获取特定Agent
|
||||
- ✅ `executeAgentTool()` - 执行Agent工具
|
||||
- ✅ `getMCPTools()` - 获取工具列表
|
||||
- ✅ `getMCPMetrics()` - 获取MCP Prometheus Metrics
|
||||
- ✅ `getMonitoringMetrics()` - 获取系统性能指标
|
||||
- ✅ `getMonitoringStats()` - 获取服务统计信息
|
||||
- ✅ `getMonitoringTrends()` - 获取性能趋势数据
|
||||
- ✅ `getMonitoringAlerts()` - 获取系统告警
|
||||
- ✅ `getMonitoringDashboard()` - 获取监控仪表盘聚合
|
||||
|
||||
#### WebSocket API (1个方法)
|
||||
- ✅ `createAgentWebSocket()` - 创建Agent WebSocket连接
|
||||
|
||||
### 2. 核心功能实现
|
||||
|
||||
#### 认证 Token 管理
|
||||
- ✅ 自动保存 Token 到 localStorage
|
||||
- ✅ 自动在请求头中添加 Authorization
|
||||
- ✅ 支持 JWT Token 和 API Key 双认证
|
||||
- ✅ 自动处理 Token 刷新
|
||||
|
||||
#### 错误处理
|
||||
- ✅ 统一的错误处理机制
|
||||
- ✅ 类型安全的响应处理
|
||||
- ✅ 友好的错误提示
|
||||
|
||||
#### 类型定义
|
||||
- ✅ 导出 `APIResponse` 类型供外部使用
|
||||
- ✅ 完整的 TypeScript 类型支持
|
||||
|
||||
### 3. 登录页面集成
|
||||
|
||||
- ✅ 更新登录页面使用新的 API 客户端
|
||||
- ✅ 添加错误处理和用户提示
|
||||
- ✅ 集成 toast 通知
|
||||
|
||||
### 4. 文档
|
||||
|
||||
- ✅ 创建 API 集成说明文档 (`docs/API_INTEGRATION.md`)
|
||||
- ✅ 包含所有 API 方法的使用示例
|
||||
- ✅ 包含错误处理、认证管理等说明
|
||||
|
||||
## 📋 使用示例
|
||||
|
||||
### 基本使用
|
||||
|
||||
```typescript
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
|
||||
// 登录
|
||||
const result = await TaijiAPIClient.login("user@example.com", "password", "user")
|
||||
|
||||
// 获取仪表板统计
|
||||
const stats = await TaijiAPIClient.getUserDashboardStats()
|
||||
|
||||
// 部署Agent
|
||||
await TaijiAPIClient.deployAgent({
|
||||
agentId: "agent-uuid",
|
||||
instances: 3,
|
||||
model: "gpt-4o-mini",
|
||||
gateway: "MCP"
|
||||
})
|
||||
```
|
||||
|
||||
## 🔧 配置
|
||||
|
||||
在 `.env.local` 文件中配置 API 端点:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_DATA_INGESTION_URL=http://localhost:8001
|
||||
NEXT_PUBLIC_MCP_SERVER_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_API_GATEWAY_URL=http://localhost:80
|
||||
```
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **环境变量**: 确保设置了正确的 API 端点 URL
|
||||
2. **CORS**: 开发环境需要后端配置 CORS
|
||||
3. **错误处理**: 所有 API 调用都应该有错误处理
|
||||
4. **Token 管理**: Token 自动管理,无需手动处理
|
||||
5. **类型安全**: 使用 TypeScript 类型检查
|
||||
|
||||
## 🚀 下一步
|
||||
|
||||
1. 在各个页面组件中集成 API 调用
|
||||
2. 替换现有的模拟数据为真实 API 调用
|
||||
3. 添加加载状态和错误处理
|
||||
4. 实现数据缓存和优化
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- API 集成说明: `docs/API_INTEGRATION.md`
|
||||
- 后端 API 文档: `/tools/taiji-AI-PAD/Docs/前后端调试说明/API接口文档.md`
|
||||
- 后端需求文档: `docs/backend-api-requirements.md`
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
# API 集成说明
|
||||
|
||||
本文档说明如何在前端项目中使用后端 API 接口。
|
||||
|
||||
## 环境配置
|
||||
|
||||
在项目根目录创建 `.env.local` 文件(或使用 `.env.example` 作为模板):
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_DATA_INGESTION_URL=http://localhost:8001
|
||||
NEXT_PUBLIC_MCP_SERVER_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_API_GATEWAY_URL=http://localhost:80
|
||||
```
|
||||
|
||||
## API 客户端使用
|
||||
|
||||
### 导入 API 客户端
|
||||
|
||||
```typescript
|
||||
import { TaijiAPIClient } from "@/lib/api-client"
|
||||
```
|
||||
|
||||
### 认证相关
|
||||
|
||||
#### 登录
|
||||
```typescript
|
||||
try {
|
||||
const result = await TaijiAPIClient.login("user@example.com", "password", "user")
|
||||
if (result.success) {
|
||||
// Token 已自动保存到 localStorage
|
||||
console.log("登录成功", result.data.user)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("登录失败", error)
|
||||
}
|
||||
```
|
||||
|
||||
#### 登出
|
||||
```typescript
|
||||
await TaijiAPIClient.logout()
|
||||
// Token 已自动清除
|
||||
```
|
||||
|
||||
#### 刷新 Token
|
||||
```typescript
|
||||
await TaijiAPIClient.refreshToken()
|
||||
```
|
||||
|
||||
### 用户侧平台 API
|
||||
|
||||
#### 获取仪表板统计
|
||||
```typescript
|
||||
const stats = await TaijiAPIClient.getUserDashboardStats()
|
||||
console.log(stats.data.activeAgents)
|
||||
```
|
||||
|
||||
#### 部署 Agent
|
||||
```typescript
|
||||
const result = await TaijiAPIClient.deployAgent({
|
||||
agentId: "agent-uuid",
|
||||
instances: 3,
|
||||
model: "gpt-4o-mini",
|
||||
gateway: "MCP"
|
||||
})
|
||||
```
|
||||
|
||||
#### 创建工作流
|
||||
```typescript
|
||||
const workflow = await TaijiAPIClient.createWorkflow({
|
||||
name: "订单处理流程",
|
||||
gateway: "MCP",
|
||||
nodes: [
|
||||
{
|
||||
agentId: "agent-1",
|
||||
agentType: "platform",
|
||||
agentName: "订单验证Agent",
|
||||
order: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
#### 获取计费历史
|
||||
```typescript
|
||||
const history = await TaijiAPIClient.getBillingHistory({
|
||||
startTime: "2025-01-01T00:00:00Z",
|
||||
endTime: "2025-01-31T23:59:59Z",
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
})
|
||||
```
|
||||
|
||||
### 渠道合作伙伴 API
|
||||
|
||||
#### 获取租户列表
|
||||
```typescript
|
||||
const tenants = await TaijiAPIClient.getChannelTenants()
|
||||
```
|
||||
|
||||
#### 创建租户
|
||||
```typescript
|
||||
const tenant = await TaijiAPIClient.createChannelTenant({
|
||||
name: "企业客户A",
|
||||
email: "contact@company-a.com",
|
||||
password: "securepass123",
|
||||
subscriptionTier: "enterprise"
|
||||
})
|
||||
```
|
||||
|
||||
#### 分配租户资源
|
||||
```typescript
|
||||
await TaijiAPIClient.allocateTenantResources("tenant-id", {
|
||||
agents: [
|
||||
{ agentId: "agent-1", quantity: 10 }
|
||||
],
|
||||
models: [
|
||||
{ modelName: "gpt-4", rpm: 10000, tpm: 500000 }
|
||||
],
|
||||
customAgentResources: {
|
||||
cpu: 2.0,
|
||||
memory: 4.0
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 超级管理员 API
|
||||
|
||||
#### 获取平台统计
|
||||
```typescript
|
||||
const stats = await TaijiAPIClient.getAdminDashboardStats()
|
||||
```
|
||||
|
||||
#### 创建渠道
|
||||
```typescript
|
||||
const channel = await TaijiAPIClient.createAdminChannel({
|
||||
name: "合作渠道A",
|
||||
email: "partner@channel-a.com",
|
||||
password: "channelpass123",
|
||||
commissionRate: 10.0
|
||||
})
|
||||
```
|
||||
|
||||
#### 审批申请
|
||||
```typescript
|
||||
await TaijiAPIClient.reviewApplication("application-id", true, "审批通过")
|
||||
```
|
||||
|
||||
### 供应商管理 API
|
||||
|
||||
#### 获取模型供应商列表
|
||||
```typescript
|
||||
const providers = await TaijiAPIClient.getModelProviders()
|
||||
```
|
||||
|
||||
#### 创建模型供应商
|
||||
```typescript
|
||||
const provider = await TaijiAPIClient.createModelProvider({
|
||||
name: "OpenAI",
|
||||
provider: "openai",
|
||||
apiUrl: "https://api.openai.com/v1",
|
||||
apiKey: "sk-xxxxx",
|
||||
supportedModels: ["gpt-4", "gpt-4o-mini"],
|
||||
rpm: 3500,
|
||||
tpm: 90000
|
||||
})
|
||||
```
|
||||
|
||||
### Data Ingestion 服务 API
|
||||
|
||||
#### 获取健康状态
|
||||
```typescript
|
||||
const health = await TaijiAPIClient.getHealth()
|
||||
```
|
||||
|
||||
#### 同步 RapidAPI
|
||||
```typescript
|
||||
await TaijiAPIClient.syncRapidAPI("weather", 100)
|
||||
```
|
||||
|
||||
#### 处理 APILLAMA
|
||||
```typescript
|
||||
const result = await TaijiAPIClient.processAPILLAMA(
|
||||
{ title: "Weather API", description: "Get weather" },
|
||||
{ service: "Weather service" },
|
||||
"json_schema"
|
||||
)
|
||||
```
|
||||
|
||||
#### 获取工具列表
|
||||
```typescript
|
||||
const tools = await TaijiAPIClient.getTools("weather", 100, 0)
|
||||
```
|
||||
|
||||
### MCP Server API
|
||||
|
||||
#### 注册 Agent
|
||||
```typescript
|
||||
const agent = await TaijiAPIClient.registerAgent({
|
||||
name: "weather-agent",
|
||||
description: "Weather information agent",
|
||||
capabilities: ["weather_query"]
|
||||
})
|
||||
```
|
||||
|
||||
#### 获取 Agent 列表
|
||||
```typescript
|
||||
const agents = await TaijiAPIClient.getAgents(0, 100)
|
||||
```
|
||||
|
||||
#### 执行 Agent 工具
|
||||
```typescript
|
||||
const result = await TaijiAPIClient.executeAgentTool("agent-id", {
|
||||
method: "tools/call",
|
||||
params: {
|
||||
tool: { name: "math_add", function_name: "math_add" },
|
||||
arguments: { a: 10, b: 20 }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### WebSocket 连接
|
||||
|
||||
```typescript
|
||||
const ws = TaijiAPIClient.createAgentWebSocket("agent-id")
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected")
|
||||
ws.send(JSON.stringify({
|
||||
type: "mcp_request",
|
||||
payload: {
|
||||
method: "tools/list",
|
||||
params: {}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data)
|
||||
console.log("Received:", data)
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有 API 方法都会抛出错误,建议使用 try-catch 处理:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const result = await TaijiAPIClient.getUserDashboardStats()
|
||||
// 处理成功结果
|
||||
} catch (error: any) {
|
||||
console.error("API Error:", error.message)
|
||||
// 显示错误提示给用户
|
||||
}
|
||||
```
|
||||
|
||||
## 认证 Token 管理
|
||||
|
||||
API 客户端会自动管理认证 Token:
|
||||
|
||||
- **登录时**: Token 自动保存到 `localStorage`
|
||||
- **请求时**: Token 自动添加到请求头 `Authorization: Bearer <token>`
|
||||
- **登出时**: Token 自动清除
|
||||
|
||||
如果需要手动获取 Token:
|
||||
|
||||
```typescript
|
||||
const token = localStorage.getItem("auth_token")
|
||||
```
|
||||
|
||||
## API Key 认证
|
||||
|
||||
除了 JWT Token,还支持 API Key 认证:
|
||||
|
||||
```typescript
|
||||
// 设置 API Key
|
||||
localStorage.setItem("api_key", "sk-xxxxx")
|
||||
|
||||
// API 客户端会自动使用 API Key
|
||||
const result = await TaijiAPIClient.getUserDashboardStats()
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **环境变量**: 确保设置了正确的 API 端点 URL
|
||||
2. **CORS**: 开发环境需要后端配置 CORS 允许前端域名
|
||||
3. **错误处理**: 所有 API 调用都应该有错误处理
|
||||
4. **Token 过期**: 如果 Token 过期,需要重新登录或刷新 Token
|
||||
5. **类型安全**: API 客户端使用 TypeScript,建议启用类型检查
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 后端 API 接口文档: `/tools/taiji-AI-PAD/Docs/前后端调试说明/API接口文档.md`
|
||||
- 后端需求文档: `/tools/taiji-pad-v0/docs/backend-api-requirements.md`
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# 前端菜单与API接口对照表
|
||||
|
||||
> 本文档自动生成,基于 `API接口文档.md` 与前端实际代码实现。
|
||||
> 更新时间:2025-12-23
|
||||
|
||||
---
|
||||
|
||||
## 概览(/)
|
||||
- 页面文件:app/page.tsx(渲染组件:components/dashboard-overview.tsx)
|
||||
- 已接入接口:
|
||||
- MCP Server 健康检查:`GET /health`(MCP Server)
|
||||
- Data Ingestion 健康检查:`GET /health`(Data Ingestion)
|
||||
- Agent 列表:`GET /agents`(MCP Server,参数:status、limit、offset)
|
||||
- 缺失部分:
|
||||
- 全局 API 调用、EU 消耗、模型提供商数量/健康等为静态,缺少统计接口。
|
||||
- 建议后端能力:
|
||||
- `GET /stats/global?range=24h|7d`、`GET /billing/eu/usage?range=24h|30d`、`GET /gateway/providers`、`GET /gateway/metrics`
|
||||
|
||||
## 数据与工具(/data-tools)
|
||||
- 页面文件:app/data-tools/page.tsx
|
||||
- 已接入接口:
|
||||
- 工具列表:`GET /tools`(Data Ingestion,参数:category、limit、offset)
|
||||
- 删除工具:`DELETE /tools/{tool_name}`(Data Ingestion)
|
||||
- 统计信息:`GET /stats`(Data Ingestion)
|
||||
- RapidAPI 同步:`POST /rapidapi/sync`(Data Ingestion,参数:category、limit)
|
||||
- APILLAMA 处理:`POST /apillama/process`(Data Ingestion,body: api_doc, context, output_format)
|
||||
- 生成工具定义:`POST /tools/generate`(Data Ingestion,body: url, method, name, description)
|
||||
- 缺失部分:
|
||||
- `POST /rapidapi/test`、`POST /openapi/parse`、`POST /cache/clear`、`GET /metrics`、`GET /tools/{tool_name}` 未接入。
|
||||
- 建议后端能力:
|
||||
- 所有接口统一 envelope 返回,删除工具返回元信息。
|
||||
|
||||
## 代理工厂(/agent-factory)
|
||||
- 页面文件:app/agent-factory/page.tsx
|
||||
- 已接入接口:
|
||||
- Agent 列表:`GET /agents`(MCP Server,参数:status、limit、offset)
|
||||
- 注册 Agent:`POST /agents`(MCP Server,body: name, description, capabilities, metadata)
|
||||
- 执行 Agent 工具:`POST /agents/{agent_id}/execute`(MCP Server,body: tool_name, parameters, context)
|
||||
- MCP 工具列表:`GET /tools`(MCP Server,参数:category、limit)
|
||||
- 缺失部分:
|
||||
- 暂停/删除 agent、Agent 详情页、WebSocket 交互未实现。
|
||||
- 建议后端能力:
|
||||
- `DELETE /agents/{agent_id}`、`POST /agents/{agent_id}/status`、`GET /agents/{agent_id}/executions`
|
||||
|
||||
## 模型网关(/model-gateway)
|
||||
- 页面文件:app/model-gateway/page.tsx
|
||||
- 已接入接口:无(全部为静态数据)
|
||||
- 缺失部分:
|
||||
- 所有数据均为静态。
|
||||
- 建议后端能力:
|
||||
- `GET /gateway/providers`、`GET /gateway/config`、`PUT /gateway/config`、`GET /gateway/metrics`
|
||||
|
||||
## 编排中心(/orchestration)
|
||||
- 页面文件:app/orchestration/page.tsx
|
||||
- 已接入接口:无(全部为静态数据)
|
||||
- 缺失部分:
|
||||
- 工作流列表、运行状态、NATS 吞吐、VM 池等均为静态。
|
||||
- 建议后端能力:
|
||||
- `GET /workflows`、`GET /workflows/{id}`、`POST /workflows/{id}/deploy`、`POST /workflows/{id}/stop`、`GET /orchestration/metrics`
|
||||
|
||||
## 计费与资源(/billing)
|
||||
- 页面文件:app/billing/page.tsx
|
||||
- 已接入接口:无(全部为静态数据)
|
||||
- 缺失部分:
|
||||
- EU 余额、历史消耗、成本拆分、资源配额等均为静态。
|
||||
- 建议后端能力:
|
||||
- `GET /billing/eu/balance`、`GET /billing/eu/history`、`GET /billing/cost/breakdown`、`GET /billing/invoices/{id}/download`
|
||||
|
||||
## 认证/渠道/租户/供应商管理(/admin、/channel、/login)
|
||||
- 页面文件:app/admin/*、app/channel/*、app/login/page.tsx
|
||||
- 已接入接口:无(全部为本地 mock 或静态)
|
||||
- 缺失部分:
|
||||
- 认证、渠道、租户、供应商管理等均无后端 API。
|
||||
- 建议后端能力:
|
||||
- `POST /auth/login`、`POST /auth/logout`、`GET/POST/PUT/DELETE /channels`、`GET/POST/PUT /tenants`、`GET/POST/PUT /providers`
|
||||
|
||||
---
|
||||
|
||||
# 前端设计缺陷与建议
|
||||
|
||||
1. **中英文切换问题**:部分页面(如登录、管理后台)存在中英文内容不一致、未全量覆盖、切换后内容未实时刷新等问题,建议:
|
||||
- 统一使用国际化 hooks(如 useLanguage),所有文案均走 t() 方法。
|
||||
- 检查所有页面的语言切换逻辑,确保切换后 UI 实时更新。
|
||||
2. **页面缺少后端功能按钮**:如“查看详情”“暂停/删除 agent”“清缓存”“OpenAPI 解析”等功能无入口或无按钮,建议:
|
||||
- 补充相关按钮和弹窗,未有后端接口时可先占位。
|
||||
- 对于已建议的后端接口,前端可预留 UI 并注释说明。
|
||||
3. **静态数据未动态化**:如概览、计费、模型网关等页面大量数据为静态,建议:
|
||||
- 后端接口补齐后,前端需全部改为动态获取。
|
||||
4. **API 错误处理与 envelope 兼容**:建议所有 API 调用均兼容 envelope 格式,前端统一处理 status/data/message/error。
|
||||
|
||||
---
|
||||
|
||||
> 详细接口与参数请参考 API接口文档.md。
|
||||
-321
@@ -1,321 +0,0 @@
|
||||
# 前端菜单 ↔ API 接口对照(自动整理)
|
||||
|
||||
更新时间:2025-12-23
|
||||
核对时间:2025-12-23(已逐项对照前端源码与 API 文档)
|
||||
|
||||
本文件用于回答:
|
||||
- 前端每个菜单/功能使用了哪些后端接口、传了哪些参数
|
||||
- 哪些菜单/功能目前没有对应后端接口(缺失项)
|
||||
- 缺失项建议补充怎样的后端能力(接口、请求参数、返回结构)
|
||||
|
||||
> 说明
|
||||
> - 仅接入了 `API接口文档.md` 中明确存在的后端接口。
|
||||
> - 文档中未提供的后端能力:前端保持静态占位,并在“缺失部分”中列出。
|
||||
> - Base URL 通过环境变量配置(若未配置则使用文档默认):
|
||||
> - `NEXT_PUBLIC_DATA_INGESTION_URL`(默认 `http://localhost:8001`)
|
||||
> - `NEXT_PUBLIC_MCP_SERVER_URL`(默认 `http://localhost:8002`)
|
||||
> - `NEXT_PUBLIC_API_GATEWAY_URL`(默认 `http://localhost:80`,当前前端未使用)
|
||||
|
||||
---
|
||||
|
||||
## 接口调用稽核(2025-12-23)
|
||||
|
||||
### Data Ingestion 服务
|
||||
|
||||
| 接口 | 前端页面 / 功能 | 代码位置 |
|
||||
| --- | --- | --- |
|
||||
| GET /health | 概览 → 系统组件区块 | [components/dashboard-overview.tsx](components/dashboard-overview.tsx#L55-L97) |
|
||||
| POST /rapidapi/sync | 数据与工具 → RapidAPI 汇聚标签 → 同步端点按钮 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L175-L193) |
|
||||
| POST /rapidapi/test | 数据与工具 → RapidAPI 汇聚标签 → 端点测试器 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L198-L226) |
|
||||
| POST /openapi/parse | 数据与工具 → 运维工具标签 → OpenAPI 解析器 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L223-L266) |
|
||||
| POST /apillama/process | 数据与工具 → APILLAMA 处理器 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L285-L306) |
|
||||
| POST /tools/generate | 数据与工具 → 生成新工具按钮 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L319-L335) |
|
||||
| GET /tools | 数据与工具 → 工具注册表列表加载 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L155-L167) |
|
||||
| GET /tools/{tool_name} | 数据与工具 → 工具详情对话框 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L268-L289) |
|
||||
| DELETE /tools/{tool_name} | 数据与工具 → 工具注册表删除按钮 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L300-L315) |
|
||||
| GET /stats | 数据与工具 → 顶部统计卡片 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L155-L167) |
|
||||
| POST /cache/clear | 数据与工具 → 运维工具 → 缓存维护 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L236-L252) |
|
||||
| GET /metrics | 数据与工具 → 运维工具 → 服务指标 | [app/data-tools/page.tsx](app/data-tools/page.tsx#L253-L268) |
|
||||
|
||||
### MCP Server 服务
|
||||
|
||||
| 接口 | 前端页面 / 功能 | 代码位置 |
|
||||
| --- | --- | --- |
|
||||
| GET /health | 概览 → 系统组件区块 | [components/dashboard-overview.tsx](components/dashboard-overview.tsx#L55-L97) |
|
||||
| GET /agents | 概览 → 活跃代理统计;代理工厂 → Agent Registry 列表 | [components/dashboard-overview.tsx](components/dashboard-overview.tsx#L55-L97)<br>[app/agent-factory/page.tsx](app/agent-factory/page.tsx#L74-L104) |
|
||||
| POST /agents | 代理工厂 → 创建新代理 | [app/agent-factory/page.tsx](app/agent-factory/page.tsx#L105-L121) |
|
||||
| GET /agents/{agent_id} | 代理工厂 → 代理详情对话框 | [app/agent-factory/page.tsx](app/agent-factory/page.tsx#L130-L146) |
|
||||
| POST /agents/{agent_id}/execute | 代理工厂 → 播放按钮执行工具 | [app/agent-factory/page.tsx](app/agent-factory/page.tsx#L116-L128) |
|
||||
| GET /tools | 代理工厂 → 可用函数工具区域 | [app/agent-factory/page.tsx](app/agent-factory/page.tsx#L74-L104) |
|
||||
| GET /metrics | 代理工厂 → MCP 服务指标卡片 | [app/agent-factory/page.tsx](app/agent-factory/page.tsx#L150-L170) |
|
||||
| WebSocket /ws/{agent_id} | 代理工厂 → MCP WebSocket 控制台 | [app/agent-factory/page.tsx](app/agent-factory/page.tsx#L178-L228) |
|
||||
|
||||
---
|
||||
|
||||
## 概览(/)
|
||||
|
||||
页面文件:app/page.tsx(渲染组件:components/dashboard-overview.tsx)
|
||||
|
||||
### 已接入接口
|
||||
|
||||
1) MCP Server 健康检查
|
||||
- `GET /health`
|
||||
- Base:MCP Server
|
||||
- 用途:在“系统组件”区块显示 MCP Server 状态(Operational/Down/Checking)
|
||||
- 参数:无
|
||||
|
||||
2) Data Ingestion 健康检查
|
||||
- `GET /health`
|
||||
- Base:Data Ingestion
|
||||
- 用途:在“系统组件”区块显示 RapidAPI Hub(以 Data Ingestion 服务健康作为近似)状态
|
||||
- 参数:无
|
||||
|
||||
3) Agent 列表
|
||||
- `GET /agents`
|
||||
- Base:MCP Server
|
||||
- 用途:将“活跃代理”数字替换为真实 active agent 数(`status=active` 计数)
|
||||
- 参数:
|
||||
- `status`(可选)
|
||||
- `limit`(默认 100)
|
||||
- `offset`(默认 0)
|
||||
|
||||
### 缺失部分(无后端接口 / 或前端未展示)
|
||||
- “全局 API 调用”“EU 消耗”“模型提供商数量/健康”等仍为前端静态:文档未提供对应统计接口。
|
||||
|
||||
### 建议补充的后端能力
|
||||
- 全局调用统计:
|
||||
- `GET /stats/global?range=24h|7d` → 返回每分钟/每天请求量、成功率、P95
|
||||
- EU 消耗统计:
|
||||
- `GET /billing/eu/usage?range=24h|30d` → 返回时间序列、余额、消耗来源拆分
|
||||
- 模型网关状态:
|
||||
- `GET /gateway/providers`、`GET /gateway/metrics`(详见“模型网关”菜单)
|
||||
|
||||
---
|
||||
|
||||
## 数据与工具(/data-tools)
|
||||
|
||||
页面文件:app/data-tools/page.tsx
|
||||
|
||||
### 已接入接口
|
||||
|
||||
1) 工具列表
|
||||
- `GET /tools`
|
||||
- Base:Data Ingestion
|
||||
- 用途:填充 Tool Registry 表格
|
||||
- 参数:
|
||||
- `category`(可选)
|
||||
- `limit`(默认 100)
|
||||
- `offset`(默认 0)
|
||||
|
||||
2) 删除工具
|
||||
- `DELETE /tools/{tool_name}`
|
||||
- Base:Data Ingestion
|
||||
- 用途:表格行的“Trash”按钮
|
||||
- 路径参数:
|
||||
- `tool_name`:工具名称
|
||||
|
||||
3) 统计信息
|
||||
- `GET /stats`
|
||||
- Base:Data Ingestion
|
||||
- 用途:顶部三张卡片:Total APIs / Generated Tools / Cache Size
|
||||
- 参数:无
|
||||
|
||||
4) RapidAPI 同步
|
||||
- `POST /rapidapi/sync`
|
||||
- Base:Data Ingestion
|
||||
- 用途:RapidAPI Hub 的“Sync Endpoints”按钮
|
||||
- 查询参数:
|
||||
- `category`(可选)
|
||||
- `limit`(默认 100)
|
||||
|
||||
5) APILLAMA 处理
|
||||
- `POST /apillama/process`
|
||||
- Base:Data Ingestion
|
||||
- 用途:APILLAMA Processor 的“Process Documentation”按钮
|
||||
- 请求体:
|
||||
- `api_doc`(string | object,必需):文本或 JSON
|
||||
- `context`(object,可选)
|
||||
- `output_format`(string,可选,默认 json_schema):`json_schema|pydantic|openapi`
|
||||
- 页面使用字段:
|
||||
- `confidence_score`
|
||||
- `completeness_score`
|
||||
|
||||
6) 生成工具定义(最小可用)
|
||||
- `POST /tools/generate`
|
||||
- Base:Data Ingestion
|
||||
- 用途:页面右上角“Generate New Tool”按钮(用浏览器 prompt 收集必要字段)
|
||||
- 请求体(页面目前仅采集最小字段):
|
||||
- `url`(必需)
|
||||
- `method`(必需)
|
||||
- `name`(必需)
|
||||
- `description`(可选)
|
||||
|
||||
### 缺失部分
|
||||
- 暂无(文档内 Data Ingestion 相关接口均已在页面中落地,可视化与操作入口齐备)。
|
||||
|
||||
### 建议补充(后端已有,但建议增强返回/错误)
|
||||
- 建议所有接口统一返回 envelope:`{ status, data, message }`,并保证错误:`{ status:'error', error:{code,message,details} }`。
|
||||
- `DELETE /tools/{tool_name}` 建议返回删除前工具元信息,便于前端回滚/提示。
|
||||
|
||||
---
|
||||
|
||||
## 代理工厂(/agent-factory)
|
||||
|
||||
页面文件:app/agent-factory/page.tsx
|
||||
|
||||
### 已接入接口
|
||||
|
||||
1) Agent 列表
|
||||
- `GET /agents`
|
||||
- Base:MCP Server
|
||||
- 用途:Agent Registry 表格
|
||||
- 参数:
|
||||
- `status`(可选)
|
||||
- `limit`(默认 100)
|
||||
- `offset`(默认 0)
|
||||
|
||||
2) 注册 Agent(最小可用)
|
||||
- `POST /agents`
|
||||
- Base:MCP Server
|
||||
- 用途:页面右上角“Create New Agent”按钮(用浏览器 prompt 收集 name/description/capabilities)
|
||||
- 请求体:
|
||||
- `name`(必需)
|
||||
- `description`(必需/建议必需)
|
||||
- `capabilities`(必需,string[])
|
||||
- `metadata`(可选)
|
||||
|
||||
3) 执行 Agent 工具
|
||||
- `POST /agents/{agent_id}/execute`
|
||||
- Base:MCP Server
|
||||
- 用途:表格行 Play 按钮(当前用 `datetime_now` 作为示例工具,执行结果用 alert 展示)
|
||||
- 路径参数:
|
||||
- `agent_id`
|
||||
- 请求体:
|
||||
- `tool_name`(必需)
|
||||
- `parameters`(必需,object)
|
||||
- `context`(可选,object)
|
||||
|
||||
4) MCP 工具列表
|
||||
- `GET /tools`
|
||||
- Base:MCP Server
|
||||
- 用途:填充“Available Function Tools”网格(若接口失败则回退静态列表)
|
||||
- 参数:
|
||||
- `category`(可选)
|
||||
- `limit`(默认 100)
|
||||
|
||||
5) Agent 详情
|
||||
- `GET /agents/{agent_id}`
|
||||
- Base:MCP Server
|
||||
- 用途:点击表格中的“Eye”按钮后弹出详情对话框,展示 capabilities/metadata 等完整信息
|
||||
- 路径参数:
|
||||
- `agent_id`
|
||||
|
||||
6) MCP Server Prometheus 指标
|
||||
- `GET /metrics`
|
||||
- Base:MCP Server
|
||||
- 用途:Agent Factory 页面新增 “MCP 服务指标” 卡片,点击按钮直接抓取 Prometheus 文本并展示
|
||||
- 参数:无
|
||||
|
||||
7) MCP WebSocket
|
||||
- `GET ws://.../ws/{agent_id}`
|
||||
- Base:MCP Server
|
||||
- 用途:页面“WebSocket 控制台”支持与代理交互、发送 `tools/list` 等 MCP 请求、实时查看消息流
|
||||
- 路径参数:
|
||||
- `agent_id`
|
||||
|
||||
### 缺失部分
|
||||
- “Pause/Trash” 按钮对应的后端接口文档未提供(暂停/删除 agent)。
|
||||
|
||||
### 建议补充的后端能力
|
||||
- 删除 Agent:
|
||||
- `DELETE /agents/{agent_id}` → `{ status:'success', data:{agent_id, deleted:true} }`
|
||||
- 启停 Agent(或状态变更):
|
||||
- `POST /agents/{agent_id}/status` body `{ status:'active'|'inactive' }` → 返回最新 Agent
|
||||
- 执行历史/审计:
|
||||
- `GET /agents/{agent_id}/executions?limit&offset` → 返回工具调用记录、耗时、结果摘要、错误
|
||||
|
||||
---
|
||||
|
||||
## 模型网关(/model-gateway)
|
||||
|
||||
页面文件:app/model-gateway/page.tsx
|
||||
|
||||
### 缺失部分(文档未提供后端接口)
|
||||
- 当前页面所有数据均为静态。
|
||||
|
||||
### 建议补充的后端能力
|
||||
- 模型提供商列表与状态:
|
||||
- `GET /gateway/providers` → `[{ name, status, latency_ms, models:[...]}]`
|
||||
- 网关配置读取/更新:
|
||||
- `GET /gateway/config` → `{ routing, fallbacks, timeouts, quotas }`
|
||||
- `PUT /gateway/config` body 同上 → 返回更新后的 config
|
||||
- 网关监控指标:
|
||||
- `GET /gateway/metrics?range=24h` → `{ p50,p95,error_rate,requests_by_provider }`
|
||||
|
||||
---
|
||||
|
||||
## 编排中心(/orchestration)
|
||||
|
||||
页面文件:app/orchestration/page.tsx
|
||||
|
||||
### 缺失部分(文档未提供后端接口)
|
||||
- 工作流列表、运行状态、NATS 吞吐、VM 池等均为静态。
|
||||
|
||||
### 建议补充的后端能力
|
||||
- 工作流列表/详情:
|
||||
- `GET /workflows?status&limit&offset` → `[{ id,name,status,agents,progress,created_at }]`
|
||||
- `GET /workflows/{id}` → 工作流定义/图结构/参数
|
||||
- 部署/启动/停止:
|
||||
- `POST /workflows/{id}/deploy` body `{ version?, params? }` → `{ task_id, status }`
|
||||
- `POST /workflows/{id}/stop` → `{ stopped:true }`
|
||||
- 运行时指标:
|
||||
- `GET /orchestration/metrics` → NATS TPS、队列深度、P95、失败率
|
||||
|
||||
---
|
||||
|
||||
## 计费与资源(/billing)
|
||||
|
||||
页面文件:app/billing/page.tsx
|
||||
|
||||
### 缺失部分(文档未提供后端接口)
|
||||
- EU 余额、历史消耗、成本拆分、资源配额等均为静态。
|
||||
|
||||
### 建议补充的后端能力
|
||||
- EU 余额:
|
||||
- `GET /billing/eu/balance` → `{ balance: number, updated_at }`
|
||||
- EU 历史:
|
||||
- `GET /billing/eu/history?from&to&granularity=day|hour` → `[{ ts, eu }]`
|
||||
- 成本拆分:
|
||||
- `GET /billing/cost/breakdown?month=YYYY-MM` → `[{ category, cost, eu }]`
|
||||
- 发票下载:
|
||||
- `GET /billing/invoices/{id}/download` → `application/pdf`
|
||||
|
||||
---
|
||||
|
||||
## 其他页面(/admin/*、/channel/*、/login)
|
||||
|
||||
这些页面存在“登录/租户/渠道/供应商/计费”等业务需求,但 `API接口文档.md` 暂未提供对应后端 API:
|
||||
|
||||
- 认证:Admin/Channel/Login 都是本地 mock token。
|
||||
- 渠道/租户管理:创建/编辑/禁用等均缺少后端。
|
||||
- 供应商(模型/数据)管理:缺少 CRUD 与密钥管理接口。
|
||||
|
||||
### 建议补充的后端能力(最小集合)
|
||||
- 认证:
|
||||
- `POST /auth/login` body `{ email,password, role:'admin'|'channel'|'tenant' }` → `{ token, user }`
|
||||
- `POST /auth/logout` → `{ ok:true }`
|
||||
- 渠道管理:
|
||||
- `GET /channels`、`POST /channels`、`PUT /channels/{id}`、`DELETE /channels/{id}`
|
||||
- 租户管理:
|
||||
- `GET /tenants?channel_id&status&limit&offset`、`POST /tenants`、`PUT /tenants/{id}`、`POST /tenants/{id}/suspend`
|
||||
- 供应商管理:
|
||||
- `GET /providers?type=model|data`、`POST /providers`、`PUT /providers/{id}`、`POST /providers/{id}/enable|disable`
|
||||
|
||||
---
|
||||
|
||||
## 代码位置(前端接入点)
|
||||
|
||||
- API Client:[lib/api-client.ts](lib/api-client.ts)
|
||||
- 概览:[components/dashboard-overview.tsx](components/dashboard-overview.tsx)
|
||||
- 数据与工具:[app/data-tools/page.tsx](app/data-tools/page.tsx)
|
||||
- 代理工厂:[app/agent-factory/page.tsx](app/agent-factory/page.tsx)
|
||||
+91
-14
@@ -7,9 +7,15 @@ const API_BASE_URLS = {
|
||||
}
|
||||
|
||||
// Helper function to get auth token
|
||||
// Helper function to get auth token - checks all possible token types
|
||||
function getAuthToken(): string | null {
|
||||
if (typeof window !== "undefined") {
|
||||
return localStorage.getItem("auth_token")
|
||||
// 检查所有可能的token类型
|
||||
const authToken = localStorage.getItem("auth_token")
|
||||
const adminToken = localStorage.getItem("admin_token")
|
||||
const channelToken = localStorage.getItem("channel_token")
|
||||
|
||||
return authToken || adminToken || channelToken
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -22,8 +28,8 @@ function getApiKey(): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
// Helper function to build headers
|
||||
function buildHeaders(contentType = "application/json"): HeadersInit {
|
||||
// Helper function to build headers and ensure authentication
|
||||
function buildHeaders(contentType = "application/json", requireAuth = false): HeadersInit {
|
||||
const headers: HeadersInit = {
|
||||
Accept: "application/json",
|
||||
}
|
||||
@@ -35,6 +41,13 @@ function buildHeaders(contentType = "application/json"): HeadersInit {
|
||||
const token = getAuthToken()
|
||||
const apiKey = getApiKey()
|
||||
|
||||
// 需要认证的请求必须有token或API key
|
||||
if (requireAuth && !token && !apiKey) {
|
||||
throw new Error(
|
||||
"认证失败:未找到有效的token或API key。请重新登录。(Authentication failed: No valid token or API key found. Please log in again.)",
|
||||
)
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`
|
||||
} else if (apiKey) {
|
||||
@@ -44,6 +57,18 @@ function buildHeaders(contentType = "application/json"): HeadersInit {
|
||||
return headers
|
||||
}
|
||||
|
||||
// Helper function to check if user is authenticated
|
||||
function requireAuth(): void {
|
||||
const token = getAuthToken()
|
||||
const apiKey = getApiKey()
|
||||
|
||||
if (!token && !apiKey) {
|
||||
throw new Error(
|
||||
"认证失败:未找到有效的token或API key。请重新登录。(Authentication failed: No valid token or API key found. Please log in again.)",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// API Response types
|
||||
export interface APIResponse<T = any> {
|
||||
success: boolean
|
||||
@@ -93,6 +118,16 @@ export class TaijiAPIClient {
|
||||
role: "user" | "channel" | "admin" | "provider" = "user",
|
||||
): Promise<APIResponse<{ token: string; refreshToken?: string; user?: any }>> {
|
||||
try {
|
||||
// 清除所有旧的token(防止多用户登录时token重复)
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("channel_token")
|
||||
localStorage.removeItem("admin_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
}
|
||||
|
||||
// 检查后端服务是否可达
|
||||
const isReachable = await checkBackendReachable(API_BASE_URLS.mcpServer)
|
||||
if (!isReachable) {
|
||||
@@ -109,7 +144,9 @@ export class TaijiAPIClient {
|
||||
})
|
||||
const data = await handleResponse<APIResponse<{ token: string; refreshToken?: string; user?: any }>>(response)
|
||||
if (data.success && data.data?.token && typeof window !== "undefined") {
|
||||
localStorage.setItem("auth_token", data.data.token)
|
||||
// 根据role存储对应的token
|
||||
const tokenKey = role === "channel" ? "channel_token" : role === "admin" ? "admin_token" : "auth_token"
|
||||
localStorage.setItem(tokenKey, data.data.token)
|
||||
if (data.data.refreshToken) {
|
||||
localStorage.setItem("refresh_token", data.data.refreshToken)
|
||||
}
|
||||
@@ -136,23 +173,37 @@ export class TaijiAPIClient {
|
||||
* 用户登出
|
||||
*/
|
||||
static async logout() {
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/auth/logout`, {
|
||||
method: "POST",
|
||||
headers: buildHeaders(),
|
||||
})
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/auth/logout`, {
|
||||
method: "POST",
|
||||
headers: buildHeaders(),
|
||||
})
|
||||
await handleResponse(response)
|
||||
} catch (error) {
|
||||
// 即使API调用失败,也要清除本地存储
|
||||
console.error("Logout API call failed:", error)
|
||||
} finally {
|
||||
// 清除所有本地存储的token和用户信息
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("auth_token")
|
||||
localStorage.removeItem("channel_token")
|
||||
localStorage.removeItem("admin_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("api_key")
|
||||
}
|
||||
}
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
static async refreshToken(): Promise<APIResponse<{ token: string; refreshToken?: string }>> {
|
||||
try {
|
||||
requireAuth()
|
||||
} catch (error) {
|
||||
throw new Error("无法刷新token:用户未认证。(Cannot refresh token: user not authenticated.)")
|
||||
}
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/auth/refresh`, {
|
||||
method: "POST",
|
||||
headers: buildHeaders(),
|
||||
@@ -171,6 +222,11 @@ export class TaijiAPIClient {
|
||||
* 修改密码
|
||||
*/
|
||||
static async changePassword(oldPassword: string, newPassword: string) {
|
||||
try {
|
||||
requireAuth()
|
||||
} catch (error) {
|
||||
throw new Error("无法修改密码:用户未认证。(Cannot change password: user not authenticated.)")
|
||||
}
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/auth/password`, {
|
||||
method: "PUT",
|
||||
headers: buildHeaders(),
|
||||
@@ -183,6 +239,11 @@ export class TaijiAPIClient {
|
||||
* 获取API密钥信息
|
||||
*/
|
||||
static async getApiKeyInfo() {
|
||||
try {
|
||||
requireAuth()
|
||||
} catch (error) {
|
||||
throw new Error("无法获取API密钥:用户未认证。(Cannot get API key: user not authenticated.)")
|
||||
}
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/auth/keys/info`, {
|
||||
headers: buildHeaders(),
|
||||
})
|
||||
@@ -193,6 +254,11 @@ export class TaijiAPIClient {
|
||||
* 重新生成API密钥
|
||||
*/
|
||||
static async regenerateApiKey(): Promise<APIResponse<{ apiKey: string; message?: string }>> {
|
||||
try {
|
||||
requireAuth()
|
||||
} catch (error) {
|
||||
throw new Error("无法重新生成API密钥:用户未认证。(Cannot regenerate API key: user not authenticated.)")
|
||||
}
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/auth/keys/regenerate`, {
|
||||
method: "POST",
|
||||
headers: buildHeaders(),
|
||||
@@ -647,6 +713,17 @@ export class TaijiAPIClient {
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Agent资源
|
||||
*/
|
||||
static async deleteAgentResource(agentId: string) {
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/admin/resources/agents/${agentId}`, {
|
||||
method: "DELETE",
|
||||
headers: buildHeaders(),
|
||||
})
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监控Agent健康状态
|
||||
*/
|
||||
|
||||
+6
-1
@@ -2,7 +2,12 @@
|
||||
|
||||
export function getAuthToken(): string | null {
|
||||
if (typeof window !== "undefined") {
|
||||
return localStorage.getItem("auth_token")
|
||||
// 检查所有可能的token类型
|
||||
const authToken = localStorage.getItem("auth_token")
|
||||
const adminToken = localStorage.getItem("admin_token")
|
||||
const channelToken = localStorage.getItem("channel_token")
|
||||
|
||||
return authToken || adminToken || channelToken
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user