feat: 重构前后端架构,添加Go后端和Python Agent服务
- 新增 Go 语言后端服务(server/),包含用户认证、Agent管理、数据库连接等API - 新增 Python Agent 服务(agent/),实现Agent核心逻辑和工具集 - 前端从原生HTML迁移到Vue.js框架(web/src/) - 添加 Docker Compose 支持(docker-compose.yml) - 添加项目架构文档(docs/ARCHITECTURE.md) - 添加环境变量示例(.env.example)和本地启动脚本(start-local.ps1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
835
docs/ARCHITECTURE.md
Normal file
835
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,835 @@
|
||||
# X-Agents 智能体平台架构设计
|
||||
|
||||
## 一、整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 用户层 │
|
||||
│ Web / App / API Consumer │
|
||||
└─────────────────────────────────┬───────────────────────────────────────────┘
|
||||
│ HTTP / WebSocket
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Go API Gateway │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
||||
│ │ • HTTP Server (Gin) • 认证鉴权 (JWT) ││
|
||||
│ │ • 路由管理 • 限流熔断 ││
|
||||
│ │ • 业务逻辑 • 日志监控 ││
|
||||
│ │ • 数据库操作 • 权限控制 (RBAC) ││
|
||||
│ └─────────────────────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────┬───────────────────────────────────────────┘
|
||||
│ HTTP JSON API
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Python Agent Engine │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
||||
│ │ • FastAPI Server • Agent Core (LangChain/AutoGen) ││
|
||||
│ │ • LLM Adapter • Tool Registry (白名单) ││
|
||||
│ │ • Memory Manager • Sandbox Executor (沙盒) ││
|
||||
│ │ • RAG Pipeline • Audit Logger ││
|
||||
│ └─────────────────────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、系统分层
|
||||
|
||||
### 2.1 Go 后端层 (server/)
|
||||
|
||||
```
|
||||
server/ # Go API Gateway 服务
|
||||
├── cmd/api/ # 程序入口
|
||||
│ └── main.go
|
||||
├── internal/
|
||||
│ ├── config/ # 配置管理
|
||||
│ │ └── config.go
|
||||
│ ├── handler/ # HTTP处理器
|
||||
│ │ ├── auth_handler.go # 认证接口
|
||||
│ │ ├── chat_handler.go # 聊天接口
|
||||
│ │ └── approval_handler.go # 审批接口
|
||||
│ ├── service/ # 业务逻辑
|
||||
│ │ ├── auth_service.go
|
||||
│ │ ├── chat_service.go
|
||||
│ │ └── approval_service.go
|
||||
│ ├── repository/ # 数据访问层
|
||||
│ │ ├── user_repo.go
|
||||
│ │ ├── agent_repo.go
|
||||
│ │ └── audit_repo.go
|
||||
│ ├── middleware/ # 中间件
|
||||
│ │ └── auth.go
|
||||
│ └── model/ # 数据模型
|
||||
│ ├── user.go
|
||||
│ ├── agent.go
|
||||
│ └── audit.go
|
||||
├── config/ # 配置文件
|
||||
│ └── config.yaml
|
||||
├── Dockerfile
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
### 2.2 Python Agent 层 (agent/)
|
||||
|
||||
```
|
||||
agent/ # Python Agent Engine
|
||||
├── app/
|
||||
│ ├── main.py # FastAPI入口
|
||||
│ ├── api/
|
||||
│ │ └── routes.py # API路由
|
||||
│ ├── agent/
|
||||
│ │ ├── core/
|
||||
│ │ │ ├── agent.py # Agent管理器
|
||||
│ │ │ └── executor.py # Agent执行器
|
||||
│ │ ├── tools/
|
||||
│ │ │ ├── registry.py # 工具注册表(白名单)
|
||||
│ │ │ └── impl/ # 工具实现
|
||||
│ │ │ ├── search.py
|
||||
│ │ │ ├── calculator.py
|
||||
│ │ │ └── time_tool.py
|
||||
│ │ └── memory/
|
||||
│ │ └── session.py # 会话管理
|
||||
│ ├── llm/
|
||||
│ │ └── factory.py # LLM工厂
|
||||
│ └── security/
|
||||
│ ├── audit.py # 审计日志
|
||||
│ └── approval.py # 审批服务
|
||||
├── requirements.txt
|
||||
├── Dockerfile
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
### 2.3 根目录结构
|
||||
|
||||
```
|
||||
X-Agents/
|
||||
├── server/ # Go API Gateway
|
||||
├── agent/ # Python Agent Engine
|
||||
├── web/ # 前端 (Vue.js)
|
||||
├── docs/
|
||||
│ └── ARCHITECTURE.md # 架构文档
|
||||
├── docker-compose.yml # 容器编排
|
||||
├── .env.example # 环境变量模板
|
||||
└── README.md
|
||||
│ ├── service/ # 业务逻辑
|
||||
│ │ ├── chat_service.go
|
||||
│ │ ├── agent_service.go
|
||||
│ │ └── approval_service.go # 审批服务
|
||||
│ ├── repository/ # 数据访问层
|
||||
│ │ ├── user_repo.go
|
||||
│ │ ├── agent_repo.go
|
||||
│ │ └── audit_repo.go
|
||||
│ ├── middleware/ # 中间件
|
||||
│ │ ├── auth.go # 认证中间件
|
||||
│ │ ├── rbac.go # 权限中间件
|
||||
│ │ └── audit.go # 审计中间件
|
||||
│ ├── client/ # 外部服务客户端
|
||||
│ │ └── python_client.go # Python服务HTTP客户端
|
||||
│ └── model/ # 数据模型
|
||||
│ ├── user.go
|
||||
│ ├── agent.go
|
||||
│ └── audit.go
|
||||
├── pkg/
|
||||
│ ├── utils/ # 工具函数
|
||||
│ └── errors/ # 错误定义
|
||||
└── go.mod
|
||||
```
|
||||
|
||||
### 2.2 Python AI 层 (智能逻辑)
|
||||
|
||||
```
|
||||
python/
|
||||
├── app/
|
||||
│ ├── main.py # FastAPI入口
|
||||
│ ├── api/
|
||||
│ │ ├── routes.py # 路由定义
|
||||
│ │ └── dependencies.py # 依赖注入
|
||||
│ ├── agent/
|
||||
│ │ ├── core/
|
||||
│ │ │ ├── agent.py # Agent核心
|
||||
│ │ │ ├── executor.py # 执行器
|
||||
│ │ │ └── memory.py # 记忆管理
|
||||
│ │ ├── tools/
|
||||
│ │ │ ├── registry.py # 工具注册表
|
||||
│ │ │ ├── base.py # 工具基类
|
||||
│ │ │ ├── security.py # 安全检查
|
||||
│ │ │ └── impl/ # 具体工具实现
|
||||
│ │ │ ├── search.py
|
||||
│ │ │ ├── calculator.py
|
||||
│ │ │ ├── database.py
|
||||
│ │ │ └── sandbox.py # 沙盒执行
|
||||
│ │ └── sandbox/
|
||||
│ │ ├── docker_sandbox.py
|
||||
│ │ └── wasm_sandbox.py
|
||||
│ ├── llm/
|
||||
│ │ ├── factory.py # LLM工厂
|
||||
│ │ ├── openai_adapter.py
|
||||
│ │ ├── anthropic_adapter.py
|
||||
│ │ └── base.py
|
||||
│ ├── rag/
|
||||
│ │ ├── vector_store.py # 向量存储
|
||||
│ │ ├── retriever.py # 检索器
|
||||
│ │ └── pipeline.py # RAG流程
|
||||
│ └── security/
|
||||
│ ├── permission.py # 权限检查
|
||||
│ ├── approval.py # 审批管理
|
||||
│ └── audit.py # 安全审计
|
||||
├── requirements.txt
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、通信机制
|
||||
|
||||
### 3.1 HTTP API 通信
|
||||
|
||||
```
|
||||
┌──────────────────┐ HTTP POST ┌──────────────────┐
|
||||
│ │ ─────────────▶ │ │
|
||||
│ Go Service │ JSON Request │ Python Service │
|
||||
│ (Port: 8080) │ │ (Port: 8081) │
|
||||
│ │ ◀───────────── │ │
|
||||
└──────────────────┘ JSON Response └──────────────────┘
|
||||
```
|
||||
|
||||
#### 接口设计
|
||||
|
||||
**1. Agent 聊天接口**
|
||||
|
||||
```
|
||||
POST /api/v1/agent/chat
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
|
||||
Request:
|
||||
{
|
||||
"agent_id": "agent_001",
|
||||
"message": "帮我查询用户数据",
|
||||
"session_id": "session_xxx",
|
||||
"context": {} // 额外上下文
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"reply": "查询结果...",
|
||||
"session_id": "session_xxx",
|
||||
"tools_used": ["query_database"],
|
||||
"metadata": {}
|
||||
}
|
||||
```
|
||||
|
||||
**2. 工具执行审批接口**
|
||||
|
||||
```
|
||||
POST /api/v1/tool/approve
|
||||
Request:
|
||||
{
|
||||
"request_id": "req_001",
|
||||
"tool_name": "execute_sql",
|
||||
"params": {"sql": "SELECT * FROM users"},
|
||||
"reason": "用户查询自己的订单",
|
||||
"approved": true // true=批准, false=拒绝
|
||||
}
|
||||
```
|
||||
|
||||
**3. 工具执行状态查询**
|
||||
|
||||
```
|
||||
GET /api/v1/tool/request/{request_id}
|
||||
Response:
|
||||
{
|
||||
"status": "pending|approved|rejected|completed",
|
||||
"tool_name": "execute_sql",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"result": null // 如果已完成
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Go → Python 客户端
|
||||
|
||||
```go
|
||||
// internal/client/python_client.go
|
||||
|
||||
package client
|
||||
|
||||
type PythonAgentClient struct {
|
||||
baseURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type ChatRequest struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
Message string `json:"message"`
|
||||
SessionID string `json:"session_id"`
|
||||
Context map[string]interface{} `json:"context"`
|
||||
}
|
||||
|
||||
type ChatResponse struct {
|
||||
Reply string `json:"reply"`
|
||||
SessionID string `json:"session_id"`
|
||||
ToolsUsed []string `json:"tools_used"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// Chat 调用Python Agent服务
|
||||
func (c *PythonAgentClient) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) {
|
||||
// 1. 构建请求
|
||||
// 2. 添加超时
|
||||
// 3. 发送请求
|
||||
// 4. 处理响应
|
||||
// 5. 错误处理
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、沙盒安全机制
|
||||
|
||||
### 4.1 安全架构总览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 安全控制层 │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
|
||||
│ │ 权限管理 │ │ 工具分级 │ │ 人工审批 │ │ 审计日志 │ │
|
||||
│ │ (RBAC) │ │ (白名单) │ │ (Approval) │ │ (Audit) │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └────┬─────┘ │
|
||||
└─────────┼─────────────────┼─────────────────┼─────────────────┼────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Agent 执行层 │
|
||||
│ │
|
||||
│ User Request ─▶ Permission Check ─▶ Tool Lookup ─▶ Execute ─▶ Result │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ [Need Approval] ──▶ [Pending Queue] ──▶ [Notify] │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 工具安全等级
|
||||
|
||||
```python
|
||||
# python/app/agent/tools/security.py
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Callable
|
||||
|
||||
class SecurityLevel(Enum):
|
||||
"""工具安全等级"""
|
||||
SAFE = "safe" # 安全操作:搜索、计算、读取公开数据
|
||||
REVIEW = "review" # 需要审核:修改数据、发送消息
|
||||
DANGER = "danger" # 危险操作:删除数据、执行代码、敏感API
|
||||
|
||||
@dataclass
|
||||
class ToolMetadata:
|
||||
"""工具元数据"""
|
||||
name: str
|
||||
description: str
|
||||
security_level: SecurityLevel
|
||||
require_approval: bool # 是否需要人工审批
|
||||
allowed_roles: List[str] # 允许调用的角色
|
||||
rate_limit: int # 调用频率限制
|
||||
timeout: int # 超时时间(秒)
|
||||
|
||||
class ToolSecurity:
|
||||
"""工具安全管理"""
|
||||
|
||||
# 安全等级阈值
|
||||
APPROVAL_THRESHOLD = SecurityLevel.REVIEW
|
||||
|
||||
@staticmethod
|
||||
def check_permission(tool: ToolMetadata, user_role: str) -> bool:
|
||||
"""检查用户权限"""
|
||||
if user_role in tool.allowed_roles:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def need_approval(tool: ToolMetadata) -> bool:
|
||||
"""判断是否需要审批"""
|
||||
return tool.security_level.value >= ToolSecurity.APPROVAL_THRESHOLD.value
|
||||
```
|
||||
|
||||
### 4.3 工具注册与执行
|
||||
|
||||
```python
|
||||
# python/app/agent/tools/registry.py
|
||||
|
||||
from typing import Dict, Callable, Any
|
||||
from .security import ToolMetadata, SecurityLevel
|
||||
|
||||
class ToolRegistry:
|
||||
"""工具注册表 - 白名单机制"""
|
||||
|
||||
def __init__(self):
|
||||
self._tools: Dict[str, tuple[Callable, ToolMetadata]] = {}
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
func: Callable,
|
||||
security_level: SecurityLevel = SecurityLevel.SAFE,
|
||||
require_approval: bool = False,
|
||||
allowed_roles: List[str] = None,
|
||||
description: str = ""
|
||||
):
|
||||
"""注册工具到白名单"""
|
||||
metadata = ToolMetadata(
|
||||
name=name,
|
||||
description=description,
|
||||
security_level=security_level,
|
||||
require_approval=require_approval or security_level == SecurityLevel.REVIEW,
|
||||
allowed_roles=allowed_roles or ["user", "admin"],
|
||||
rate_limit=100,
|
||||
timeout=30
|
||||
)
|
||||
self._tools[name] = (func, metadata)
|
||||
|
||||
def get_tool(self, name: str) -> tuple[Callable, ToolMetadata]:
|
||||
"""获取工具(必须在白名单中)"""
|
||||
if name not in self._tools:
|
||||
raise ValueError(f"Tool '{name}' not found in whitelist")
|
||||
return self._tools[name]
|
||||
|
||||
def list_tools(self) -> List[ToolMetadata]:
|
||||
"""列出所有可用工具"""
|
||||
return [meta for _, meta in self._tools.values()]
|
||||
```
|
||||
|
||||
### 4.4 沙盒执行
|
||||
|
||||
```python
|
||||
# python/app/agent/tools/sandbox/docker_sandbox.py
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
class DockerSandbox:
|
||||
"""Docker沙盒执行环境"""
|
||||
|
||||
def __init__(self, image: str = "python-sandbox:latest", timeout: int = 30):
|
||||
self.image = image
|
||||
self.timeout = timeout
|
||||
|
||||
def execute(self, code: str, language: str = "python") -> Dict[str, Any]:
|
||||
"""在沙盒中执行代码"""
|
||||
|
||||
# 1. 创建临时文件
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='w',
|
||||
suffix=f'.{language}',
|
||||
delete=False
|
||||
) as f:
|
||||
f.write(code)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
# 2. Docker容器执行
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker", "run",
|
||||
"--rm",
|
||||
"--network", "none", # 断网
|
||||
"--memory", "256m", # 内存限制
|
||||
"--cpus", "0.5", # CPU限制
|
||||
"-v", f"{temp_path}:/code/{os.path.basename(temp_path)}",
|
||||
self.image,
|
||||
"python", f"/code/{os.path.basename(temp_path)}"
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
return {
|
||||
"success": result.returncode == 0,
|
||||
"output": result.stdout.decode(),
|
||||
"error": result.stderr.decode()
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
"success": False,
|
||||
"output": "",
|
||||
"error": "Execution timeout"
|
||||
}
|
||||
finally:
|
||||
# 3. 清理临时文件
|
||||
os.unlink(temp_path)
|
||||
|
||||
# 使用示例
|
||||
@sandbox.execute
|
||||
def execute_code(code: str) -> str:
|
||||
"""安全执行用户代码"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 4.5 Human in the Loop (人工审批)
|
||||
|
||||
```python
|
||||
# python/app/security/approval.py
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
class ApprovalStatus(Enum):
|
||||
PENDING = "pending"
|
||||
APPROVED = "approved"
|
||||
REJECTED = "rejected"
|
||||
|
||||
@dataclass
|
||||
class ApprovalRequest:
|
||||
"""审批请求"""
|
||||
request_id: str
|
||||
tool_name: str
|
||||
params: dict
|
||||
user_id: str
|
||||
reason: str
|
||||
status: ApprovalStatus
|
||||
created_at: datetime
|
||||
reviewed_at: Optional[datetime]
|
||||
reviewed_by: Optional[str]
|
||||
|
||||
class ApprovalService:
|
||||
"""审批服务"""
|
||||
|
||||
def __init__(self, http_client):
|
||||
self.client = http_client
|
||||
self.pending: Dict[str, ApprovalRequest] = {}
|
||||
|
||||
async def request_approval(
|
||||
self,
|
||||
tool_name: str,
|
||||
params: dict,
|
||||
user_id: str,
|
||||
reason: str
|
||||
) -> str:
|
||||
"""请求审批"""
|
||||
request_id = generate_uuid()
|
||||
|
||||
approval_req = ApprovalRequest(
|
||||
request_id=request_id,
|
||||
tool_name=tool_name,
|
||||
params=params,
|
||||
user_id=user_id,
|
||||
reason=reason,
|
||||
status=ApprovalStatus.PENDING,
|
||||
created_at=datetime.now(),
|
||||
reviewed_at=None,
|
||||
reviewed_by=None
|
||||
)
|
||||
|
||||
self.pending[request_id] = approval_req
|
||||
|
||||
# 通知Go后端有新审批
|
||||
await self.notify_go_service(approval_req)
|
||||
|
||||
return request_id
|
||||
|
||||
async def wait_for_approval(self, request_id: str, timeout: int = 300) -> bool:
|
||||
"""等待审批结果"""
|
||||
start = datetime.now()
|
||||
|
||||
while (datetime.now() - start).seconds < timeout:
|
||||
if request_id in self.pending:
|
||||
status = self.pending[request_id].status
|
||||
if status == ApprovalStatus.APPROVED:
|
||||
return True
|
||||
elif status == ApprovalStatus.REJECTED:
|
||||
return False
|
||||
await asyncio.sleep(1)
|
||||
|
||||
raise TimeoutError("Approval request timeout")
|
||||
```
|
||||
|
||||
### 4.6 全链路审计
|
||||
|
||||
```python
|
||||
# python/app/security/audit.py
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict
|
||||
import json
|
||||
|
||||
class AuditLogger:
|
||||
"""审计日志"""
|
||||
|
||||
def __init__(self, log_file: str = "audit.log"):
|
||||
self.log_file = log_file
|
||||
|
||||
def log(
|
||||
self,
|
||||
action: str,
|
||||
user_id: str,
|
||||
agent_id: str,
|
||||
details: Dict[str, Any],
|
||||
result: str = "success"
|
||||
):
|
||||
"""记录审计日志"""
|
||||
entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"action": action,
|
||||
"user_id": user_id,
|
||||
"agent_id": agent_id,
|
||||
"details": details,
|
||||
"result": result
|
||||
}
|
||||
|
||||
# 写入日志文件
|
||||
with open(self.log_file, 'a') as f:
|
||||
f.write(json.dumps(entry) + '\n')
|
||||
|
||||
# 发送到Go后端
|
||||
self.send_to_backend(entry)
|
||||
|
||||
def log_tool_execution(
|
||||
self,
|
||||
user_id: str,
|
||||
tool_name: str,
|
||||
params: Dict[str, Any],
|
||||
approved: bool,
|
||||
result: Any
|
||||
):
|
||||
"""记录工具执行"""
|
||||
self.log(
|
||||
action="tool_execution",
|
||||
user_id=user_id,
|
||||
agent_id="",
|
||||
details={
|
||||
"tool_name": tool_name,
|
||||
"params": params,
|
||||
"approved": approved,
|
||||
"result_preview": str(result)[:100]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、权限模型 (Go端)
|
||||
|
||||
### 5.1 用户角色
|
||||
|
||||
```go
|
||||
// go/internal/model/user.go
|
||||
|
||||
package model
|
||||
|
||||
// 权限级别
|
||||
type PermissionLevel int
|
||||
|
||||
const (
|
||||
PermissionRead PermissionLevel = 1 // 只读
|
||||
PermissionWrite PermissionLevel = 2 // 读写
|
||||
PermissionExecute PermissionLevel = 3 // 可执行工具
|
||||
PermissionAdmin PermissionLevel = 4 // 管理员
|
||||
)
|
||||
|
||||
// 角色定义
|
||||
type Role struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Permissions []PermissionLevel `json:"permissions"`
|
||||
}
|
||||
|
||||
// 用户
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
RoleID string `json:"role_id"`
|
||||
Role *Role `json:"role,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Agent定义
|
||||
|
||||
```go
|
||||
// go/internal/model/agent.go
|
||||
|
||||
package model
|
||||
|
||||
type Agent struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
|
||||
// Agent能力配置
|
||||
Capabilities []string `json:"capabilities"` // 可用工具列表
|
||||
MemoryLimit int64 `json:"memory_limit"` // 内存限制
|
||||
Timeout int `json:"timeout"` // 超时时间
|
||||
|
||||
// 安全配置
|
||||
SecurityLevel SecurityLevel `json:"security_level"`
|
||||
AllowDangerousTools bool `json:"allow_dangerous_tools"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、部署架构
|
||||
|
||||
### 6.1 Docker Compose
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Go API 服务
|
||||
go-api:
|
||||
build: ./go
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- DATABASE_URL=postgres://user:pass@db:5432/agents
|
||||
- PYTHON_SERVICE_URL=http://python-agent:8081
|
||||
- JWT_SECRET=your-secret
|
||||
depends_on:
|
||||
- db
|
||||
- python-agent
|
||||
|
||||
# Python Agent 服务
|
||||
python-agent:
|
||||
build: ./python
|
||||
ports:
|
||||
- "8081:8081"
|
||||
environment:
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
volumes:
|
||||
- ./python/app:/app
|
||||
- /var/run/docker.sock:/var/run/docker.sock # 如果需要Docker沙盒
|
||||
|
||||
# 数据库
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: pass
|
||||
POSTGRES_DB: agents
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
|
||||
# Redis (缓存/会话)
|
||||
redis:
|
||||
image: redis:7
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
||||
# 向量数据库 (可选)
|
||||
qdrant:
|
||||
image: qdrant/qdrant
|
||||
volumes:
|
||||
- qdrant-data:/qdrant/storage
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
redis-data:
|
||||
qdrant-data:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、开发流程
|
||||
|
||||
### 7.1 请求流程图
|
||||
|
||||
```
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ 用户 │────▶│ Go │────▶│ Python │────▶│ LLM │────▶│ 返回 │
|
||||
│ 请求 │ │ 鉴权 │ │ Agent │ │ +Tools │ │ 结果 │
|
||||
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────┐ ┌─────────┐
|
||||
│ 检查 │ │ 权限 │
|
||||
│ 权限 │ │ 检查 │
|
||||
└─────────┘ └─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ 工具安全等级判断 │
|
||||
└─────────────────────┘
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Safe │ │ Review │ │ Danger │
|
||||
│ 直接执行 │ │ 等待审批 │ │ 拒绝执行 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
### 7.2 目录结构总览
|
||||
|
||||
```
|
||||
X-Agents/
|
||||
├── docs/
|
||||
│ └── ARCHITECTURE.md # 本文档
|
||||
│
|
||||
├── go/ # Go 后端
|
||||
│ ├── cmd/
|
||||
│ ├── internal/
|
||||
│ ├── pkg/
|
||||
│ ├── go.mod
|
||||
│ └── Dockerfile
|
||||
│
|
||||
├── python/ # Python AI 层
|
||||
│ ├── app/
|
||||
│ │ ├── api/
|
||||
│ │ ├── agent/
|
||||
│ │ ├── llm/
|
||||
│ │ ├── rag/
|
||||
│ │ └── security/
|
||||
│ ├── requirements.txt
|
||||
│ └── Dockerfile
|
||||
│
|
||||
├── web/ # 前端 (Vue)
|
||||
│ ├── src/
|
||||
│ └── package.json
|
||||
│
|
||||
├── docker-compose.yml # 容器编排
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、总结
|
||||
|
||||
### 架构核心原则
|
||||
|
||||
| 原则 | 实现方式 |
|
||||
|------|----------|
|
||||
| **分层治理** | Go负责业务/权限,Python负责AI逻辑 |
|
||||
| **安全优先** | 工具分级+权限控制+人工审批+审计日志 |
|
||||
| **通信简洁** | HTTP JSON API,后续可升级gRPC |
|
||||
| **可扩展** | 模块化设计,支持多Agent/多Python服务 |
|
||||
| **可观测** | 全链路日志+监控 |
|
||||
|
||||
### 安全特性
|
||||
|
||||
- [x] 工具白名单机制
|
||||
- [x] 安全等级分级 (Safe/Review/Danger)
|
||||
- [x] RBAC权限控制
|
||||
- [x] Human in the Loop 人工审批
|
||||
- [x] 沙盒执行环境 (Docker)
|
||||
- [x] 全链路审计日志
|
||||
|
||||
---
|
||||
|
||||
*本文档将随项目开发持续更新*
|
||||
337
docs/agents.html
Normal file
337
docs/agents.html
Normal file
@@ -0,0 +1,337 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Remo MVP Design Interface</title>
|
||||
<!-- 引入Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- 引入Font Awesome图标 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- 自定义Tailwind配置 -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#1E6BF9',
|
||||
figma: '#F24E1E',
|
||||
action: '#000000',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.node-shadow {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
.flow-line {
|
||||
@apply bg-gray-300 w-0.5;
|
||||
}
|
||||
.flow-line-h {
|
||||
@apply bg-gray-300 h-0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 font-sans h-screen flex flex-col overflow-hidden">
|
||||
<!-- 顶部浏览器模拟栏 -->
|
||||
<div class="bg-white border-b border-gray-200 px-4 py-2 flex items-center gap-2">
|
||||
<div class="flex gap-1.5">
|
||||
<div class="w-3 h-3 rounded-full bg-red-400"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-400"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
</div>
|
||||
<div class="flex-1 mx-4 bg-gray-100 rounded-md px-3 py-1 text-sm text-gray-500 flex items-center gap-2">
|
||||
<i class="fa-solid fa-lock text-xs"></i>
|
||||
<span>Remo MVP Design</span>
|
||||
</div>
|
||||
<div class="flex gap-2 text-gray-500">
|
||||
<i class="fa-solid fa-chevron-left"></i>
|
||||
<i class="fa-solid fa-chevron-right opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容三栏布局 -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- 左侧边栏 -->
|
||||
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col h-full">
|
||||
<!-- 顶部导航信息 -->
|
||||
<div class="p-4 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<i class="fa-solid fa-th text-gray-600"></i>
|
||||
<span class="font-medium text-gray-800">Designera</span>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-gray-600 text-sm">Web Design</span>
|
||||
</div>
|
||||
<i class="fa-solid fa-window-maximize text-gray-500"></i>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">Drafts</p>
|
||||
</div>
|
||||
|
||||
<!-- 文件/资产标签栏 -->
|
||||
<div class="px-4 py-2 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex gap-4">
|
||||
<button class="text-sm font-medium text-gray-800 border-b-2 border-primary pb-1">File</button>
|
||||
<button class="text-sm font-medium text-gray-400 pb-1">Assets</button>
|
||||
</div>
|
||||
<i class="fa-solid fa-magnifying-glass text-gray-500 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pages区域 -->
|
||||
<div class="p-4 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Pages</span>
|
||||
<button class="text-gray-500 hover:text-gray-800">
|
||||
<i class="fa-solid fa-plus text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 bg-gray-100 rounded px-3 py-2">
|
||||
<i class="fa-solid fa-palette text-sm text-gray-600"></i>
|
||||
<span class="text-sm font-medium text-gray-800">Design</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layers区域 -->
|
||||
<div class="p-4 flex-1 overflow-y-auto">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<i class="fa-solid fa-layer-group text-xs text-gray-500"></i>
|
||||
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Layers</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 中间画布区域 -->
|
||||
<main class="flex-1 bg-gray-50 relative overflow-auto">
|
||||
<!-- 流程图容器 -->
|
||||
<div class="min-h-full w-full flex flex-col items-center py-10 px-4">
|
||||
<!-- 节点1 -->
|
||||
<div class="node bg-white rounded-lg node-shadow w-72 p-3 relative z-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" checked class="w-5 h-5 rounded text-primary focus:ring-primary">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<i class="fa-brands fa-figma text-figma text-lg"></i>
|
||||
<span class="font-medium text-gray-800">Figma</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square text-sm"></i>
|
||||
<i class="fa-solid fa-pen text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 ml-8 mt-1">1. Start your project from sketch</p>
|
||||
</div>
|
||||
|
||||
<!-- 连接线1 -->
|
||||
<div class="flow-line h-6"></div>
|
||||
|
||||
<!-- 节点2 Action黑色节点 -->
|
||||
<div class="node bg-action rounded-lg node-shadow w-72 p-3 relative z-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-5 h-5 flex items-center justify-center">
|
||||
<i class="fa-solid fa-bolt text-white text-sm"></i>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="font-medium text-white">Action</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-white/70">
|
||||
<i class="fa-solid fa-pen text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-white/90 ml-8 mt-1">2. Take a frame and start designing</p>
|
||||
</div>
|
||||
|
||||
<!-- 连接线2 -->
|
||||
<div class="flow-line h-6"></div>
|
||||
|
||||
<!-- 节点3 -->
|
||||
<div class="node bg-white rounded-lg node-shadow w-72 p-3 relative z-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" checked class="w-5 h-5 rounded text-primary focus:ring-primary">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<i class="fa-brands fa-figma text-figma text-lg"></i>
|
||||
<span class="font-medium text-gray-800">Figma</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square text-sm"></i>
|
||||
<i class="fa-solid fa-pen text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 ml-8 mt-1">3. Add few shapes</p>
|
||||
</div>
|
||||
|
||||
<!-- 连接线3 -->
|
||||
<div class="flow-line h-6"></div>
|
||||
|
||||
<!-- 节点4 -->
|
||||
<div class="node bg-white rounded-lg node-shadow w-72 p-3 relative z-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" checked class="w-5 h-5 rounded text-primary focus:ring-primary">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<i class="fa-brands fa-figma text-figma text-lg"></i>
|
||||
<span class="font-medium text-gray-800">Figma</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square text-sm"></i>
|
||||
<i class="fa-solid fa-pen text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 ml-8 mt-1">4. Split into paths</p>
|
||||
</div>
|
||||
|
||||
<!-- 分支连接线 -->
|
||||
<div class="w-full max-w-2xl relative flex justify-center">
|
||||
<div class="flow-line h-6"></div>
|
||||
<div class="absolute top-6 w-full flex justify-center">
|
||||
<div class="flow-line-h w-2/3"></div>
|
||||
</div>
|
||||
<div class="absolute top-6 w-full flex justify-between px-[12.5%]">
|
||||
<div class="flow-line h-6"></div>
|
||||
<div class="flow-line h-6"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分支标签 -->
|
||||
<div class="w-full max-w-2xl flex justify-between px-[12.5%] mb-6">
|
||||
<span class="bg-primary text-white text-xs font-medium px-3 py-1 rounded-full">Path A</span>
|
||||
<span class="bg-primary text-white text-xs font-medium px-3 py-1 rounded-full">Path B</span>
|
||||
</div>
|
||||
|
||||
<!-- 双分支节点 -->
|
||||
<div class="w-full max-w-2xl flex justify-between px-4">
|
||||
<!-- 节点5 Path A -->
|
||||
<div class="node bg-white rounded-lg node-shadow w-72 p-3 relative z-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" checked class="w-5 h-5 rounded text-primary focus:ring-primary">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<i class="fa-brands fa-figma text-figma text-lg"></i>
|
||||
<span class="font-medium text-gray-800">Figma</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square text-sm"></i>
|
||||
<i class="fa-solid fa-pen text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 ml-8 mt-1">5. Split into paths</p>
|
||||
</div>
|
||||
|
||||
<!-- 节点6 Path B -->
|
||||
<div class="node bg-white rounded-lg node-shadow w-72 p-3 relative z-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" checked class="w-5 h-5 rounded text-primary focus:ring-primary">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<i class="fa-brands fa-figma text-figma text-lg"></i>
|
||||
<span class="font-medium text-gray-800">Figma</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square text-sm"></i>
|
||||
<i class="fa-solid fa-pen text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 ml-8 mt-1">6. Split into paths</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部工具栏 -->
|
||||
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 bg-white rounded-full shadow-lg px-4 py-2 flex items-center gap-4">
|
||||
<button class="text-primary p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-solid fa-cursor-pointer"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-solid fa-hand"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-regular fa-square"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-solid fa-t"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-regular fa-circle"></i>
|
||||
</button>
|
||||
<div class="w-px h-5 bg-gray-200"></div>
|
||||
<button class="text-gray-600 p-1.5 rounded hover:bg-gray-100">
|
||||
<i class="fa-solid fa-code"></i>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 右侧Copilot面板 -->
|
||||
<aside class="w-96 bg-white border-l border-gray-200 flex flex-col h-full">
|
||||
<!-- 顶部Copilot品牌区 -->
|
||||
<div class="p-6 flex flex-col items-center text-center border-b border-gray-200">
|
||||
<div class="w-16 h-16 rounded-xl bg-gradient-to-tr from-blue-500 via-purple-500 to-orange-400 mb-4 flex items-center justify-center">
|
||||
<div class="w-10 h-10 bg-white/20 backdrop-blur-sm rounded-lg"></div>
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-2">Hey, what's on your mind today?</h2>
|
||||
<button class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg transition">
|
||||
Get advice
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 聊天对话区 -->
|
||||
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
|
||||
<!-- Copilot消息 -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<div class="w-5 h-5 rounded bg-gradient-to-tr from-blue-500 to-purple-500 flex items-center justify-center">
|
||||
<i class="fa-brands fa-microsoft text-white text-xs"></i>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-700">Copilot</span>
|
||||
</div>
|
||||
<div class="bg-gray-100 rounded-2xl rounded-tl-none px-4 py-3 text-gray-700 ml-7">
|
||||
Advice on what, exactly? Whether it's life decisions, career choices, relationships, or something entirely random, I'm here to help. Let me know the topic, and we'll dive into it together!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户消息 -->
|
||||
<div class="flex flex-col gap-2 items-end">
|
||||
<div class="bg-gray-800 rounded-2xl rounded-tr-none px-4 py-3 text-white max-w-[80%]">
|
||||
What are some common life decisions people seek advice on?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部输入框 -->
|
||||
<div class="p-4 border-t border-gray-200">
|
||||
<div class="relative">
|
||||
<input type="text" placeholder="Ask anything..." class="w-full border border-gray-300 rounded-full px-4 py-3 pr-12 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
|
||||
<button class="absolute right-3 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center">
|
||||
<i class="fa-solid fa-arrow-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 mt-2 ml-2 text-xs text-gray-500">
|
||||
<i class="fa-brands fa-microsoft"></i>
|
||||
<span>Copilot by Microsoft</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
692
docs/dashboard.html
Normal file
692
docs/dashboard.html
Normal file
@@ -0,0 +1,692 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Saasfactor Dashboard</title>
|
||||
<!-- 引入Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- 引入Font Awesome图标 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<!-- 自定义Tailwind配置 -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
dark: {
|
||||
950: '#0a0c10', // 侧边栏背景
|
||||
900: '#0f1115', // 主页面背景
|
||||
800: '#121419',
|
||||
700: '#171922', // 卡片背景
|
||||
600: '#1a1c25',
|
||||
500: '#2a2c36', // 次要背景/边框
|
||||
},
|
||||
primary: {
|
||||
orange: '#ff9500',
|
||||
yellow: '#ffc247',
|
||||
cyan: '#36bffa',
|
||||
purple: '#a78bfa',
|
||||
danger: '#ef4444',
|
||||
success: '#10b981',
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-dark-900 text-gray-100 font-sans min-h-screen flex overflow-x-hidden">
|
||||
<!-- 左侧侧边栏 -->
|
||||
<aside class="w-64 bg-dark-950 h-screen flex flex-col fixed left-0 top-0 overflow-y-auto scrollbar-hide z-10">
|
||||
<!-- 顶部Logo与组织信息 -->
|
||||
<div class="p-5 flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-primary-orange to-red-500 flex items-center justify-center">
|
||||
<i class="fa-solid fa-basketball text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold text-lg flex items-center gap-1">
|
||||
Organization
|
||||
<i class="fa-solid fa-chevron-down text-xs text-gray-400"></i>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400">Saasfactor</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航菜单 -->
|
||||
<nav class="flex-1 px-3 py-2">
|
||||
<ul class="space-y-1">
|
||||
<!-- Dashboard 激活项 -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-dark-600 text-white">
|
||||
<i class="fa-solid fa-gauge w-5 text-center"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Agents -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-robot w-5 text-center"></i>
|
||||
<span>Agents</span>
|
||||
</div>
|
||||
<span class="bg-dark-500 text-xs px-2 py-0.5 rounded-full">3</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- MCP -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-code w-5 text-center"></i>
|
||||
<span>MCP</span>
|
||||
</div>
|
||||
<span class="bg-dark-500 text-xs px-2 py-0.5 rounded-full">21</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Model APIs -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-cube w-5 text-center"></i>
|
||||
<span>Model APIs</span>
|
||||
</div>
|
||||
<span class="bg-dark-500 text-xs px-2 py-0.5 rounded-full">13</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Policies -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-shield-halved w-5 text-center"></i>
|
||||
<span>Policies</span>
|
||||
</div>
|
||||
<span class="bg-dark-500 text-xs px-2 py-0.5 rounded-full">1</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<li class="my-4 border-t border-dark-500"></li>
|
||||
|
||||
<!-- API Keys -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-key w-5 text-center"></i>
|
||||
<span>API Keys</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Settings -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-gear w-5 text-center"></i>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<span class="w-2 h-2 rounded-full bg-primary-orange"></span>
|
||||
<span class="w-2 h-2 rounded-full bg-yellow-500"></span>
|
||||
<span class="w-2 h-2 rounded-full bg-gray-500"></span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Team -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-users w-5 text-center"></i>
|
||||
<span>Team</span>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<span class="w-2 h-2 rounded-full bg-primary-orange"></span>
|
||||
<span class="w-2 h-2 rounded-full bg-blue-500"></span>
|
||||
<span class="w-2 h-2 rounded-full bg-gray-500"></span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Service Accounts -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-user-shield w-5 text-center"></i>
|
||||
<span>Service Accounts</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Integrations -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-plug w-5 text-center"></i>
|
||||
<span>Integrations</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<li class="my-4 border-t border-dark-500"></li>
|
||||
|
||||
<!-- Information -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-circle-info w-5 text-center"></i>
|
||||
<span>Information</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Account -->
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-dark-600 text-gray-300 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-user w-5 text-center"></i>
|
||||
<span>Account</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 底部用户信息 -->
|
||||
<div class="p-4 border-t border-dark-500">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="https://picsum.photos/id/64/40/40" alt="User Avatar" class="w-8 h-8 rounded-full object-cover">
|
||||
<div>
|
||||
<div class="font-medium text-sm">Alex Smith</div>
|
||||
<div class="text-xs text-gray-400">alex@gmail.com</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<main class="ml-64 flex-1 p-6">
|
||||
<!-- 顶部导航与日期选择区 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-gauge text-gray-400"></i>
|
||||
<span class="font-medium">Dashboard</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-gray-400">Date Range</span>
|
||||
<div class="flex items-center gap-2 bg-dark-600 rounded-lg px-3 py-2">
|
||||
<span>10 December</span>
|
||||
<span class="text-gray-400">To</span>
|
||||
<span>12 December</span>
|
||||
<i class="fa-solid fa-calendar text-gray-400"></i>
|
||||
</div>
|
||||
<button class="text-primary-orange hover:text-orange-400 transition-colors">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片网格布局 -->
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<!-- 第一行:3个状态卡片 -->
|
||||
<!-- Active Agents 卡片 -->
|
||||
<div class="bg-dark-700 rounded-xl p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-semibold text-lg">Active Agents</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-400">Errors</span>
|
||||
<!-- 开启状态开关 -->
|
||||
<div class="w-10 h-5 rounded-full bg-primary-orange relative cursor-pointer">
|
||||
<div class="absolute right-0.5 top-0.5 w-4 h-4 rounded-full bg-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-4xl font-bold mb-4">3</div>
|
||||
<!-- 进度条 -->
|
||||
<div class="w-full h-2 rounded-full bg-dark-500 overflow-hidden mb-4">
|
||||
<div class="flex h-full">
|
||||
<div class="w-[33%] bg-primary-yellow"></div>
|
||||
<div class="w-[33%] bg-primary-cyan"></div>
|
||||
<div class="w-[34%] bg-primary-purple"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 明细列表 -->
|
||||
<ul class="space-y-2">
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-yellow"></span>
|
||||
<span class="text-sm text-gray-300">template-google-adk-api</span>
|
||||
</div>
|
||||
<span class="text-sm">1</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-cyan"></span>
|
||||
<span class="text-sm text-gray-300">mcp-google-adk-api</span>
|
||||
<span class="bg-primary-danger text-white text-xs px-1.5 py-0.5 rounded">Error</span>
|
||||
</div>
|
||||
<span class="text-sm">1</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-purple"></span>
|
||||
<span class="text-sm text-gray-300">template-openai-api</span>
|
||||
<span class="bg-primary-danger text-white text-xs px-1.5 py-0.5 rounded">Error</span>
|
||||
</div>
|
||||
<span class="text-sm">1</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Active MCP Servers 卡片 -->
|
||||
<div class="bg-dark-700 rounded-xl p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-semibold text-lg">Active MCP Servers</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-400">Errors</span>
|
||||
<!-- 关闭状态开关 -->
|
||||
<div class="w-10 h-5 rounded-full bg-dark-500 relative cursor-pointer">
|
||||
<div class="absolute left-0.5 top-0.5 w-4 h-4 rounded-full bg-gray-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-4xl font-bold mb-4">21</div>
|
||||
<!-- 进度条 -->
|
||||
<div class="w-full h-2 rounded-full bg-dark-500 overflow-hidden mb-4">
|
||||
<div class="flex h-full">
|
||||
<div class="w-[71%] bg-primary-yellow"></div>
|
||||
<div class="w-[19%] bg-primary-cyan"></div>
|
||||
<div class="w-[10%] bg-primary-purple"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 明细列表 -->
|
||||
<ul class="space-y-2">
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-yellow"></span>
|
||||
<span class="text-sm text-gray-300">linear-demo</span>
|
||||
</div>
|
||||
<span class="text-sm">15</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-cyan"></span>
|
||||
<span class="text-sm text-gray-300">google-maps</span>
|
||||
</div>
|
||||
<span class="text-sm">4</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-purple"></span>
|
||||
<span class="text-sm text-gray-300">explorer-mcp</span>
|
||||
</div>
|
||||
<span class="text-sm">2</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Active Models 卡片 -->
|
||||
<div class="bg-dark-700 rounded-xl p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-semibold text-lg">Active Models</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-400">Errors</span>
|
||||
<!-- 关闭状态开关 -->
|
||||
<div class="w-10 h-5 rounded-full bg-dark-500 relative cursor-pointer">
|
||||
<div class="absolute left-0.5 top-0.5 w-4 h-4 rounded-full bg-gray-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-4xl font-bold mb-4">13</div>
|
||||
<!-- 进度条 -->
|
||||
<div class="w-full h-2 rounded-full bg-dark-500 overflow-hidden mb-4">
|
||||
<div class="flex h-full">
|
||||
<div class="w-[46%] bg-primary-yellow"></div>
|
||||
<div class="w-[15%] bg-primary-cyan"></div>
|
||||
<div class="w-[39%] bg-primary-purple"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 明细列表 -->
|
||||
<ul class="space-y-2">
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-yellow"></span>
|
||||
<span class="text-sm text-gray-300">gpt-40-2024-08-12</span>
|
||||
</div>
|
||||
<span class="text-sm">2</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-cyan"></span>
|
||||
<span class="text-sm text-gray-300">cerebras-sandbox</span>
|
||||
</div>
|
||||
<span class="text-sm">6</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-purple"></span>
|
||||
<span class="text-sm text-gray-300">sandbox-openai</span>
|
||||
</div>
|
||||
<span class="text-sm">5</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 第二行 -->
|
||||
<!-- All deployment request Insights 卡片(跨2列)【已修复对齐问题】 -->
|
||||
<div class="bg-dark-700 rounded-xl p-5 col-span-2">
|
||||
<h3 class="font-semibold text-lg mb-4">All deployment request Insights</h3>
|
||||
<!-- 数据概览 -->
|
||||
<div class="grid grid-cols-4 gap-4 mb-6">
|
||||
<div>
|
||||
<div class="text-sm text-gray-400 mb-1">Requests</div>
|
||||
<div class="text-2xl font-bold">36</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-400 mb-1">Agents calls</div>
|
||||
<div class="text-2xl font-bold">3</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-400 mb-1">MCP servers calls</div>
|
||||
<div class="text-2xl font-bold">21</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-400 mb-1">Models requests</div>
|
||||
<div class="text-2xl font-bold">13</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 【重构后】图表容器 完美对齐0刻度 -->
|
||||
<div class="relative h-52 w-full">
|
||||
<!-- 绘图区(网格线+Y轴+柱子) 预留底部20px给时间标签 -->
|
||||
<div class="relative h-[calc(100%-20px)] w-full">
|
||||
<!-- 横向网格线 与Y轴刻度完全对齐 0刻度线在绘图区最底部 -->
|
||||
<div class="absolute left-0 top-0 w-full h-full z-0">
|
||||
<div class="absolute w-full h-[1px] bg-white/[0.06] top-0"></div>
|
||||
<div class="absolute w-full h-[1px] bg-white/[0.06] top-[25%]"></div>
|
||||
<div class="absolute w-full h-[1px] bg-white/[0.06] top-[50%]"></div>
|
||||
<div class="absolute w-full h-[1px] bg-white/[0.06] top-[75%]"></div>
|
||||
<div class="absolute w-full h-[1px] bg-white/[0.06] top-[100%]"></div>
|
||||
</div>
|
||||
|
||||
<!-- Y轴刻度 0刻度与绘图区最底部的0网格线完美垂直对齐 -->
|
||||
<div class="absolute left-0 top-0 h-full flex flex-col justify-between text-xs text-gray-500 z-10">
|
||||
<span>8</span>
|
||||
<span>6</span>
|
||||
<span>4</span>
|
||||
<span>2</span>
|
||||
<span>0</span>
|
||||
</div>
|
||||
|
||||
<!-- 柱状图容器 柱子底部严丝合缝对齐0网格线 -->
|
||||
<div class="ml-6 h-full flex items-end justify-between gap-1 z-10 relative">
|
||||
<!-- 3:02 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 12.5%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 25%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 18.75%"></div>
|
||||
</div>
|
||||
<!-- 3:07 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 25%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 31.25%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 25%"></div>
|
||||
</div>
|
||||
<!-- 3:12 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 31.25%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 62.5%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 50%"></div>
|
||||
</div>
|
||||
<!-- 3:17 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 31.25%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 37.5%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 25%"></div>
|
||||
</div>
|
||||
<!-- 3:22 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 18.75%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 31.25%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 18.75%"></div>
|
||||
</div>
|
||||
<!-- 3:27 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 18.75%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 37.5%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 31.25%"></div>
|
||||
</div>
|
||||
<!-- 3:32 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 12.5%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 25%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 31.25%"></div>
|
||||
</div>
|
||||
<!-- 3:37 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 31.25%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 100%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 37.5%"></div>
|
||||
</div>
|
||||
<!-- 3:42 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 12.5%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 62.5%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 31.25%"></div>
|
||||
</div>
|
||||
<!-- 3:47 PM 柱子 -->
|
||||
<div class="flex items-end gap-1 h-full w-full justify-center">
|
||||
<div class="w-3 bg-primary-purple rounded-t-sm" style="height: 31.25%"></div>
|
||||
<div class="w-3 bg-primary-yellow rounded-t-sm" style="height: 37.5%"></div>
|
||||
<div class="w-3 bg-primary-cyan rounded-t-sm" style="height: 25%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间标签区 统一放在0刻度线下方 与对应柱子居中对齐 -->
|
||||
<div class="ml-6 w-full flex justify-between gap-1 mt-1">
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:02 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:07 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:12 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:17 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:22 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:27 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:32 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:37 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:42 PM</span>
|
||||
<span class="text-xs text-gray-500 w-full text-center">3:47 PM</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图例 -->
|
||||
<div class="flex justify-center gap-6 mt-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-purple"></span>
|
||||
<span class="text-xs text-gray-400">Agents calls</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-yellow"></span>
|
||||
<span class="text-xs text-gray-400">MCP servers calls</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-sm bg-primary-cyan"></span>
|
||||
<span class="text-xs text-gray-400">Models requests</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top 10 requests 卡片 -->
|
||||
<div class="bg-dark-700 rounded-xl p-5">
|
||||
<h3 class="font-semibold text-lg mb-4">Top 10 requests</h3>
|
||||
<!-- 标签切换 -->
|
||||
<div class="flex mb-4">
|
||||
<button class="flex-1 bg-dark-500 text-white py-1.5 rounded-l-lg text-sm">General</button>
|
||||
<button class="flex-1 bg-transparent text-gray-400 py-1.5 rounded-r-lg text-sm hover:bg-dark-600 transition-colors">Errors</button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<ul class="space-y-3">
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">gpt-40-2024-08-12</span>
|
||||
</div>
|
||||
<span class="text-sm">7</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-code text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">google-maps</span>
|
||||
</div>
|
||||
<span class="text-sm">4</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-code text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">explorer-mcp</span>
|
||||
</div>
|
||||
<span class="text-sm">2</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">template-google-adk-api</span>
|
||||
</div>
|
||||
<span class="text-sm">4</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">linear-demo</span>
|
||||
</div>
|
||||
<span class="text-sm">2</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-code text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">cerebras-sandbox</span>
|
||||
</div>
|
||||
<span class="text-sm">1</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<span class="text-sm text-gray-300">sandbox-openai</span>
|
||||
</div>
|
||||
<span class="text-sm">2</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 第三行 -->
|
||||
<!-- What's new 卡片(跨2列) -->
|
||||
<div class="bg-dark-700 rounded-xl p-5 col-span-2">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-semibold text-lg">What's new</h3>
|
||||
<a href="#" class="text-primary-orange text-sm flex items-center gap-1 hover:text-orange-400 transition-colors">
|
||||
Full change log
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400 mb-4">Stay up to date with our latest feature and improvements</p>
|
||||
<!-- 更新列表 -->
|
||||
<ul class="space-y-4">
|
||||
<li class="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 class="font-medium mb-1">New framework supported: PydanticAI</h4>
|
||||
<p class="text-sm text-gray-400">Added support for PydanticAI framework</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">2025-04-12</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 class="font-medium mb-1">New framework supported: Google ADK</h4>
|
||||
<p class="text-sm text-gray-400">Added support for Google ADK (Agent Development Kit) framework</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">2025-04-07</span>
|
||||
</li>
|
||||
<li class="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 class="font-medium mb-1">Improved Analytics Dashboard</h4>
|
||||
<p class="text-sm text-gray-400">Enhanced real-time monitoring with faster data refresh</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">2025-04-15</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Recent requests (10) 卡片 -->
|
||||
<div class="bg-dark-700 rounded-xl p-5">
|
||||
<h3 class="font-semibold text-lg mb-4">Recent requests (10)</h3>
|
||||
<!-- 列表 -->
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">linear-demo</span>
|
||||
<span class="text-xs text-gray-500">in 21 hours</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-xs text-primary-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<span>Success</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-robot text-gray-400"></i>
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">myagent</span>
|
||||
<span class="text-xs text-gray-500">in 21 hours</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-xs text-primary-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<span>Success</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">linear-demo</span>
|
||||
<span class="text-xs text-gray-500">in 21 hours</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-xs text-primary-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<span>Success</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-code text-gray-400"></i>
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">gpt-40</span>
|
||||
<span class="text-xs text-gray-500">in 21 hours</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-xs text-primary-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<span>Success</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">linear-demo</span>
|
||||
<span class="text-xs text-gray-500">in 21 hours</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-xs text-primary-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<span>Success</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
909
docs/graph.html
Normal file
909
docs/graph.html
Normal file
@@ -0,0 +1,909 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>思维本体元模型 — 知识图谱</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<script src="./graph-data.js"></script>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', 'PingFang SC', -apple-system, sans-serif;
|
||||
background: #0a0e1a;
|
||||
color: #e6edf3;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
height: 52px;
|
||||
background: linear-gradient(90deg, #0f1628, #1a1040);
|
||||
border-bottom: 1px solid #2d2d55;
|
||||
flex-shrink: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#topbar .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#topbar .title .icon {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
#topbar .title h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(90deg, #8B5CF6, #3B82F6, #10B981);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
#topbar .subtitle {
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
#topbar .controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ctrl-btn {
|
||||
background: #1a1a2e;
|
||||
border: 1px solid #2d2d55;
|
||||
color: #c9d1d9;
|
||||
padding: 5px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.ctrl-btn:hover {
|
||||
background: #2d2d55;
|
||||
border-color: #8B5CF6;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
|
||||
.ctrl-btn.active {
|
||||
background: #4C1D95;
|
||||
border-color: #8B5CF6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#main-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#left-panel {
|
||||
width: 230px;
|
||||
background: #0f1628;
|
||||
border-right: 1px solid #2d2d55;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.panel-section {
|
||||
padding: 14px 14px 10px;
|
||||
border-bottom: 1px solid #1a1a2e;
|
||||
}
|
||||
|
||||
.panel-section-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
color: #8b949e;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background .15s;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.legend-item:hover {
|
||||
background: #1a1a2e;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 6px currentColor;
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
font-size: 13px;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
|
||||
.legend-count {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
color: #8b949e;
|
||||
background: #1a1a2e;
|
||||
padding: 1px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scenario-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 4px;
|
||||
background: #1a1a2e;
|
||||
border: 1px solid #2d2d55;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: #c9d1d9;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.scenario-btn:hover {
|
||||
border-color: #8B5CF6;
|
||||
color: #8B5CF6;
|
||||
background: #1a1040;
|
||||
}
|
||||
|
||||
.scenario-btn.active {
|
||||
border-color: var(--sc-color);
|
||||
color: var(--sc-color);
|
||||
background: rgba(139, 92, 246, .08);
|
||||
box-shadow: 0 0 8px rgba(139, 92, 246, .2);
|
||||
}
|
||||
|
||||
.scenario-btn .sc-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--sc-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scenario-btn .sc-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scenario-btn .sc-count {
|
||||
font-size: 10px;
|
||||
color: #8b949e;
|
||||
background: #0f1628;
|
||||
padding: 1px 5px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rel-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 3px 0;
|
||||
font-size: 11px;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
.rel-line {
|
||||
width: 28px;
|
||||
height: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#chart-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#graph-hint {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(15, 22, 40, 0.85);
|
||||
border: 1px solid #2d2d55;
|
||||
border-radius: 8px;
|
||||
padding: 6px 14px;
|
||||
font-size: 11px;
|
||||
color: #8b949e;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
#right-panel {
|
||||
width: 260px;
|
||||
background: #0f1628;
|
||||
border-left: 1px solid #2d2d55;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#detail-header {
|
||||
padding: 14px 16px 10px;
|
||||
border-bottom: 1px solid #1a1a2e;
|
||||
}
|
||||
|
||||
#detail-type-badge {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#detail-name {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #e6edf3;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
#detail-tag {
|
||||
font-size: 11px;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
#detail-body {
|
||||
padding: 12px 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#detail-desc {
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
background: #0a0e1a;
|
||||
border: 1px solid #1a1a2e;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#detail-connections {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.conn-title {
|
||||
font-size: 11px;
|
||||
color: #6e7681;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .5px;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.conn-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 11px;
|
||||
color: #8b949e;
|
||||
background: #0a0e1a;
|
||||
border: 1px solid #1a1a2e;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.conn-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.conn-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.conn-rel {
|
||||
font-size: 10px;
|
||||
color: #6e7681;
|
||||
}
|
||||
|
||||
#detail-placeholder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #484f58;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#detail-placeholder .ph-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#detail-placeholder .ph-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#stats-bar {
|
||||
display: flex;
|
||||
padding: 10px 16px;
|
||||
gap: 10px;
|
||||
border-top: 1px solid #1a1a2e;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 10px;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
/* 模拟控制栏 */
|
||||
#sim-bar {
|
||||
position: fixed;
|
||||
bottom: -160px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: min(820px, 94vw);
|
||||
background: linear-gradient(135deg, #1a1040, #0f1628);
|
||||
border: 1px solid #2d2d55;
|
||||
border-bottom: none;
|
||||
border-radius: 14px 14px 0 0;
|
||||
box-shadow: 0 -4px 32px rgba(0, 0, 0, 0.6);
|
||||
z-index: 500;
|
||||
transition: bottom .4s cubic-bezier(.4, 0, .2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sim-bar.visible {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#sim-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 18px 8px;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #1a1a2e;
|
||||
}
|
||||
|
||||
#sim-header .sim-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#sim-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #c084fc;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#sim-step-badge {
|
||||
font-size: 11px;
|
||||
background: #1a1a2e;
|
||||
color: #8b949e;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #2d2d55;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#sim-phase-badge {
|
||||
font-size: 10px;
|
||||
padding: 3px 9px;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#sim-progress {
|
||||
height: 3px;
|
||||
background: #1a1a2e;
|
||||
margin: 0 18px;
|
||||
}
|
||||
|
||||
#sim-progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #8B5CF6, #c084fc);
|
||||
border-radius: 2px;
|
||||
transition: width .4s ease;
|
||||
}
|
||||
|
||||
#sim-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 18px 14px;
|
||||
}
|
||||
|
||||
#sim-desc {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: #c9d1d9;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#sim-desc .sim-step-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #e6edf3;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#sim-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sim-btn {
|
||||
padding: 7px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2d2d55;
|
||||
background: #1a1a2e;
|
||||
color: #c9d1d9;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transition: all .2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sim-btn:hover {
|
||||
background: #2d2d55;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sim-btn:disabled {
|
||||
opacity: .35;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.sim-btn.primary {
|
||||
background: linear-gradient(135deg, #7C3AED, #8B5CF6);
|
||||
border-color: #7C3AED;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 12px rgba(124, 58, 237, .4);
|
||||
}
|
||||
|
||||
.sim-btn.stop {
|
||||
background: #2d1f1f;
|
||||
border-color: #EF4444;
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
.sim-btn.stop:hover {
|
||||
background: #EF4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#sim-auto-btn.playing {
|
||||
background: linear-gradient(135deg, #059669, #10B981);
|
||||
border-color: #10B981;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #2d2d55;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<div class="title">
|
||||
<span class="icon">🧠</span>
|
||||
<div>
|
||||
<h1>思维本体元模型 — 知识图谱</h1>
|
||||
<div class="subtitle">Thinking Ontology Meta-Model · ECharts Force-Directed Graph</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button class="ctrl-btn" id="btn-reset">↺ 复位</button>
|
||||
<button class="ctrl-btn active" id="btn-labels">标签 ON</button>
|
||||
<button class="ctrl-btn active" id="btn-rules">规则边</button>
|
||||
<button class="ctrl-btn" id="btn-fullscreen">⛶ 全屏</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-layout">
|
||||
<div id="left-panel">
|
||||
<div class="panel-section">
|
||||
<div class="panel-section-title">节点类型图例</div>
|
||||
<div class="legend-item" onclick="filterByCategory(0)">
|
||||
<div class="legend-dot" style="background:#3B82F6;color:#3B82F6"></div>
|
||||
<span class="legend-label">语义对象</span>
|
||||
<span class="legend-count" id="cnt-0">—</span>
|
||||
</div>
|
||||
<div class="legend-item" onclick="filterByCategory(1)">
|
||||
<div class="legend-dot" style="background:#10B981;color:#10B981"></div>
|
||||
<span class="legend-label">对象行为</span>
|
||||
<span class="legend-count" id="cnt-1">—</span>
|
||||
</div>
|
||||
<div class="legend-item" onclick="filterByCategory(2)">
|
||||
<div class="legend-dot" style="background:#F59E0B;color:#F59E0B"></div>
|
||||
<span class="legend-label">约束规则</span>
|
||||
<span class="legend-count" id="cnt-2">—</span>
|
||||
</div>
|
||||
<div class="legend-item" onclick="filterByCategory(3)">
|
||||
<div class="legend-dot" style="background:#8B5CF6;color:#8B5CF6"></div>
|
||||
<span class="legend-label">编排流程</span>
|
||||
<span class="legend-count" id="cnt-3">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-section">
|
||||
<div class="panel-section-title">场景联动筛选</div>
|
||||
<div id="scenario-buttons"></div>
|
||||
<button class="ctrl-btn" style="width:100%;margin-top:6px;font-size:11px" onclick="clearScenario()">✕
|
||||
清除筛选</button>
|
||||
</div>
|
||||
<div class="panel-section">
|
||||
<div class="panel-section-title">关系类型</div>
|
||||
<div class="rel-item">
|
||||
<div class="rel-line" style="background:#3B82F6"></div><span>语义对象关系</span>
|
||||
</div>
|
||||
<div class="rel-item">
|
||||
<div class="rel-line" style="background:#10B981"></div><span>行为操作关系</span>
|
||||
</div>
|
||||
<div class="rel-item">
|
||||
<div class="rel-line"
|
||||
style="background:repeating-linear-gradient(90deg,#F59E0B 0,#F59E0B 4px,transparent 4px,transparent 8px)">
|
||||
</div><span>规则引用关系</span>
|
||||
</div>
|
||||
<div class="rel-item">
|
||||
<div class="rel-line" style="background:#8B5CF6"></div><span>流程调用关系</span>
|
||||
</div>
|
||||
<div class="rel-item">
|
||||
<div class="rel-line" style="background:#EF4444"></div><span>⭐ 核心关系</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="stats-bar">
|
||||
<div class="stat-item">
|
||||
<div class="stat-num" style="color:#3B82F6" id="s-nodes">—</div>
|
||||
<div class="stat-label">节点</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-num" style="color:#10B981" id="s-links">—</div>
|
||||
<div class="stat-label">关系</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chart-container">
|
||||
<div id="chart"></div>
|
||||
<div id="graph-hint">🖱 拖拽节点 · 滚轮缩放 · 点击查看详情 · 双击流程触发场景</div>
|
||||
<div id="sim-bar">
|
||||
<div id="sim-header">
|
||||
<span class="sim-icon">🎬</span>
|
||||
<span id="sim-title">问题分析与解决 — 动态模拟</span>
|
||||
<span id="sim-phase-badge"></span>
|
||||
<span id="sim-step-badge">0 / 14</span>
|
||||
</div>
|
||||
<div id="sim-progress">
|
||||
<div id="sim-progress-bar" style="width:0%"></div>
|
||||
</div>
|
||||
<div id="sim-body">
|
||||
<div id="sim-desc">
|
||||
<div class="sim-step-title" id="sim-step-title">点击「下一步」开始模拟</div>
|
||||
<div id="sim-step-desc">逐步展示问题分析与解决的完整调用链路。</div>
|
||||
</div>
|
||||
<div id="sim-controls">
|
||||
<button class="sim-btn" id="sim-prev-btn" disabled onclick="simStep(-1)">◀ 上一步</button>
|
||||
<button class="sim-btn primary" id="sim-next-btn" onclick="simStep(1)">下一步 ▶</button>
|
||||
<button class="sim-btn" id="sim-auto-btn" onclick="simToggleAuto()">⏵ 自动</button>
|
||||
<button class="sim-btn stop" onclick="simStop()">■ 停止</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="right-panel">
|
||||
<div id="detail-placeholder">
|
||||
<div class="ph-icon">🔍</div>
|
||||
<div class="ph-text">点击图谱中任意节点<br>查看详细元数据信息</div>
|
||||
</div>
|
||||
<div id="detail-content" style="display:none;flex-direction:column;flex:1">
|
||||
<div id="detail-header">
|
||||
<div id="detail-type-badge"></div>
|
||||
<div id="detail-name"></div>
|
||||
<div id="detail-tag"></div>
|
||||
</div>
|
||||
<div id="detail-body">
|
||||
<div class="conn-title">节点描述</div>
|
||||
<div id="detail-desc"></div>
|
||||
<div id="detail-connections">
|
||||
<div class="conn-title" style="margin-top:14px">关联节点</div>
|
||||
<div id="conn-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { CATEGORIES, NODES, LINKS, SCENARIOS, SIMULATION_STEPS } = ONTOLOGY_DATA;
|
||||
let chart = null, activeScenario = null, showLabels = true, showRuleEdges = true, catFilter = null;
|
||||
const CAT_COLORS = ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6'];
|
||||
const CAT_NAMES = ['语义对象', '对象行为', '约束规则', '编排流程'];
|
||||
const nodeMap = {}; NODES.forEach(n => nodeMap[n.id] = n);
|
||||
|
||||
document.getElementById('s-nodes').textContent = NODES.length;
|
||||
document.getElementById('s-links').textContent = LINKS.length;
|
||||
for (let i = 0; i < 4; i++) document.getElementById('cnt-' + i).textContent = NODES.filter(n => n.category === i).length;
|
||||
|
||||
// 场景按钮
|
||||
const scColors = { 'WF.ProblemSolving': '#F59E0B', 'WF.ArticleGen': '#3B82F6', 'WF.KnowledgeUpdate': '#10B981', 'WF.CognitionUpgrade': '#EF4444' };
|
||||
Object.entries(SCENARIOS).forEach(([key, sc]) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'scenario-btn'; btn.dataset.key = key;
|
||||
btn.style.setProperty('--sc-color', scColors[key] || '#8B5CF6');
|
||||
btn.innerHTML = `<span class="sc-dot"></span><span class="sc-label">${sc.label}</span><span class="sc-count">${sc.nodes.length}节点</span>`;
|
||||
btn.onclick = () => activateScenario(key);
|
||||
document.getElementById('scenario-buttons').appendChild(btn);
|
||||
});
|
||||
|
||||
// 构建 ECharts 选项
|
||||
function buildOption(highlightIds = null) {
|
||||
const filteredLinks = showRuleEdges ? LINKS : LINKS.filter(l => l.relType !== 'reference');
|
||||
const nodes = NODES.map(n => {
|
||||
const hi = highlightIds ? highlightIds.includes(n.id) : true;
|
||||
const cv = catFilter === null || n.category === catFilter;
|
||||
const op = (hi && cv) ? 1 : 0.08;
|
||||
return {
|
||||
id: n.id, name: n.name, category: n.category,
|
||||
symbolSize: n.symbolSize * (hi ? 1 : 0.85),
|
||||
label: { show: showLabels && hi && cv, fontSize: n.category === 3 ? 11 : (n.category === 0 ? 10 : 9), fontWeight: n.category <= 1 ? 'bold' : 'normal' },
|
||||
itemStyle: {
|
||||
color: CAT_COLORS[n.category], opacity: op,
|
||||
shadowBlur: hi && n.tag && n.tag.includes('⭐') ? 20 : (hi ? 8 : 0),
|
||||
shadowColor: hi ? CAT_COLORS[n.category] : 'transparent',
|
||||
borderColor: hi && n.tag && n.tag.includes('⭐') ? '#FFD700' : CAT_COLORS[n.category],
|
||||
borderWidth: hi && n.tag && n.tag.includes('⭐') ? 2.5 : 0,
|
||||
}, _data: n,
|
||||
};
|
||||
});
|
||||
const links = filteredLinks.map(l => {
|
||||
const sv = highlightIds ? highlightIds.includes(l.source) : true;
|
||||
const tv = highlightIds ? highlightIds.includes(l.target) : true;
|
||||
const v = sv && tv;
|
||||
return {
|
||||
source: l.source, target: l.target,
|
||||
label: { show: v && showLabels && l.relType !== 'reference', formatter: l.label, fontSize: 9, color: '#8b949e' },
|
||||
lineStyle: { ...l.lineStyle, opacity: v ? (l.relType === 'reference' ? 0.5 : 0.75) : 0.03, curveness: 0.2, type: l.lineStyle.type || 'solid' },
|
||||
symbol: ['none', 'arrow'], symbolSize: [0, 7],
|
||||
};
|
||||
});
|
||||
return {
|
||||
backgroundColor: '#0a0e1a',
|
||||
tooltip: {
|
||||
trigger: 'item', backgroundColor: 'rgba(15,22,40,0.95)', borderColor: '#2d2d55', textStyle: { color: '#e6edf3', fontSize: 12 },
|
||||
formatter: p => { if (p.dataType !== 'node') return ''; const n = nodeMap[p.data.id]; if (!n) return ''; return `<div style="max-width:260px"><div style="color:${CAT_COLORS[n.category]};font-weight:bold;font-size:13px;margin-bottom:4px">${n.name.replace(/\n/g, ' ')}</div><div style="color:#8b949e;font-size:10px;margin-bottom:6px">[${CAT_NAMES[n.category]}] ${n.tag || ''}</div><div style="color:#c9d1d9;font-size:11px;line-height:1.6;white-space:pre-wrap">${n.desc || ''}</div></div>`; }
|
||||
},
|
||||
legend: { show: false },
|
||||
series: [{
|
||||
type: 'graph', layout: 'force', animation: true, animationDuration: 800, animationEasingUpdate: 'quinticInOut',
|
||||
data: nodes, links: links,
|
||||
categories: CATEGORIES.map((c, i) => ({ name: c.name, itemStyle: { color: CAT_COLORS[i] } })),
|
||||
roam: true, draggable: true, focusNodeAdjacency: false,
|
||||
symbol: (v, p) => { const c = p.data.category; return c === 3 ? 'diamond' : c === 2 ? 'roundRect' : 'circle'; },
|
||||
label: { show: showLabels, position: 'bottom', formatter: '{b}', color: '#c9d1d9', textBorderColor: '#0a0e1a', textBorderWidth: 3 },
|
||||
edgeLabel: { show: showLabels, fontSize: 9, color: '#8b949e' },
|
||||
force: { repulsion: 320, edgeLength: [80, 200], gravity: 0.06, layoutAnimation: true, friction: 0.6 },
|
||||
lineStyle: { curveness: 0.2 },
|
||||
emphasis: { focus: 'adjacency', scale: true, itemStyle: { shadowBlur: 20 }, lineStyle: { width: 3, opacity: 1 }, label: { show: true } },
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
chart = echarts.init(document.getElementById('chart'), 'dark');
|
||||
chart.setOption(buildOption());
|
||||
chart.on('click', p => { if (p.dataType === 'node') showDetail(p.data.id); });
|
||||
chart.on('dblclick', p => { if (p.dataType === 'node' && SCENARIOS[p.data.id]) activateScenario(p.data.id); });
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
}
|
||||
|
||||
function showDetail(nodeId) {
|
||||
const n = nodeMap[nodeId]; if (!n) return;
|
||||
document.getElementById('detail-placeholder').style.display = 'none';
|
||||
document.getElementById('detail-content').style.display = 'flex';
|
||||
const color = CAT_COLORS[n.category];
|
||||
const badge = document.getElementById('detail-type-badge');
|
||||
badge.textContent = CAT_NAMES[n.category]; badge.style.background = color + '22'; badge.style.color = color; badge.style.border = `1px solid ${color}55`;
|
||||
document.getElementById('detail-name').textContent = n.name.replace(/\n/g, ' ');
|
||||
document.getElementById('detail-name').style.color = color;
|
||||
document.getElementById('detail-tag').textContent = n.tag || '';
|
||||
document.getElementById('detail-desc').textContent = n.desc || '暂无描述';
|
||||
const connList = document.getElementById('conn-list'); connList.innerHTML = '';
|
||||
const rels = LINKS.filter(l => l.source === nodeId || l.target === nodeId);
|
||||
if (!rels.length) { connList.innerHTML = '<div style="color:#484f58;font-size:11px">无直接关联</div>'; return; }
|
||||
rels.slice(0, 12).forEach(l => {
|
||||
const isOut = l.source === nodeId, otherId = isOut ? l.target : l.source, o = nodeMap[otherId];
|
||||
if (!o) return;
|
||||
const div = document.createElement('div'); div.className = 'conn-item';
|
||||
div.innerHTML = `<div class="conn-dot" style="background:${CAT_COLORS[o.category]}"></div><div class="conn-text">${o.name.replace(/\n/g, ' ')}</div><div class="conn-rel">${isOut ? '→' : '←'} ${l.label}</div>`;
|
||||
div.onclick = () => showDetail(otherId); connList.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function activateScenario(key) {
|
||||
activeScenario = key;
|
||||
document.querySelectorAll('.scenario-btn').forEach(b => b.classList.toggle('active', b.dataset.key === key));
|
||||
chart.setOption(buildOption(SCENARIOS[key].nodes));
|
||||
showDetail(key);
|
||||
if (SIMULATION_STEPS[key]) simInit(key); else simHide();
|
||||
}
|
||||
function clearScenario() {
|
||||
activeScenario = null; catFilter = null; simHide(); simClearTimer(); simCurrentStep = -1;
|
||||
document.querySelectorAll('.scenario-btn').forEach(b => b.classList.remove('active'));
|
||||
chart.setOption(buildOption());
|
||||
}
|
||||
function filterByCategory(cat) {
|
||||
catFilter = catFilter === cat ? null : cat;
|
||||
chart.setOption(buildOption(activeScenario ? SCENARIOS[activeScenario].nodes : null));
|
||||
}
|
||||
|
||||
document.getElementById('btn-reset').onclick = () => { clearScenario(); chart.dispatchAction({ type: 'restore' }); };
|
||||
document.getElementById('btn-labels').onclick = function () { showLabels = !showLabels; this.textContent = showLabels ? '标签 ON' : '标签 OFF'; this.classList.toggle('active', showLabels); chart.setOption(buildOption(activeScenario ? SCENARIOS[activeScenario].nodes : null)); };
|
||||
document.getElementById('btn-rules').onclick = function () { showRuleEdges = !showRuleEdges; this.textContent = showRuleEdges ? '规则边' : '规则边 OFF'; this.classList.toggle('active', showRuleEdges); chart.setOption(buildOption(activeScenario ? SCENARIOS[activeScenario].nodes : null)); };
|
||||
document.getElementById('btn-fullscreen').onclick = () => { if (!document.fullscreenElement) document.documentElement.requestFullscreen(); else document.exitFullscreen(); };
|
||||
|
||||
initChart();
|
||||
|
||||
// ================================================================
|
||||
// 动态模拟系统
|
||||
// ================================================================
|
||||
let simCurrentStep = -1, simScenarioKey = null, simAutoTimer = null, simIsPlaying = false;
|
||||
const PHASE_META = {
|
||||
process: { color: '#8B5CF6', bg: 'rgba(139,92,246,0.15)', label: '🟣 流程启动' },
|
||||
behavior: { color: '#10B981', bg: 'rgba(16,185,129,0.15)', label: '🟢 行为调用' },
|
||||
rule: { color: '#F59E0B', bg: 'rgba(245,158,11,0.15)', label: '🟠 规则引用' },
|
||||
entity: { color: '#3B82F6', bg: 'rgba(59,130,246,0.15)', label: '🔵 对象生成' },
|
||||
complete: { color: '#FFD700', bg: 'rgba(255,215,0,0.15)', label: '✅ 完成' },
|
||||
};
|
||||
|
||||
function simInit(key) {
|
||||
simScenarioKey = key; simCurrentStep = -1; simClearTimer(); simIsPlaying = false;
|
||||
document.getElementById('sim-auto-btn').classList.remove('playing');
|
||||
document.getElementById('sim-auto-btn').textContent = '⏵ 自动';
|
||||
document.getElementById('sim-title').textContent = (SCENARIOS[key] ? SCENARIOS[key].label : key) + ' — 动态模拟';
|
||||
document.getElementById('sim-step-badge').textContent = '0 / ' + SIMULATION_STEPS[key].length;
|
||||
document.getElementById('sim-step-title').textContent = '点击「下一步」开始模拟';
|
||||
document.getElementById('sim-step-desc').textContent = '逐步展示问题分析与解决的完整调用链路。';
|
||||
document.getElementById('sim-progress-bar').style.width = '0%';
|
||||
document.getElementById('sim-phase-badge').textContent = '';
|
||||
document.getElementById('sim-phase-badge').style.cssText = '';
|
||||
document.getElementById('sim-prev-btn').disabled = true;
|
||||
document.getElementById('sim-next-btn').disabled = false;
|
||||
document.getElementById('sim-next-btn').textContent = '下一步 ▶';
|
||||
document.getElementById('sim-bar').classList.add('visible');
|
||||
}
|
||||
function simHide() { document.getElementById('sim-bar').classList.remove('visible'); }
|
||||
function simClearTimer() { if (simAutoTimer) { clearInterval(simAutoTimer); simAutoTimer = null; } }
|
||||
|
||||
function simStep(dir) {
|
||||
if (!simScenarioKey) return;
|
||||
const steps = SIMULATION_STEPS[simScenarioKey], next = simCurrentStep + dir;
|
||||
if (next < 0 || next >= steps.length) return;
|
||||
simCurrentStep = next; simRenderStep(steps[simCurrentStep]);
|
||||
}
|
||||
|
||||
function simRenderStep(step) {
|
||||
const steps = SIMULATION_STEPS[simScenarioKey], total = steps.length;
|
||||
document.getElementById('sim-progress-bar').style.width = Math.round((step.stepNo / total) * 100) + '%';
|
||||
document.getElementById('sim-step-badge').textContent = step.stepNo + ' / ' + total;
|
||||
const pm = PHASE_META[step.phase] || PHASE_META.process;
|
||||
const badge = document.getElementById('sim-phase-badge');
|
||||
badge.textContent = pm.label; badge.style.cssText = `color:${pm.color};background:${pm.bg};border:1px solid ${pm.color}44;font-size:10px;padding:3px 9px;border-radius:10px;font-weight:700;`;
|
||||
document.getElementById('sim-step-title').textContent = step.title;
|
||||
document.getElementById('sim-step-desc').textContent = step.desc;
|
||||
document.getElementById('sim-prev-btn').disabled = simCurrentStep <= 0;
|
||||
const isLast = simCurrentStep >= total - 1;
|
||||
document.getElementById('sim-next-btn').disabled = isLast;
|
||||
document.getElementById('sim-next-btn').textContent = isLast ? '已完成 ✓' : '下一步 ▶';
|
||||
if (isLast && simIsPlaying) simToggleAuto();
|
||||
|
||||
const allNodeIds = step.allNodes, newNodeIds = step.newNodes || [];
|
||||
const activeEdgeSet = new Set(step.activeEdges.map(e => e.source + '__' + e.target));
|
||||
const edgeSeqMap = {}; step.activeEdges.forEach(e => { edgeSeqMap[e.source + '__' + e.target] = e; });
|
||||
|
||||
const simNodes = NODES.map(n => {
|
||||
const isActive = allNodeIds.includes(n.id), isNew = newNodeIds.includes(n.id) && !step.isComplete, isComp = step.isComplete;
|
||||
let sb = 0, sc = 'transparent', bw = 0, bc = CAT_COLORS[n.category];
|
||||
if (isNew) { sb = 30; sc = CAT_COLORS[n.category]; bw = 3; bc = '#fff'; }
|
||||
else if (isActive && isComp) { sb = 16; sc = CAT_COLORS[n.category]; }
|
||||
else if (isActive) { sb = 10; sc = CAT_COLORS[n.category]; }
|
||||
return {
|
||||
id: n.id, name: n.name, category: n.category,
|
||||
symbolSize: n.symbolSize * (isNew ? 1.22 : (isActive ? 1 : 0.8)),
|
||||
label: { show: isActive, fontSize: n.category === 3 ? 11 : (n.category === 0 ? 10 : 9), fontWeight: isNew ? 'bold' : 'normal', color: isNew ? '#fff' : '#c9d1d9', textBorderColor: '#0a0e1a', textBorderWidth: 3 },
|
||||
itemStyle: { color: CAT_COLORS[n.category], opacity: isActive ? 1 : 0.05, shadowBlur: sb, shadowColor: sc, borderWidth: bw, borderColor: bc },
|
||||
};
|
||||
});
|
||||
|
||||
const simLinks = LINKS.map(l => {
|
||||
const ek = l.source + '__' + l.target, ia = activeEdgeSet.has(ek), ei = edgeSeqMap[ek];
|
||||
return {
|
||||
source: l.source, target: l.target,
|
||||
label: { show: ia && !!ei, formatter: ei ? ei.label : l.label, fontSize: 10, fontWeight: 'bold', color: '#FFD700', textBorderColor: '#0a0e1a', textBorderWidth: 2, backgroundColor: 'rgba(0,0,0,0.55)', padding: [2, 5], borderRadius: 4 },
|
||||
lineStyle: { color: ia ? (pm.color || '#10B981') : l.lineStyle.color, width: ia ? (l.lineStyle.width || 1.5) + 1.5 : (l.lineStyle.width || 1.5), opacity: ia ? 0.92 : 0.03, curveness: 0.2, type: 'solid', shadowBlur: ia ? 12 : 0, shadowColor: ia ? (pm.color || '#10B981') : 'transparent' },
|
||||
symbol: ['none', 'arrow'], symbolSize: ia ? [0, 9] : [0, 6],
|
||||
};
|
||||
});
|
||||
|
||||
chart.setOption({ series: [{ data: simNodes, links: simLinks, force: { repulsion: 320, edgeLength: [80, 200], gravity: 0.06, layoutAnimation: false } }] }, false);
|
||||
}
|
||||
|
||||
function simToggleAuto() {
|
||||
simIsPlaying = !simIsPlaying; const btn = document.getElementById('sim-auto-btn');
|
||||
if (simIsPlaying) {
|
||||
btn.classList.add('playing'); btn.textContent = '⏸ 暂停';
|
||||
simAutoTimer = setInterval(() => { const s = SIMULATION_STEPS[simScenarioKey]; if (simCurrentStep >= s.length - 1) simToggleAuto(); else simStep(1); }, 2200);
|
||||
} else { btn.classList.remove('playing'); btn.textContent = '⏵ 自动'; simClearTimer(); }
|
||||
}
|
||||
function simStop() {
|
||||
simClearTimer(); simIsPlaying = false; simCurrentStep = -1; simScenarioKey = null; simHide();
|
||||
if (activeScenario) chart.setOption(buildOption(SCENARIOS[activeScenario].nodes)); else chart.setOption(buildOption());
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user