前端代码
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
VITE_API_INGESTION=http://localhost:8001
|
||||||
|
VITE_API_MCP=http://localhost:8000
|
||||||
|
VITE_API_GATEWAY=http://localhost:80
|
||||||
|
VITE_WS_MCP=ws://localhost:8000/ws
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
VITE_API_INGESTION=http://localhost:8001
|
||||||
|
VITE_API_MCP=http://localhost:8000
|
||||||
|
VITE_API_GATEWAY=http://localhost:80
|
||||||
|
VITE_WS_MCP=ws://localhost:8000/ws
|
||||||
+879
@@ -0,0 +1,879 @@
|
|||||||
|
# taiji-AI-PAD API 接口文档
|
||||||
|
|
||||||
|
**版本**: v1.2.1
|
||||||
|
**更新时间**: 2025年12月22日
|
||||||
|
**最后更新**: 2025年12月22日
|
||||||
|
**基础URL**:
|
||||||
|
- Data Ingestion 服务: `http://localhost:8001`
|
||||||
|
- MCP Server 服务: `http://localhost:8000`
|
||||||
|
- 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:8000`
|
||||||
|
|
||||||
|
### 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:8000/ws/{agent_id}`
|
||||||
|
|
||||||
|
**连接示例**:
|
||||||
|
```javascript
|
||||||
|
const ws = new WebSocket('ws://localhost:8000/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:8000/docs`
|
||||||
|
|
||||||
|
### ReDoc
|
||||||
|
|
||||||
|
- Data Ingestion: `http://localhost:8001/redoc`
|
||||||
|
- MCP Server: `http://localhost:8000/redoc`
|
||||||
|
|
||||||
|
### OpenAPI JSON
|
||||||
|
|
||||||
|
- Data Ingestion: `http://localhost:8001/openapi.json`
|
||||||
|
- MCP Server: `http://localhost:8000/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 端点文档
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# taiji-AI-PAD 前端工程
|
||||||
|
|
||||||
|
本项目是基于 Vue 3 + TypeScript + Vite 构建的 Agent 赋能平台前端界面。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
- Vue 3 (Composition API)
|
||||||
|
- TypeScript
|
||||||
|
- Pinia (状态管理)
|
||||||
|
- Vue Router (路由)
|
||||||
|
- Axios (接口请求)
|
||||||
|
- CSS Variables + Web Animations API (动画与主题)
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
- src/api: 接口定义与 Axios 封装
|
||||||
|
- src/components: 公用 UI 组件 (Card, Sidebar, Header, Toast)
|
||||||
|
- src/stores: Pinia 状态存储
|
||||||
|
- src/views: 业务页面 (仪表盘, 工具中心, RapidAPI, OpenAPI, APILLAMA, Agent 管理)
|
||||||
|
- src/styles: 全局样式与动画定义
|
||||||
|
- src/utils: 工具函数与请求拦截
|
||||||
|
|
||||||
|
## 快速启动
|
||||||
|
1. 安装依赖:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
2. 配置环境变量:
|
||||||
|
修改 .env 文件中的 VITE_API_INGESTION, VITE_API_MCP 等地址。
|
||||||
|
|
||||||
|
3. 运行开发服务器:
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
4. 构建生产版本:
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
## 接口映射说明
|
||||||
|
- 仪表盘: 对接 /health 与 /stats
|
||||||
|
- 工具中心: 对接 /tools (GET/POST/DELETE)
|
||||||
|
- RapidAPI: 对接 /rapidapi/sync 与 /rapidapi/test
|
||||||
|
- OpenAPI: 对接 /openapi/parse
|
||||||
|
- APILLAMA: 对接 /apillama/process
|
||||||
|
- Agent 管理: 对接 /agents 与 /agents/{id}/execute
|
||||||
|
- 实时会话: 对接 ws://localhost:8000/ws/{agent_id}
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
- 全站严禁使用 emoji
|
||||||
|
- 默认支持浅色/深色主题切换
|
||||||
|
- 响应式适配 1366px 及以上桌面端与移动端
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Taiji AI-PAD | Agent 赋能平台</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+1830
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "taiji_agent_vue",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Agent 赋能平台是一个旨在将 AI Agents 从实验性脚本演进为工业级生产力单元的全栈工程化平台 。平台通过构建标准化的智力资源分发与治理体系,整合异构数据,支持多模型动态切换,并具备透明的计费与安全隔离机制 。",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"pinia": "^2.3.1",
|
||||||
|
"vue": "^3.5.26",
|
||||||
|
"vue-router": "^4.6.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.19.3",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite": "^6.4.1",
|
||||||
|
"vue-tsc": "^2.2.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="grid-bg"></div>
|
||||||
|
<Sidebar />
|
||||||
|
<main class="main-content">
|
||||||
|
<Header />
|
||||||
|
<div class="view-container">
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Toast />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Sidebar from '@/components/Sidebar.vue';
|
||||||
|
import Header from '@/components/Header.vue';
|
||||||
|
import Toast from '@/components/Toast.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-container {
|
||||||
|
padding: 24px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { ingestionApi, mcpApi } from '@/utils/request';
|
||||||
|
|
||||||
|
// Data Ingestion Services
|
||||||
|
export const ingestionService = {
|
||||||
|
getHealth: () => ingestionApi.get('/health'),
|
||||||
|
getStats: () => ingestionApi.get('/stats'),
|
||||||
|
syncRapidApi: (params: { category?: string; limit?: number }) =>
|
||||||
|
ingestionApi.post('/rapidapi/sync', null, { params }),
|
||||||
|
testRapidApi: (data: any) => ingestionApi.post('/rapidapi/test', data),
|
||||||
|
parseOpenApi: (url: string) => ingestionApi.post(`/openapi/parse?url=${encodeURIComponent(url)}`),
|
||||||
|
processApiLlama: (data: any) => ingestionApi.post('/apillama/process', data),
|
||||||
|
generateTool: (data: any) => ingestionApi.post('/tools/generate', data),
|
||||||
|
getTools: (params?: any) => ingestionApi.get('/tools', { params }),
|
||||||
|
getTool: (name: string) => ingestionApi.get(`/tools/${name}`),
|
||||||
|
deleteTool: (name: string) => ingestionApi.delete(`/tools/${name}`),
|
||||||
|
clearCache: (pattern?: string) => ingestionApi.post('/cache/clear', null, { params: { pattern } }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// MCP Server Services
|
||||||
|
export const mcpService = {
|
||||||
|
getHealth: () => mcpApi.get('/health'),
|
||||||
|
registerAgent: (data: any) => mcpApi.post('/agents', data),
|
||||||
|
getAgents: (params?: any) => mcpApi.get('/agents', { params }),
|
||||||
|
getAgent: (id: string) => mcpApi.get(`/agents/${id}`),
|
||||||
|
executeTool: (id: string, data: any) => mcpApi.post(`/agents/${id}/execute`, data),
|
||||||
|
};
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card" :class="{ hoverable }">
|
||||||
|
<div v-if="$slots.header || title" class="card-header">
|
||||||
|
<slot name="header">
|
||||||
|
<h3 class="card-title">{{ title }}</h3>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div v-if="$slots.footer" class="card-footer">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title?: string;
|
||||||
|
hoverable?: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.hoverable:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer {
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="page-title">{{ route.meta.title || 'Dashboard' }}</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div class="status-indicators">
|
||||||
|
<div class="status-item" :class="{ healthy: ingestionHealthy }">
|
||||||
|
<span class="dot"></span> Ingestion
|
||||||
|
</div>
|
||||||
|
<div class="status-item" :class="{ healthy: mcpHealthy }">
|
||||||
|
<span class="dot"></span> MCP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="theme-toggle" @click="appStore.toggleTheme">
|
||||||
|
{{ appStore.theme === 'light' ? 'DARK' : 'LIGHT' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
import { ingestionService, mcpService } from '@/api';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const ingestionHealthy = ref(false);
|
||||||
|
const mcpHealthy = ref(false);
|
||||||
|
|
||||||
|
const checkHealth = async () => {
|
||||||
|
try {
|
||||||
|
const res1 = await ingestionService.getHealth();
|
||||||
|
ingestionHealthy.value = res1.status === 'healthy';
|
||||||
|
const res2 = await mcpService.getHealth();
|
||||||
|
mcpHealthy.value = res2.status === 'healthy';
|
||||||
|
} catch (e) {
|
||||||
|
ingestionHealthy.value = false;
|
||||||
|
mcpHealthy.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkHealth();
|
||||||
|
setInterval(checkHealth, 30000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
height: 72px;
|
||||||
|
padding: 0 24px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicators {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.healthy .dot {
|
||||||
|
background-color: #10b981;
|
||||||
|
box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="logo">
|
||||||
|
<div class="logo-icon"></div>
|
||||||
|
<span class="logo-text">TAIJI AI-PAD</span>
|
||||||
|
</div>
|
||||||
|
<nav class="nav-links">
|
||||||
|
<router-link to="/" class="nav-item">
|
||||||
|
<span class="icon">D</span>
|
||||||
|
<span class="label">仪表盘</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/tools" class="nav-item">
|
||||||
|
<span class="icon">T</span>
|
||||||
|
<span class="label">工具中心</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/rapidapi" class="nav-item">
|
||||||
|
<span class="icon">R</span>
|
||||||
|
<span class="label">RapidAPI</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/openapi" class="nav-item">
|
||||||
|
<span class="icon">O</span>
|
||||||
|
<span class="label">OpenAPI</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/apillama" class="nav-item">
|
||||||
|
<span class="icon">L</span>
|
||||||
|
<span class="label">APILLAMA</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/agents" class="nav-item">
|
||||||
|
<span class="icon">A</span>
|
||||||
|
<span class="label">Agent 管理</span>
|
||||||
|
</router-link>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar {
|
||||||
|
width: 260px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border-right: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24px 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
padding: 0 24px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.router-link-active {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
background-color: rgba(59, 130, 246, 0.05);
|
||||||
|
border-left-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 12px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
.logo { margin-bottom: 0; }
|
||||||
|
.nav-links { flex-direction: row; overflow-x: auto; }
|
||||||
|
.nav-item { padding: 8px 12px; border-left: none; border-bottom: 3px solid transparent; }
|
||||||
|
.nav-item.router-link-active { border-bottom-color: var(--accent-primary); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div class="toast-container">
|
||||||
|
<transition-group name="list">
|
||||||
|
<div v-for="n in appStore.notifications" :key="n.id" class="toast" :class="n.type">
|
||||||
|
{{ n.message }}
|
||||||
|
</div>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
const appStore = useAppStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success { border-left: 4px solid #10b981; }
|
||||||
|
.toast.error { border-left: 4px solid #ef4444; }
|
||||||
|
.toast.info { border-left: 4px solid var(--accent-primary); }
|
||||||
|
</style>
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import './styles/main.css'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
// Initialize theme
|
||||||
|
const theme = localStorage.getItem('theme') || 'light'
|
||||||
|
document.documentElement.setAttribute('data-theme', theme)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Dashboard',
|
||||||
|
component: () => import('@/views/Dashboard.vue'),
|
||||||
|
meta: { title: '总览仪表盘' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/tools',
|
||||||
|
name: 'Tools',
|
||||||
|
component: () => import('@/views/Tools.vue'),
|
||||||
|
meta: { title: '工具中心' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/rapidapi',
|
||||||
|
name: 'RapidAPI',
|
||||||
|
component: () => import('@/views/RapidAPI.vue'),
|
||||||
|
meta: { title: 'RapidAPI' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/openapi',
|
||||||
|
name: 'OpenAPI',
|
||||||
|
component: () => import('@/views/OpenAPI.vue'),
|
||||||
|
meta: { title: 'OpenAPI 解析' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/apillama',
|
||||||
|
name: 'APILLAMA',
|
||||||
|
component: () => import('@/views/APILLAMA.vue'),
|
||||||
|
meta: { title: 'APILLAMA 处理' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agents',
|
||||||
|
name: 'Agents',
|
||||||
|
component: () => import('@/views/Agents.vue'),
|
||||||
|
meta: { title: 'Agent 管理' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/agents/:id',
|
||||||
|
name: 'AgentDetail',
|
||||||
|
component: () => import('@/views/AgentDetail.vue'),
|
||||||
|
meta: { title: 'Agent 详情' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', {
|
||||||
|
state: () => ({
|
||||||
|
theme: localStorage.getItem('theme') || 'light',
|
||||||
|
baseUrls: {
|
||||||
|
ingestion: import.meta.env.VITE_API_INGESTION,
|
||||||
|
mcp: import.meta.env.VITE_API_MCP,
|
||||||
|
gateway: import.meta.env.VITE_API_GATEWAY,
|
||||||
|
},
|
||||||
|
notifications: [] as { id: number; type: 'success' | 'error' | 'info'; message: string }[],
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
toggleTheme() {
|
||||||
|
this.theme = this.theme === 'light' ? 'dark' : 'light';
|
||||||
|
localStorage.setItem('theme', this.theme);
|
||||||
|
document.documentElement.setAttribute('data-theme', this.theme);
|
||||||
|
},
|
||||||
|
notify(message: string, type: 'success' | 'error' | 'info' = 'info') {
|
||||||
|
const id = Date.now();
|
||||||
|
this.notifications.push({ id, type, message });
|
||||||
|
setTimeout(() => {
|
||||||
|
this.notifications = this.notifications.filter(n => n.id !== id);
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
:root {
|
||||||
|
--bg-primary: #f8fafc;
|
||||||
|
--bg-secondary: #ffffff;
|
||||||
|
--text-primary: #1e293b;
|
||||||
|
--text-secondary: #64748b;
|
||||||
|
--accent-primary: #3b82f6;
|
||||||
|
--accent-secondary: #60a5fa;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||||
|
--radius-md: 12px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
--transition-fast: 160ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--transition-normal: 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] {
|
||||||
|
--bg-primary: #0f172a;
|
||||||
|
--bg-secondary: #1e293b;
|
||||||
|
--text-primary: #f1f5f9;
|
||||||
|
--text-secondary: #94a3b8;
|
||||||
|
--accent-primary: #3b82f6;
|
||||||
|
--border-color: #334155;
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
transition: background-color var(--transition-normal), color var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea, select {
|
||||||
|
font-family: inherit;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sci-fi Grid Background */
|
||||||
|
.grid-bg {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(var(--border-color) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, var(--border-color) 1px, transparent 1px);
|
||||||
|
background-size: 40px 40px;
|
||||||
|
opacity: 0.05;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
.fade-enter-active, .fade-leave-active {
|
||||||
|
transition: opacity var(--transition-normal);
|
||||||
|
}
|
||||||
|
.fade-enter-from, .fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-active, .slide-up-leave-active {
|
||||||
|
transition: transform var(--transition-normal), opacity var(--transition-normal);
|
||||||
|
}
|
||||||
|
.slide-up-enter-from {
|
||||||
|
transform: translateY(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.slide-up-leave-to {
|
||||||
|
transform: translateY(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-move, .list-enter-active, .list-leave-active {
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
.list-enter-from, .list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skeleton */
|
||||||
|
.skeleton {
|
||||||
|
background: linear-gradient(90deg, var(--border-color) 25%, var(--bg-secondary) 50%, var(--border-color) 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-loading 1.5s infinite;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export interface ApiResponse<T = any> {
|
||||||
|
status?: string;
|
||||||
|
message?: string;
|
||||||
|
data?: T;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
private instance: AxiosInstance;
|
||||||
|
|
||||||
|
constructor(baseURL: string) {
|
||||||
|
this.instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setupInterceptors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupInterceptors() {
|
||||||
|
this.instance.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
(config as any).metadata = { startTime: new Date() };
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.instance.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => {
|
||||||
|
const startTime = (response.config as any).metadata.startTime;
|
||||||
|
const endTime = new Date();
|
||||||
|
const duration = endTime.getTime() - startTime.getTime();
|
||||||
|
|
||||||
|
// Log request for debugging panel
|
||||||
|
console.log(`[API] ${response.config.method?.toUpperCase()} ${response.config.url} - ${duration}ms`);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
const message = error.response?.data?.message || error.message || 'Request failed';
|
||||||
|
console.error(`[API Error]`, error);
|
||||||
|
return Promise.reject({
|
||||||
|
message,
|
||||||
|
status: error.response?.status,
|
||||||
|
data: error.response?.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
return this.instance.get(url, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
return this.instance.post(url, data, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
return this.instance.delete(url, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ingestionApi = new Request(import.meta.env.VITE_API_INGESTION);
|
||||||
|
export const mcpApi = new Request(import.meta.env.VITE_API_MCP);
|
||||||
|
export const gatewayApi = new Request(import.meta.env.VITE_API_GATEWAY);
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<div class="openapi-view">
|
||||||
|
<Card title="解析 OpenAPI 规范">
|
||||||
|
<div class="input-row">
|
||||||
|
<input v-model="url" placeholder="输入 OpenAPI JSON/YAML URL..." />
|
||||||
|
<button class="btn-primary" @click="handleParse" :disabled="parsing">
|
||||||
|
{{ parsing ? '解析中...' : '开始解析' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="result" class="result-container">
|
||||||
|
<div class="summary-grid">
|
||||||
|
<Card title="基本信息">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">标题</span>
|
||||||
|
<span class="value">{{ result.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">版本</span>
|
||||||
|
<span class="value">{{ result.version }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">端点数量</span>
|
||||||
|
<span class="value">{{ result.endpoints_count }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Schema 数量</span>
|
||||||
|
<span class="value">{{ result.schemas_count }}</span>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card title="端点摘要 (Paths)" class="paths-card">
|
||||||
|
<div class="paths-list">
|
||||||
|
<div v-for="(methods, path) in result.parsed_data.paths" :key="path" class="path-item">
|
||||||
|
<div class="path-header" @click="togglePath(path)">
|
||||||
|
<span class="path-text">{{ path }}</span>
|
||||||
|
<div class="method-tags">
|
||||||
|
<span v-for="(_, method) in methods" :key="method" class="method-tag" :class="method">
|
||||||
|
{{ method }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="expandedPaths.has(path)" class="path-detail">
|
||||||
|
<div v-for="(detail, method) in methods" :key="method" class="method-detail">
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Summary:</span>
|
||||||
|
<span>{{ detail.summary || '无' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Responses:</span>
|
||||||
|
<div class="response-tags">
|
||||||
|
<span v-for="(res, code) in detail.responses" :key="code" class="res-tag">
|
||||||
|
{{ code }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import Card from '@/components/Card.vue';
|
||||||
|
import { ingestionService } from '@/api';
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const url = ref('');
|
||||||
|
const parsing = ref(false);
|
||||||
|
const result = ref<any>(null);
|
||||||
|
const expandedPaths = ref(new Set<string>());
|
||||||
|
|
||||||
|
const handleParse = async () => {
|
||||||
|
if (!url.value) return;
|
||||||
|
parsing.value = true;
|
||||||
|
try {
|
||||||
|
result.value = await ingestionService.parseOpenApi(url.value);
|
||||||
|
appStore.notify('解析成功', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('解析失败', 'error');
|
||||||
|
} finally {
|
||||||
|
parsing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePath = (path: string) => {
|
||||||
|
if (expandedPaths.value.has(path)) {
|
||||||
|
expandedPaths.value.delete(path);
|
||||||
|
} else {
|
||||||
|
expandedPaths.value.add(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.openapi-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-row input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:last-child { border-bottom: none; }
|
||||||
|
|
||||||
|
.info-item .label { color: var(--text-secondary); font-size: 0.9rem; }
|
||||||
|
.info-item .value { font-weight: 600; }
|
||||||
|
|
||||||
|
.paths-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-item {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-text { font-family: monospace; font-weight: 600; font-size: 0.9rem; }
|
||||||
|
|
||||||
|
.method-tags { display: flex; gap: 4px; }
|
||||||
|
|
||||||
|
.method-tag {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 800;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-tag.get { background: #3b82f6; }
|
||||||
|
.method-tag.post { background: #10b981; }
|
||||||
|
.method-tag.put { background: #f59e0b; }
|
||||||
|
.method-tag.delete { background: #ef4444; }
|
||||||
|
|
||||||
|
.path-detail {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-detail {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px dashed var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-detail:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
|
||||||
|
|
||||||
|
.detail-row { display: flex; gap: 12px; margin-bottom: 4px; font-size: 0.85rem; }
|
||||||
|
.detail-label { color: var(--text-secondary); font-weight: 600; min-width: 80px; }
|
||||||
|
|
||||||
|
.response-tags { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
|
.res-tag {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<div class="apillama-view">
|
||||||
|
<div class="view-grid">
|
||||||
|
<Card title="文档处理配置">
|
||||||
|
<form @submit.prevent="handleProcess" class="form-stack">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>API 文档 (String or JSON)</label>
|
||||||
|
<textarea v-model="form.api_doc_str" rows="10" placeholder="粘贴 API 文档内容..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>输出格式</label>
|
||||||
|
<select v-model="form.output_format">
|
||||||
|
<option value="json_schema">JSON Schema</option>
|
||||||
|
<option value="pydantic">Pydantic</option>
|
||||||
|
<option value="openapi">OpenAPI</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary" :disabled="processing">
|
||||||
|
{{ processing ? '处理中...' : '开始处理' }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="result" class="result-stack">
|
||||||
|
<Card title="处理结果摘要">
|
||||||
|
<div class="metrics-grid">
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="label">置信度</span>
|
||||||
|
<div class="progress-mini">
|
||||||
|
<div class="fill" :style="{ width: (result.confidence_score * 100) + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
<span class="value">{{ (result.confidence_score * 100).toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="label">完整度</span>
|
||||||
|
<div class="progress-mini">
|
||||||
|
<div class="fill" :style="{ width: (result.completeness_score * 100) + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
<span class="value">{{ (result.completeness_score * 100).toFixed(1) }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="label">耗时</span>
|
||||||
|
<span class="value">{{ result.processing_time }}s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="result-desc">{{ result.description }}</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="生成的 Schema">
|
||||||
|
<pre class="code-block">{{ JSON.stringify(result.schema, null, 2) }}</pre>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import Card from '@/components/Card.vue';
|
||||||
|
import { ingestionService } from '@/api';
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const processing = ref(false);
|
||||||
|
const result = ref<any>(null);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
api_doc_str: '',
|
||||||
|
output_format: 'json_schema',
|
||||||
|
context: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleProcess = async () => {
|
||||||
|
if (!form.api_doc_str) return;
|
||||||
|
processing.value = true;
|
||||||
|
try {
|
||||||
|
let api_doc = form.api_doc_str;
|
||||||
|
try {
|
||||||
|
api_doc = JSON.parse(form.api_doc_str);
|
||||||
|
} catch (e) {
|
||||||
|
// Keep as string
|
||||||
|
}
|
||||||
|
|
||||||
|
result.value = await ingestionService.processApiLlama({
|
||||||
|
api_doc,
|
||||||
|
output_format: form.output_format,
|
||||||
|
context: form.context
|
||||||
|
});
|
||||||
|
appStore.notify('处理完成', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('处理失败', 'error');
|
||||||
|
} finally {
|
||||||
|
processing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.apillama-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-stack { display: flex; flex-direction: column; gap: 16px; }
|
||||||
|
.form-group { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
.form-group label { font-size: 0.85rem; font-weight: 600; }
|
||||||
|
.form-group textarea, .form-group select {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-stack { display: flex; flex-direction: column; gap: 24px; }
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-item .label { font-size: 0.8rem; color: var(--text-secondary); min-width: 60px; }
|
||||||
|
|
||||||
|
.progress-mini {
|
||||||
|
flex: 1;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-mini .fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-item .value { font-size: 0.85rem; font-weight: 700; min-width: 50px; text-align: right; }
|
||||||
|
|
||||||
|
.result-desc { font-size: 0.9rem; color: var(--text-secondary); line-height: 1.6; }
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
padding: 16px;
|
||||||
|
background: #1e293b;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.view-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<div class="stats-grid">
|
||||||
|
<Card v-for="(val, key) in stats" :key="key" :title="formatKey(key)" hoverable>
|
||||||
|
<div class="stat-value">{{ val }}</div>
|
||||||
|
<div class="stat-trend" :class="{ up: true }">
|
||||||
|
<span class="trend-icon">↑</span> 正常运行中
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="charts-row">
|
||||||
|
<Card title="服务健康状态" class="health-card">
|
||||||
|
<div class="health-list">
|
||||||
|
<div v-for="(status, name) in health?.services" :key="name" class="health-item">
|
||||||
|
<span class="service-name">{{ name }}</span>
|
||||||
|
<span class="status-badge" :class="status">{{ status }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="分类统计" class="category-card">
|
||||||
|
<div class="category-list">
|
||||||
|
<div v-for="(count, cat) in stats?.categories" :key="cat" class="category-item">
|
||||||
|
<div class="cat-info">
|
||||||
|
<span class="cat-name">{{ cat }}</span>
|
||||||
|
<span class="cat-count">{{ count }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" :style="{ width: (count / (stats?.total_apis || 1) * 100) + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import Card from '@/components/Card.vue';
|
||||||
|
import { ingestionService } from '@/api';
|
||||||
|
|
||||||
|
const stats = ref<any>({});
|
||||||
|
const health = ref<any>({});
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
stats.value = await ingestionService.getStats();
|
||||||
|
health.value = await ingestionService.getHealth();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatKey = (key: string) => {
|
||||||
|
const map: any = {
|
||||||
|
total_apis: '总 API 数量',
|
||||||
|
processed_apis: '已处理 API',
|
||||||
|
generated_tools: '已生成工具',
|
||||||
|
cache_size: '缓存规模 (MB)'
|
||||||
|
};
|
||||||
|
return map[key] || key;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dashboard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-trend {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-trend.up { color: #10b981; }
|
||||||
|
|
||||||
|
.charts-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.health-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.health-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.healthy { background: rgba(16, 185, 129, 0.1); color: #10b981; }
|
||||||
|
.status-badge.unhealthy { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
|
||||||
|
|
||||||
|
.category-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 6px;
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
transition: width 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.charts-row { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<template>
|
||||||
|
<div class="openapi-view">
|
||||||
|
<Card title="解析 OpenAPI 规范">
|
||||||
|
<div class="input-row">
|
||||||
|
<input v-model="url" placeholder="输入 OpenAPI JSON/YAML URL..." />
|
||||||
|
<button class="btn-primary" @click="handleParse" :disabled="parsing">
|
||||||
|
{{ parsing ? '解析中...' : '开始解析' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="result" class="result-container">
|
||||||
|
<div class="summary-grid">
|
||||||
|
<Card title="基本信息">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">标题</span>
|
||||||
|
<span class="value">{{ result.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">版本</span>
|
||||||
|
<span class="value">{{ result.version }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">端点数量</span>
|
||||||
|
<span class="value">{{ result.endpoints_count }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Schema 数量</span>
|
||||||
|
<span class="value">{{ result.schemas_count }}</span>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card title="端点摘要 (Paths)" class="paths-card">
|
||||||
|
<div class="paths-list">
|
||||||
|
<div v-for="(methods, path) in result.parsed_data.paths" :key="path" class="path-item">
|
||||||
|
<div class="path-header" @click="togglePath(path)">
|
||||||
|
<span class="path-text">{{ path }}</span>
|
||||||
|
<div class="method-tags">
|
||||||
|
<span v-for="(_, method) in methods" :key="method" class="method-tag" :class="method">
|
||||||
|
{{ method }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="expandedPaths.has(path)" class="path-detail">
|
||||||
|
<div v-for="(detail, method) in methods" :key="method" class="method-detail">
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Summary:</span>
|
||||||
|
<span>{{ detail.summary || '无' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Responses:</span>
|
||||||
|
<div class="response-tags">
|
||||||
|
<span v-for="(res, code) in detail.responses" :key="code" class="res-tag">
|
||||||
|
{{ code }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import Card from '@/components/Card.vue';
|
||||||
|
import { ingestionService } from '@/api';
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const url = ref('');
|
||||||
|
const parsing = ref(false);
|
||||||
|
const result = ref<any>(null);
|
||||||
|
const expandedPaths = ref(new Set<string>());
|
||||||
|
|
||||||
|
const handleParse = async () => {
|
||||||
|
if (!url.value) return;
|
||||||
|
parsing.value = true;
|
||||||
|
try {
|
||||||
|
result.value = await ingestionService.parseOpenApi(url.value);
|
||||||
|
appStore.notify('解析成功', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('解析失败', 'error');
|
||||||
|
} finally {
|
||||||
|
parsing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePath = (path: string) => {
|
||||||
|
if (expandedPaths.value.has(path)) {
|
||||||
|
expandedPaths.value.delete(path);
|
||||||
|
} else {
|
||||||
|
expandedPaths.value.add(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.openapi-view { display: flex; flex-direction: column; gap: 24px; }
|
||||||
|
.input-row { display: flex; gap: 12px; }
|
||||||
|
.input-row input { flex: 1; padding: 12px 16px; border: 1px solid var(--border-color); border-radius: 8px; background: var(--bg-primary); }
|
||||||
|
.result-container { display: flex; flex-direction: column; gap: 24px; }
|
||||||
|
.info-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border-color); }
|
||||||
|
.info-item:last-child { border-bottom: none; }
|
||||||
|
.info-item .label { color: var(--text-secondary); font-size: 0.9rem; }
|
||||||
|
.info-item .value { font-weight: 600; }
|
||||||
|
.paths-list { display: flex; flex-direction: column; gap: 12px; }
|
||||||
|
.path-item { border: 1px solid var(--border-color); border-radius: 8px; overflow: hidden; }
|
||||||
|
.path-header { padding: 12px 16px; background: var(--bg-primary); cursor: pointer; display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.path-text { font-family: monospace; font-weight: 600; font-size: 0.9rem; }
|
||||||
|
.method-tags { display: flex; gap: 4px; }
|
||||||
|
.method-tag { font-size: 0.65rem; font-weight: 800; padding: 2px 6px; border-radius: 4px; color: white; text-transform: uppercase; }
|
||||||
|
.method-tag.get { background: #3b82f6; }
|
||||||
|
.method-tag.post { background: #10b981; }
|
||||||
|
.method-tag.put { background: #f59e0b; }
|
||||||
|
.method-tag.delete { background: #ef4444; }
|
||||||
|
.path-detail { padding: 16px; background: var(--bg-secondary); border-top: 1px solid var(--border-color); }
|
||||||
|
.method-detail { margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px dashed var(--border-color); }
|
||||||
|
.method-detail:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
|
||||||
|
.detail-row { display: flex; gap: 12px; margin-bottom: 4px; font-size: 0.85rem; }
|
||||||
|
.detail-label { color: var(--text-secondary); font-weight: 600; min-width: 80px; }
|
||||||
|
.response-tags { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
|
.res-tag { background: var(--bg-primary); border: 1px solid var(--border-color); padding: 1px 6px; border-radius: 4px; font-size: 0.75rem; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
<template>
|
||||||
|
<div class="rapidapi-view">
|
||||||
|
<div class="view-grid">
|
||||||
|
<!-- Sync Section -->
|
||||||
|
<Card title="同步端点" class="sync-card">
|
||||||
|
<form @submit.prevent="handleSync" class="form-stack">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>分类 (Category)</label>
|
||||||
|
<input v-model="syncForm.category" placeholder="例如: weather" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>限制数量 (Limit)</label>
|
||||||
|
<input type="number" v-model="syncForm.limit" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary" :disabled="syncing">
|
||||||
|
{{ syncing ? '同步中...' : '开始同步' }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Test Section -->
|
||||||
|
<Card title="测试端点" class="test-card">
|
||||||
|
<form @submit.prevent="handleTest" class="form-stack">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>端点 URL</label>
|
||||||
|
<input v-model="testForm.endpoint" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>方法</label>
|
||||||
|
<select v-model="testForm.method">
|
||||||
|
<option>GET</option>
|
||||||
|
<option>POST</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>参数 (JSON)</label>
|
||||||
|
<textarea v-model="testForm.paramsStr" rows="3" placeholder='{"key": "value"}'></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-accent" :disabled="testing">
|
||||||
|
{{ testing ? '测试中...' : '发送请求' }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Test Result -->
|
||||||
|
<transition name="fade">
|
||||||
|
<Card v-if="testResult" title="测试结果" class="result-card">
|
||||||
|
<div class="result-header">
|
||||||
|
<span class="status-code" :class="{ success: testResult.success }">
|
||||||
|
{{ testResult.status_code }}
|
||||||
|
</span>
|
||||||
|
<span class="time">{{ testResult.response_time }}ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="result-body">
|
||||||
|
<div class="collapsible">
|
||||||
|
<div class="collapsible-header" @click="showData = !showData">
|
||||||
|
<span>响应数据 (Data)</span>
|
||||||
|
<span>{{ showData ? '收起' : '展开' }}</span>
|
||||||
|
</div>
|
||||||
|
<transition name="fade">
|
||||||
|
<pre v-if="showData" class="code-block">{{ JSON.stringify(testResult.data, null, 2) }}</pre>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<div class="collapsible">
|
||||||
|
<div class="collapsible-header" @click="showHeaders = !showHeaders">
|
||||||
|
<span>响应头 (Headers)</span>
|
||||||
|
<span>{{ showHeaders ? '收起' : '展开' }}</span>
|
||||||
|
</div>
|
||||||
|
<transition name="fade">
|
||||||
|
<pre v-if="showHeaders" class="code-block">{{ JSON.stringify(testResult.headers, null, 2) }}</pre>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import Card from '@/components/Card.vue';
|
||||||
|
import { ingestionService } from '@/api';
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const syncing = ref(false);
|
||||||
|
const testing = ref(false);
|
||||||
|
const testResult = ref<any>(null);
|
||||||
|
const showData = ref(true);
|
||||||
|
const showHeaders = ref(false);
|
||||||
|
|
||||||
|
const syncForm = reactive({
|
||||||
|
category: '',
|
||||||
|
limit: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
const testForm = reactive({
|
||||||
|
endpoint: '',
|
||||||
|
method: 'GET',
|
||||||
|
paramsStr: '{}',
|
||||||
|
headers: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSync = async () => {
|
||||||
|
syncing.value = true;
|
||||||
|
try {
|
||||||
|
await ingestionService.syncRapidApi(syncForm);
|
||||||
|
appStore.notify('同步任务已启动', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('同步失败', 'error');
|
||||||
|
} finally {
|
||||||
|
syncing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTest = async () => {
|
||||||
|
testing.value = true;
|
||||||
|
try {
|
||||||
|
const params = JSON.parse(testForm.paramsStr);
|
||||||
|
const res = await ingestionService.testRapidApi({
|
||||||
|
...testForm,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
testResult.value = res;
|
||||||
|
appStore.notify('测试完成', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('测试请求失败', 'error');
|
||||||
|
} finally {
|
||||||
|
testing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rapidapi-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label { font-size: 0.85rem; font-weight: 600; }
|
||||||
|
|
||||||
|
.form-group input, .form-group select, .form-group textarea {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row { display: grid; grid-template-columns: 1fr; gap: 16px; }
|
||||||
|
|
||||||
|
.btn-primary, .btn-accent {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary { background: var(--accent-primary); }
|
||||||
|
.btn-accent { background: #10b981; }
|
||||||
|
|
||||||
|
.result-card { margin-top: 24px; }
|
||||||
|
|
||||||
|
.result-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-code {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-code.success { background: #10b981; }
|
||||||
|
|
||||||
|
.time { font-size: 0.85rem; color: var(--text-secondary); }
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
padding: 16px;
|
||||||
|
background: #1e293b;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.view-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tools-view">
|
||||||
|
<div class="view-header">
|
||||||
|
<div class="search-bar">
|
||||||
|
<input v-model="query.category" placeholder="按分类搜索..." @keyup.enter="fetchTools" />
|
||||||
|
<button class="btn-primary" @click="fetchTools">查询</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn-accent" @click="showCreate = true">生成新工具</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="skeleton-grid">
|
||||||
|
<div v-for="i in 6" :key="i" class="skeleton card-skeleton"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition-group name="list" tag="div" class="tools-grid" v-else>
|
||||||
|
<Card v-for="tool in tools" :key="tool.name" :title="tool.name" hoverable>
|
||||||
|
<template #header>
|
||||||
|
<div class="tool-header">
|
||||||
|
<span class="method-badge" :class="tool.method">{{ tool.method }}</span>
|
||||||
|
<h3 class="tool-name">{{ tool.name }}</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<p class="tool-desc">{{ tool.description }}</p>
|
||||||
|
<div class="tool-meta">
|
||||||
|
<span class="cat-tag">{{ tool.category }}</span>
|
||||||
|
<span class="date">{{ formatDate(tool.created_at) }}</span>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="tool-actions">
|
||||||
|
<button class="btn-text" @click="viewDetail(tool)">详情</button>
|
||||||
|
<button class="btn-text danger" @click="confirmDelete(tool)">删除</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</transition-group>
|
||||||
|
|
||||||
|
<!-- Create Tool Modal -->
|
||||||
|
<div v-if="showCreate" class="modal-overlay" @click.self="showCreate = false">
|
||||||
|
<Card title="生成工具定义" class="modal-card">
|
||||||
|
<form @submit.prevent="handleCreate" class="tool-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>名称</label>
|
||||||
|
<input v-model="form.name" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>URL</label>
|
||||||
|
<input v-model="form.url" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>方法</label>
|
||||||
|
<select v-model="form.method">
|
||||||
|
<option>GET</option>
|
||||||
|
<option>POST</option>
|
||||||
|
<option>PUT</option>
|
||||||
|
<option>DELETE</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>分类</label>
|
||||||
|
<input v-model="form.category" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>描述</label>
|
||||||
|
<textarea v-model="form.description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button type="button" class="btn-secondary" @click="showCreate = false">取消</button>
|
||||||
|
<button type="submit" class="btn-primary" :disabled="submitting">
|
||||||
|
{{ submitting ? '生成中...' : '提交生成' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import Card from '@/components/Card.vue';
|
||||||
|
import { ingestionService } from '@/api';
|
||||||
|
import { useAppStore } from '@/stores/app';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const tools = ref<any[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const showCreate = ref(false);
|
||||||
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
const query = reactive({
|
||||||
|
category: '',
|
||||||
|
limit: 20,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
method: 'GET',
|
||||||
|
description: '',
|
||||||
|
category: 'general',
|
||||||
|
parameters: [],
|
||||||
|
headers: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchTools = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
tools.value = await ingestionService.getTools(query);
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('获取工具列表失败', 'error');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = async () => {
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
await ingestionService.generateTool(form);
|
||||||
|
appStore.notify('工具生成任务已启动', 'success');
|
||||||
|
showCreate.value = false;
|
||||||
|
fetchTools();
|
||||||
|
} catch (e) {
|
||||||
|
appStore.notify('生成失败', 'error');
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async (tool: any) => {
|
||||||
|
if (confirm(`确定要删除工具 ${tool.name} 吗?`)) {
|
||||||
|
const originalTools = [...tools.value];
|
||||||
|
tools.value = tools.value.filter(t => t.name !== tool.name);
|
||||||
|
try {
|
||||||
|
await ingestionService.deleteTool(tool.name);
|
||||||
|
appStore.notify('工具已删除', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
tools.value = originalTools;
|
||||||
|
appStore.notify('删除失败', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewDetail = (tool: any) => {
|
||||||
|
// Implementation for detail view
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateStr: string) => {
|
||||||
|
return new Date(dateStr).toLocaleDateString();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchTools);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tools-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar input {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-badge.GET { background: #3b82f6; }
|
||||||
|
.method-badge.POST { background: #10b981; }
|
||||||
|
.method-badge.DELETE { background: #ef4444; }
|
||||||
|
|
||||||
|
.tool-desc {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-tag {
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text.danger { color: #ef4444; }
|
||||||
|
|
||||||
|
/* Modal Styles */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 100;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input, .form-group select, .form-group textarea {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary, .btn-accent, .btn-secondary {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary { background: var(--accent-primary); color: white; }
|
||||||
|
.btn-accent { background: #10b981; color: white; }
|
||||||
|
.btn-secondary { background: var(--border-color); color: var(--text-primary); }
|
||||||
|
|
||||||
|
.skeleton-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-skeleton {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
全球化智力互联:Agent 赋能平台全栈工程架构与实施白皮书
|
||||||
|
人工智能代理(AI Agents)正迅速从实验性的脚本演变为具备自主决策能力、工具调用能力以及多代理协同能力的生产力单元。随着模型能力的增强,企业对 Agent 的需求已不再满足于简单的聊天接口,而是要求构建一种能够整合异构数据、灵活切换底层模型、支持跨框架互操作并具备透明计费体系的工程化平台。本报告旨在详细阐述一个全栈 Agent 赋能平台的五个核心技术平面,探讨其如何通过模型上下文协议(MCP)、代理间通信协议(A2A)以及执行单元(EU)计费模型,构建一个标准化的智力资源分发与治理体系。
|
||||||
|
第一平面:全域数据接入与工具化治理
|
||||||
|
在 Agent 生态系统中,数据不仅是静态的背景知识,更是可被感知的环境和可被操作的工具。第一平面的核心目标是将零散、异构的外部 API 资源转化为 Agent 可理解、可调用的标准“工具集”。
|
||||||
|
数据接入的多样性与 RapidAPI 生态集成
|
||||||
|
平台将 RapidAPI 作为核心数据供应源,利用其超过 16,000 个 API 的庞大市场,为 Agent 提供涵盖气象、金融、物流、社交媒体等全领域的行动能力 1。传统的 REST API 接入通常面临文档不规范、参数描述模糊以及身份验证流程复杂等问题,这直接限制了大型语言模型(LLM)对工具的理解能力。为解决这一痛点,平台引入了“LLM-Ready API”转换机制。
|
||||||
|
通过集成 Nokia API Hub 的技术理念,平台能够为每个 API 端点自动生成专用的工具模式(Tool-per-endpoint Schema),并将其托管在专用的 MCP 主机上 2。这种方式允许 Agent 开发者通过单一的 Rapid 应用密钥(x-rapidapi-key)管理所有订阅的 API,极大地简化了身份验证逻辑 2。
|
||||||
|
模式提取与语义增强技术
|
||||||
|
为了使 API 端点能够被 LLM 准确调用,平台采用了基于 APILLAMA 的结构化知识提取技术。APILLAMA 利用经过微调的 Llama-3-8B-Instruct 模型,通过软提示(Soft Prompt)技术,将原始的 API 文档转换为符合 Pydantic 或 JSON Schema 规范的结构化定义 1。相比于传统的通用模型,APILLAMA 在提取端点描述和参数约束方面表现出更高的准确性,有效避免了 Agent 在参数构造时的“幻觉”现象 1。
|
||||||
|
在工程实践中,平台要求 API 的 OpenAPI 规范必须包含详尽的自然语言提示。研究表明,LLM 极度依赖操作摘要(Summary)和描述(Description)字段来理解端点的意图 3。例如,将一个简单的 submit request 摘要替换为 为客户创建新的技术支持工单,能显著提升 Agent 在动态动作映射中的识别成功率 3。
|
||||||
|
异构数据源的动态管理
|
||||||
|
除了 RapidAPI,平台还兼顾了企业内部私有数据和其他第三方 SaaS 接口。通过 FastMCP 等工具,平台能够直接加载本地的 OpenAPI (Swagger) 定义文件,并实时生成 MCP 服务器代码 4。这种动态热加载机制允许平台在不中断服务的情况下,对 API 版本进行更新或对路由映射(Route Maps)进行自定义调整 3。
|
||||||
|
下表展示了平台在数据接入平面中对不同 API 类型的处理策略:
|
||||||
|
|
||||||
|
特性
|
||||||
|
RapidAPI 集成
|
||||||
|
自定义/私有 API
|
||||||
|
传统库函数封装
|
||||||
|
接入方式
|
||||||
|
统一 API Key 代理 2
|
||||||
|
OpenAPI/Swagger 导入 4
|
||||||
|
Python 函数装饰器 (@tool) 5
|
||||||
|
元数据生成
|
||||||
|
自动映射 + 人工微调
|
||||||
|
APILLAMA 结构化提取 1
|
||||||
|
源代码注释 (Docstrings) 6
|
||||||
|
安全性
|
||||||
|
平台侧密钥托管
|
||||||
|
OAuth2/mTLS 穿透 7
|
||||||
|
沙箱化运行环境 8
|
||||||
|
计费采集
|
||||||
|
API 调用成本折算 EU
|
||||||
|
运行时资源消耗统计
|
||||||
|
内部配额管理
|
||||||
|
|
||||||
|
第二平面:模型抽象层与动态治理体系
|
||||||
|
模型 management 层是平台的“大脑指挥部”,负责处理模型能力、成本与可用性之间的复杂平衡。该平面通过统一的模型抽象接口,实现了对底层 LLM 服务商的屏蔽,支持按需实时更换模型。
|
||||||
|
统一网关与模型治理架构
|
||||||
|
平台集成了 LiteLLM 作为核心的模型代理网关。LiteLLM 充当了应用程序与超过 100 个模型 API 之间的翻译层,提供了一个 OpenAI 兼容的统一端点 9。这种架构允许开发者在不修改业务代码的前提下,通过 YAML 配置文件灵活定义模型组(Model Groups) 11。
|
||||||
|
在治理维度上,平台通过 LiteLLM Proxy 实现了复杂的路由、负载均衡和故障转移机制。当主模型服务商(如 OpenAI)发生中断或触发频率限制时,系统会自动将请求切换至备份服务商(如 Anthropic 或自建的本地模型服务) 10。这种高可用性设计对于生产级的 Agent 应用至关重要。
|
||||||
|
模型性能监控与成本归因
|
||||||
|
为了实现精确的计费和性能评估,平台在模型管理层嵌入了全链路追踪(Tracing)功能。利用 LangSmith 或 OpenTelemetry 集成,平台可以记录每一次模型调用的延迟、输入输出长度以及模型特定的元数据 13。
|
||||||
|
模型治理的关键在于“上下文窗口管理”。平台能够自动检测不同模型的上下文限制,并根据任务需求自动执行会话截断或总结逻辑,确保 Agent 不会因超出 token 限制而失败 8。此外,通过集成 OpenRouter 等第三方聚合服务,平台可以进一步降低初创团队的账户管理复杂度,实现“一票制”结算 10。
|
||||||
|
模型选择的维度对比
|
||||||
|
下表分析了平台支持的主要模型类别的适用场景:
|
||||||
|
|
||||||
|
模型类别
|
||||||
|
典型代表
|
||||||
|
核心优势
|
||||||
|
缺点
|
||||||
|
平台应用策略
|
||||||
|
通用闭源大模型
|
||||||
|
GPT-4o, Claude 3.5
|
||||||
|
推理能力极强,支持复杂工具调用 14
|
||||||
|
成本高,隐私风险
|
||||||
|
用于复杂业务编排和最终决策 17
|
||||||
|
垂直领域模型
|
||||||
|
Granite, Llama-3
|
||||||
|
特定任务(如代码编写、API 提取)效率高 1
|
||||||
|
通用性较弱
|
||||||
|
用于数据预处理和结构化信息提取 1
|
||||||
|
端侧/本地模型
|
||||||
|
Ollama, LM Studio
|
||||||
|
低延迟,数据主权隔离 12
|
||||||
|
依赖硬件,能力受限
|
||||||
|
用于轻量级反思和敏感数据脱敏
|
||||||
|
|
||||||
|
第三平面:单体 Agent 的协议化封装与自治
|
||||||
|
每一个单体 Agent 被定义为一个具有明确边界、特定技能且符合行业标准协议的功能单元。平台强调单体 Agent 内部逻辑的极简性,将复杂的编排交由用户在本地完成。
|
||||||
|
极简逻辑与原子化设计
|
||||||
|
平台定义的单体 Agent 通常只包含其角色描述(Role)、目标(Goal)和一系列被授权访问的工具(Tools) 8。这种原子化设计使得每个 Agent 能够专注于特定的专业任务,如“需求分类代理”、“账单处理代理”或“紧急情况检测代理” 17。
|
||||||
|
核心通信协议:MCP、API 与 A2A
|
||||||
|
平台的核心竞争力在于其对多种 Agent 通信协议的全面支持,确保了 Agent 能够在不同的宿主环境中自由运行。
|
||||||
|
1. 模型上下文协议 (MCP)
|
||||||
|
MCP 由 Anthropic 推出,旨在解决 Agent 与工具之间硬编码集成的难题 7。在平台中,单体 Agent 既可以作为 MCP Client 访问外部数据源,也可以被封装为 MCP Server 暴露给 IDE(如 Cursor)或其他 AI 应用 19。
|
||||||
|
传输层支持:平台支持基于 stdio 的本地快速通信和基于服务器发送事件(SSE)的远程流式传输 7。
|
||||||
|
资源与工具发现:MCP 允许宿主应用动态列出、调用和观察 Agent 暴露的所有能力,实现真正的“即插即用” 20。
|
||||||
|
2. 代理间通信协议 (A2A)
|
||||||
|
A2A 协议由 Google 引入并捐赠给 Linux 基金会,它关注于代理之间的协作和任务委派 22。
|
||||||
|
Agent Card(代理名片):平台为每个 Agent 生成一个 JSON 格式的“代理卡”,描述其技能(Skills)、端点(Endpoints)和身份验证要求 22。这使得 Agent 能够像在社交网络中一样彼此发现。
|
||||||
|
任务生命周期管理:A2A 标准化了任务(Task)的提交、轮询、订阅和结果返回流程。任务状态包括“已提交”、“运行中”、“需要输入”、“已完成”等 22。
|
||||||
|
工件(Artifacts)交换:Agent 协同产生的结果(如生成的代码文件或分析报告)通过标准化的工件格式进行传递,支持多部分(Parts)数据的流式处理 22。
|
||||||
|
本地化运行与 API 暴露
|
||||||
|
对于开发者,单体 Agent 接口提供了标准的 REST API。这种设计确保了 Agent 可以轻松集成到传统的 Web 应用中,同时也支持异步轮询和基于 SSE 的实时状态更新,以优化长耗时任务的用户体验 15。
|
||||||
|
第四平面:以 MCP 为核心的本地编排与集成平面
|
||||||
|
在 Agent 赋能平台的工程化实践中,第四平面的核心逻辑已从传统的框架绑定转向“MCP-First”集成策略。通过将平台提供的子 Agent 服务封装为标准的 MCP 服务器,用户可以在本地利用日益成熟的 MCP 生态系统进行灵活编排。
|
||||||
|
MCP 作为本地集成的“通用语言”
|
||||||
|
MCP 已成为连接 AI 模型与外部工具、数据的行业标准协议 7。平台将每个单体 Agent 及其背后的数据接口抽象为 MCP 节点,支持用户在本地环境通过 stdio(用于本地进程间通信)或 SSE(用于远程流式服务)进行无缝接入。
|
||||||
|
这种“协议优先”的设计避免了为每个开源框架编写专用插件的重复劳动。当一个 Agent 符合 MCP 规范时,它不仅能被开发者的业务逻辑调用,还能直接在 IDE(如 Cursor)、聊天客户端(如 Claude Desktop)以及各类企业级 Agent 后台中作为原生工具使用。
|
||||||
|
主流 Agent 框架对 MCP 的深度支持
|
||||||
|
目前市面上主流的 Agent 开发框架均已实现了对 MCP 的原生或适配器支持,这使得本地编排变得异常简单:
|
||||||
|
LangChain 适配方案:LangChain 通过 langchain-mcp-adapters 库,能够将 MCP 服务器定义的 Tools、Resources 和 Prompts 自动转换为 LangChain 兼容的组件。开发者只需初始化一个 MultiServerMCPClient,即可同时加载分布在不同本地路径或远程 URL 的 Agent 技能。
|
||||||
|
CrewAI 集成机制:CrewAI 在 Agent 类中直接提供了 mcps 字段。开发者只需提供 MCP 服务器的端点信息(如 SSE URL 或特定的 Stdio 启动命令),框架即可自动发现工具并注入到代理的执行上下文中。
|
||||||
|
Microsoft AutoGen 扩展:AutoGen v0.4+ 版本引入了 StdioMcpToolAdapter 和 SseMcpToolAdapter,允许将外部 MCP 协议包装为 AutoGen 代理可识别的 Action 单元,极大地增强了跨语言和跨环境的协作能力。
|
||||||
|
协议化集成的工程优势
|
||||||
|
解耦与复用:工具逻辑在 MCP 服务器端维护,编排逻辑在本地维护,双方通过 JSON-RPC 2.0 契约通信,任何一方的升级都不会导致系统崩溃。
|
||||||
|
动态发现机制:本地编排器可以在运行时通过 tools/list 请求动态发现 Agent 的新技能,无需手动更新本地代码中的工具定义。
|
||||||
|
安全隔离:用户可以在本地沙箱中运行高风险的 MCP 工具(如文件操作系统),而将复杂的逻辑计算卸载到云端平台,确保数据主权与执行安全的平衡。
|
||||||
|
下表展示了以 MCP 为核心的集成链路:
|
||||||
|
|
||||||
|
组件层级
|
||||||
|
实现方式
|
||||||
|
技术标准
|
||||||
|
能力供应方
|
||||||
|
平台 Agent 导出为 MCP Server 2
|
||||||
|
JSON-RPC 2.0 / stdio / SSE 7
|
||||||
|
连接层适配
|
||||||
|
MCPServerAdapter / MultiServerMCPClient
|
||||||
|
MCP SDK (Python/TS/Go) 43
|
||||||
|
业务逻辑层
|
||||||
|
用户本地 Python/JS 逻辑或 DSL 配置
|
||||||
|
OpenAPI 3.0 / Pydantic
|
||||||
|
宿主框架
|
||||||
|
LangGraph, CrewAI, AutoGen, Cursor
|
||||||
|
框架原生接口 + MCP Adapter
|
||||||
|
|
||||||
|
第五平面:基于执行单元 (EU) 的资源计量与计费模型
|
||||||
|
为了解决 Token 计费模型在 Agent 场景下的不确定性(如 Agent 的过度反思或重复调用导致的 Token 激增),平台引入了基于执行时间与资源消耗的**执行单元(Execution Unit, EU)**计费模式。
|
||||||
|
执行单元 (EU) 的定义与公式
|
||||||
|
执行单元(EU)是一个衡量计算、内存和网络资源消耗的综合性指标。该模型参考了 AWS Lambda、Google Cloud Run 以及 LUMI 超算中心的计费实践 26。
|
||||||
|
一个典型的 EU 计算公式如下:
|
||||||
|
|
||||||
|
$$EU = \left( \max\left( \lceil \frac{vCPU_{Allocated}}{Step_{CPU}} \rceil, \lceil \frac{Memory_{Allocated}}{Step_{Mem}} \rceil \right) \times T_{Runtime} \right) \times \gamma_{Model\_Tax}$$
|
||||||
|
其中:
|
||||||
|
$vCPU_{Allocated}$:分配给该 Agent 任务的虚拟处理器核数 28。
|
||||||
|
$Memory_{Allocated}$:分配的内存容量(例如以 2GB 为一个计费切片) 27。
|
||||||
|
$T_{Runtime}$:任务实际执行的 wall-clock 时间(通常精确到毫秒级) 26。
|
||||||
|
$\gamma_{Model\_Tax}$:模型权重因子,反映了底层调用特定高级模型(如 GPT-4o)时的额外许可溢价。
|
||||||
|
计费引擎的架构实施
|
||||||
|
计费平面的核心是实时计量引擎。该引擎通过以下步骤确保收入不流失并提供透明的客户账单:
|
||||||
|
事件采集:利用 Golang 编写的高性能计量服务,通过 NATS 消息队列监听 Agent 的启动(Start)和停止(Stop)事件 31。
|
||||||
|
配额管理:系统在任务启动前预扣除一定额度的 EU。如果账户余额低于阈值,则拒绝启动,防止欠费运行 32。
|
||||||
|
动态调优:通过 Datadog 或 Prometheus 监控函数调用的内存峰值,建议用户“右调”资源分配,以在性能与成本间取得最优解 34。
|
||||||
|
实时仪表盘:为客户提供可视化看板,展示按 Agent、按特征、按环境划分的成本明细,并利用 AI 预测未来的支出趋势 32。
|
||||||
|
EU 计费与 Token 计费的对比分析
|
||||||
|
|
||||||
|
维度
|
||||||
|
Token 计费模型
|
||||||
|
执行单元 (EU) 计费模型
|
||||||
|
透明度
|
||||||
|
较低。用户难以理解隐藏的推理链 Token 消耗 32
|
||||||
|
较高。类似于云计算实例,运行多久付多久费 34
|
||||||
|
激励方向
|
||||||
|
鼓励生成短文本。可能损害 Agent 的推理深度
|
||||||
|
鼓励代码和算法优化。更短的运行时间意味着更低的费用 35
|
||||||
|
架构适配性
|
||||||
|
仅适用于单次 API 调用
|
||||||
|
完美契合 Serverless 函数和长时运行的自治任务 38
|
||||||
|
成本管控
|
||||||
|
难以实时熔断。可能在分钟内产生巨大账单
|
||||||
|
易于实施基于时间配额的强制停机逻辑 33
|
||||||
|
|
||||||
|
工程化平台治理:安全、隔离与多租户
|
||||||
|
作为一个赋能平台,确保多租户环境下的数据隔离和系统稳定性是商业化落地的先决条件。
|
||||||
|
多层级隔离机制
|
||||||
|
平台在计算、数据和网络三个层级实施了严格的隔离策略:
|
||||||
|
计算隔离:利用微虚拟机(MicroVMs,如 Firecracker)或受限容器(gVisor)运行 Agent 逻辑。通过设置硬性的 CPU 和内存 Limit,防止“吵闹邻居”(Noisy Neighbor)效应影响其他租户 40。
|
||||||
|
数据隔离:数据库采用行级安全性(RLS)和按租户加密(Per-tenant Encryption)。所有的 SQL 查询都必须带上 TenantID 过滤器 40。
|
||||||
|
网络隔离:为敏感的 Agent 编排提供虚拟私有云(VPC)和私有子网,限制对后端数据库和凭据存储的非授权访问 42。
|
||||||
|
身份验证与权限管控 (RBAC/ABAC)
|
||||||
|
平台集成了 Pomerium 作为智能访问关口。与传统的基于 API Key 的简单验证不同,Pomerium 能够集成 Okta 等身份提供商,实施基于上下文的访问策略(例如:仅允许特定部门的成员在工作时间内调用具有财务权限的 Agent) 9。
|
||||||
|
运行时监控与审计
|
||||||
|
全方位的观测能力对于 Agent 调试至关重要。平台在每个 Agent 的运行环境中注入了 Sidecar 代理,实时采集:
|
||||||
|
性能指标:CPU 利用率、内存驻留集大小、网络延迟。
|
||||||
|
Agent 轨迹:记录所有的工具调用请求和模型推理过程,生成可交互的轨迹图(Traces) 37。
|
||||||
|
合规审计:保存完整的对话日志和任务工件,以满足 SOC 2 或 HIPAA 等合规性要求 9。
|
||||||
|
结论:构建 Agentic Web 的基础设施
|
||||||
|
本报告详述的 Agent 赋能平台,不仅是一个简单的工具集,更是一个旨在标准化未来“智力交换”的基础设施。通过第一平面对 RapidAPI 等海量资源的工具化封装,平台解决了数据获取的广度问题;通过第二平面的多模型动态切换,平台保障了大脑的可替代性与成本可控性。
|
||||||
|
在协议层面,MCP 与 A2A 的深度整合,标志着平台从封闭系统向开放生态的转变。以 MCP 为核心的第四平面设计,使得平台 Agent 能够以标准插件的形式瞬间触达全球主流开发框架和 IDE。 这种原子化设计结合本地编排的灵活性,赋予了用户构建复杂业务逻辑的主动权。而基于 EU 的计费模型,则为 Agent 这一新型生产力单元提供了最符合工程直觉的价值衡量尺度。
|
||||||
|
随着 Agent 技术的不断演进,平台未来的研究重点将转向:
|
||||||
|
自治成本优化:开发能够自主选择最经济路径(模型 + 工具组合)的元调度器。
|
||||||
|
跨代理信誉体系:基于任务完成率和资源效率,为 A2A 生态中的代理建立信任评分。
|
||||||
|
异构计算卸载:根据 Agent 的计算强度,自动在端侧设备与云端高性能集群之间动态分配任务载荷。
|
||||||
|
通过实施这五个维度的技术标准,该平台将为企业提供一个稳健、透明且易于扩展的 Agent 运行环境,助力从“模型优先”时代平稳过渡到“代理优先”时代。
|
||||||
|
引用的著作
|
||||||
|
ToolFactory: Automating Tool Generation by Leveraging LLM to Understand REST API Documentations - arXiv, 访问时间为 十二月 20, 2025, https://arxiv.org/html/2501.16945v1
|
||||||
|
Consume APIs using AI - RapidAPI, 访问时间为 十二月 20, 2025, https://docs.rapidapi.com/docs/consume-apis-using-ai
|
||||||
|
Automate AI Workflows with OpenAPI to Build LLM-Ready APIs - Gravitee, 访问时间为 十二月 20, 2025, https://www.gravitee.io/blog/ai-workflows-with-openapi-and-llm-apis
|
||||||
|
How to Connect an LLM to a REST API - FastMCP, 访问时间为 十二月 20, 2025, https://gofastmcp.com/tutorials/rest-api
|
||||||
|
Tools - CrewAI Documentation, 访问时间为 十二月 20, 2025, https://docs.crewai.com/en/concepts/tools
|
||||||
|
12 Best Technical Documentation Templates for 2025 | DocuWriter.ai, 访问时间为 十二月 20, 2025, https://www.docuwriter.ai/posts/technical-documentation-templates
|
||||||
|
What is Model Context Protocol (MCP)? A guide - Google Cloud, 访问时间为 十二月 20, 2025, https://cloud.google.com/discover/what-is-model-context-protocol
|
||||||
|
Agents - CrewAI Documentation, 访问时间为 十二月 20, 2025, https://docs.crewai.com/en/concepts/agents
|
||||||
|
LiteLLM vs. Pomerium: Key Differences and When to Use Each One, 访问时间为 十二月 20, 2025, https://www.pomerium.com/blog/litellm-vs-pomerium
|
||||||
|
LiteLLM: A Guide With Practical Examples - DataCamp, 访问时间为 十二月 20, 2025, https://www.datacamp.com/tutorial/litellm
|
||||||
|
How Model Access Works - LiteLLM, 访问时间为 十二月 20, 2025, https://docs.litellm.ai/docs/proxy/model_access_guide
|
||||||
|
Olla vs LiteLLM - Comparison Guide for LLM Infrastructure, 访问时间为 十二月 20, 2025, https://thushan.github.io/olla/compare/litellm/
|
||||||
|
Trace with AutoGen - Docs by LangChain, 访问时间为 十二月 20, 2025, https://docs.langchain.com/langsmith/trace-with-autogen
|
||||||
|
How to integrate LangGraph with AutoGen, CrewAI, and other frameworks - LangChain docs, 访问时间为 十二月 20, 2025, https://docs.langchain.com/langsmith/autogen-integration
|
||||||
|
Designing APIs for LLM Apps: Build Scalable and AI-Ready Interfaces - Gravitee, 访问时间为 十二月 20, 2025, https://www.gravitee.io/blog/designing-apis-for-llm-apps
|
||||||
|
Why are we still pretending multi-model abstraction layers work? : r/LLMDevs - Reddit, 访问时间为 十二月 20, 2025, https://www.reddit.com/r/LLMDevs/comments/1owtio8/why_are_we_still_pretending_multimodel/
|
||||||
|
How to Build a Multi-Agent System (Part 1/3): From Problem to Design, 访问时间为 十二月 20, 2025, https://www.intotheagileshop.com/post/how-to-build-a-multi-agent-system-part-1-3-from-problem-to-design
|
||||||
|
How to Build Your Own Agentic AI System Using CrewAI | Towards Data Science, 访问时间为 十二月 20, 2025, https://towardsdatascience.com/how-to-build-your-own-agentic-ai-system-using-crewai/
|
||||||
|
Model Context Protocol (MCP) | Cursor Docs, 访问时间为 十二月 20, 2025, https://cursor.com/docs/context/mcp
|
||||||
|
Build Your Own Model Context Protocol Server | by C. L. Beard | BrainScriblr | Nov, 2025, 访问时间为 十二月 20, 2025, https://medium.com/brainscriblr/build-your-own-model-context-protocol-server-0207625472d0
|
||||||
|
Tools - Model Context Protocol, 访问时间为 十二月 20, 2025, https://modelcontextprotocol.io/docs/concepts/tools
|
||||||
|
What is A2A protocol (Agent2Agent)? - IBM, 访问时间为 十二月 20, 2025, https://www.ibm.com/think/topics/agent2agent-protocol
|
||||||
|
A2A Protocol, 访问时间为 十二月 20, 2025, https://a2a-protocol.org/latest/
|
||||||
|
Why Agent2Agent Matters for Multi-Agent Systems? | by Ricardo Olivieri | IBM IT Automation and AI | Dec, 2025, 访问时间为 十二月 20, 2025, https://medium.com/ibm-watson-aiops/why-agent2agent-matters-for-multi-agent-systems-45c070fdd1b9
|
||||||
|
What is Serverless Architecture? A Practical Guide with Examples - Middleware.io, 访问时间为 十二月 20, 2025, https://middleware.io/blog/serverless-architecture/
|
||||||
|
Demystifying Serverless Costs on Public Platforms: Bridging Billing, Architecture, and OS Scheduling - arXiv, 访问时间为 十二月 20, 2025, https://arxiv.org/html/2506.01283v2
|
||||||
|
Billing policy - Documentation - LUMI, 访问时间为 十二月 20, 2025, https://docs.lumi-supercomputer.eu/runjobs/lumi_env/billing/
|
||||||
|
Can anyone explain to me in simple terms what vCPU means? I have been scratching my head over this. : r/golang - Reddit, 访问时间为 十二月 20, 2025, https://www.reddit.com/r/golang/comments/1irz945/can_anyone_explain_to_me_in_simple_terms_what/
|
||||||
|
Memory and vCPU considerations for AWS Batch on Amazon EKS, 访问时间为 十二月 20, 2025, https://docs.aws.amazon.com/batch/latest/userguide/memory-cpu-batch-eks.html
|
||||||
|
Getting to the Bottom of Serverless Billing - arXiv, 访问时间为 十二月 20, 2025, https://arxiv.org/html/2506.01283v1
|
||||||
|
How to Implement Scalable Usage-Based Billing for AI Workloads - CloudRaft, 访问时间为 十二月 20, 2025, https://www.cloudraft.io/blog/usage-based-billing-for-ai-workloads
|
||||||
|
How to Build Custom Billing Systems for AI Agents: A Complete Guide, 访问时间为 十二月 20, 2025, https://www.getmonetizely.com/articles/how-to-build-custom-billing-systems-for-ai-agents-a-complete-guide
|
||||||
|
AI Billing Showdown: 6 Billing Platforms for AI Agents | Paid.ai blog, 访问时间为 十二月 20, 2025, https://paid.ai/blog/billing/ai-billing-showdown-6-billing-platforms
|
||||||
|
Minimizing Development Costs with Serverless Architecture - IntexSoft, 访问时间为 十二月 20, 2025, https://intexsoft.com/blog/minimizing-development-costs-with-serverless-architecture/
|
||||||
|
Impact of Serverless Architecture on Software Development Costs | Zetaton, 访问时间为 十二月 20, 2025, https://www.zetaton.com/blogs/the-impact-of-serverless-architecture-on-software-development-costs
|
||||||
|
Automated Billing Software Development: A Step-by-Step Guide - Appinventiv, 访问时间为 十二月 20, 2025, https://appinventiv.com/blog/automated-billing-software-development/
|
||||||
|
AI Agent Costs on Databricks: A Complete Guide to Pricing, Optimization, and Real-World Examples, 访问时间为 十二月 20, 2025, https://community.databricks.com/t5/technical-blog/demystifying-databricks-pricing-for-ai-agents/ba-p/122281
|
||||||
|
Top 5 Things to Know Before Using Serverless Computing - CloudOptimo, 访问时间为 十二月 20, 2025, https://www.cloudoptimo.com/blog/top-5-things-to-know-before-using-serverless-computing/
|
||||||
|
Serverless Architecture: What It Is & How It Works | Datadog, 访问时间为 十二月 20, 2025, https://www.datadoghq.com/knowledge-center/serverless-architecture/
|
||||||
|
Real-Time Monitoring for Multi-Tenant Workflows | Prompts.ai, 访问时间为 十二月 20, 2025, https://www.prompts.ai/en/blog/real-time-monitoring-for-multi-tenant-workflows
|
||||||
|
Multi-Tenant Architecture: The Complete Guide for Modern SaaS and Analytics Platforms -, 访问时间为 十二月 20, 2025, https://bix-tech.com/multi-tenant-architecture-the-complete-guide-for-modern-saas-and-analytics-platforms-2/
|
||||||
|
Building Multi-Tenant n8n Workflows for Agency Clients, 访问时间为 十二月 20, 2025, https://www.wednesday.is/writing-articles/building-multi-tenant-n8n-workflows-for-agency-clients
|
||||||
|
Model Context Protocol - GitHub, 访问时间为 十二月 20, 2025, https://github.com/modelcontextprotocol
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
proxy: {
|
||||||
|
'/api-ingestion': {
|
||||||
|
target: 'http://localhost:8001',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api-ingestion/, ''),
|
||||||
|
},
|
||||||
|
'/api-mcp': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api-mcp/, ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user