chore: 删除过时的文档和截图

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:25:44 +08:00
parent 5d956dd712
commit e34d4bcd37
46 changed files with 0 additions and 8130 deletions

View File

@@ -1,835 +0,0 @@
# 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] 全链路审计日志
---
*本文档将随项目开发持续更新*

View File

@@ -1,337 +0,0 @@
<!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>

View File

@@ -1,692 +0,0 @@
<!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>

View File

@@ -1,909 +0,0 @@
<!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>

View File

@@ -1,304 +0,0 @@
<template>
<div class="min-h-screen bg-[#121212] text-gray-200 p-6">
<!-- 顶部开关 -->
<div class="flex justify-end items-center mb-6">
<span class="text-gray-400 mr-3">记忆管理 已启用</span>
<button class="w-12 h-6 bg-green-500 rounded-full relative">
<span class="absolute right-1 top-1 w-4 h-4 bg-white rounded-full"></span>
</button>
</div>
<!-- 统计卡片区域 -->
<div class="grid grid-cols-4 gap-4 mb-6">
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
<div class="text-3xl font-bold text-white mb-1">15</div>
<div class="text-sm text-gray-400">总记忆数</div>
</div>
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
<div class="text-3xl font-bold text-white mb-1">0.78</div>
<div class="text-sm text-gray-400">平均分数</div>
</div>
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
<div class="text-3xl font-bold text-cyan-400 mb-1">6</div>
<div class="text-sm text-gray-400">经验</div>
</div>
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
<div class="text-3xl font-bold text-red-400 mb-1">9</div>
<div class="text-sm text-gray-400">经验教训</div>
</div>
</div>
<!-- 搜索栏 -->
<div class="mb-4">
<div class="relative">
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
<input
type="text"
placeholder="搜索记忆内容..."
class="w-full bg-[#0a0a0a] border border-gray-700 rounded-lg py-3 pl-10 pr-4 text-gray-200 focus:outline-none focus:border-gray-600"
>
</div>
</div>
<!-- 筛选与操作栏 -->
<div class="flex items-center justify-between mb-6">
<div class="flex items-center space-x-3">
<select class="bg-[#0a0a0a] border border-gray-700 rounded-lg py-2 px-4 text-gray-200 focus:outline-none">
<option>全部类型</option>
<option>经验</option>
<option>经验教训</option>
</select>
<button class="flex items-center space-x-2 bg-[#1e1e1e] border border-gray-700 rounded-lg py-2 px-4 text-gray-200 hover:bg-[#2a2a2a]">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span>刷新</span>
</button>
<button class="flex items-center space-x-2 bg-indigo-600 rounded-lg py-2 px-4 text-white hover:bg-indigo-700">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
</svg>
<span>LLM 智能审查</span>
</button>
</div>
</div>
<!-- 表格区域 -->
<div class="bg-[#1e1e1e] rounded-xl overflow-hidden">
<table class="w-full">
<thead class="bg-[#0a0a0a]">
<tr>
<th class="w-10 px-4 py-3">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">类型</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">内容</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">分数</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">创建时间</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<!-- 表格行 1 -->
<tr class="hover:bg-[#252525]">
<td class="px-4 py-4">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
</td>
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
当任务明确要求获取外部信息搜索最新数据、新闻、财报、行业报告等模型必须主动调用搜索工具而非仅依赖内部知识。应在系统提示中明确要求「必须使用搜索工具获取XX最新数据」首次迭代即调用搜索工具采用「搜索→分析→输出」的递进式流程避免模型陷入纯文字生成的空转循环。
<div class="text-xs text-gray-500 mt-1">主体: 外部信息获取规范 · 属性: 工具调用原则</div>
</td>
<td class="px-4 py-4">
<span class="text-emerald-400 font-medium">0.95</span>
</td>
<td class="px-4 py-4 text-sm text-gray-400">3/10 15:35</td>
<td class="px-4 py-4">
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-white">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
</tr>
<!-- 表格行 2 -->
<tr class="hover:bg-[#252525]">
<td class="px-4 py-4">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
</td>
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
当任务明确要求获取外部信息搜索最新数据、新闻、财报、行业报告等模型必须调用搜索工具而非仅依赖内部知识。应在系统提示中明确要求「必须使用搜索工具获取XX最新数据」并建立工具调用检测机制确保任务执行路径正确。
<div class="text-xs text-gray-500 mt-1">主体: 工具调用 · 属性: 错误教训</div>
</td>
<td class="px-4 py-4">
<span class="text-emerald-400 font-medium">0.95</span>
</td>
<td class="px-4 py-4 text-sm text-gray-400">3/10 12:34</td>
<td class="px-4 py-4">
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-white">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
</tr>
<!-- 表格行 3 -->
<tr class="hover:bg-[#252525]">
<td class="px-4 py-4">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
</td>
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
任务执行应采用「一次性完整输出」策略避免分批次小幅输出导致的迭代空转。对于代码生成、文件写入等任务应要求模型一次性完成方案设计与工具执行的完整流程将确认性回复并入上一次响应将冗余迭代压缩合并复杂任务控制在2-3次迭代内完成。
<div class="text-xs text-gray-500 mt-1">主体: 任务执行效率优化 · 属性: 迭代策略原则</div>
</td>
<td class="px-4 py-4">
<span class="text-emerald-400 font-medium">0.92</span>
</td>
<td class="px-4 py-4 text-sm text-gray-400">3/10 15:35</td>
<td class="px-4 py-4">
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-white">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
</tr>
<!-- 表格行 4 -->
<tr class="hover:bg-[#252525]">
<td class="px-4 py-4">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
</td>
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
任务执行过程中应建立工具调用检测机制监控模型是否按要求调用了必要的工具。若迭代中工具调用数为0说明执行路径不正确应触发异常处理或提示而非继续无效迭代。
<div class="text-xs text-gray-500 mt-1">主体: 执行路径监控 · 属性: 质量控制机制</div>
</td>
<td class="px-4 py-4">
<span class="text-emerald-400 font-medium">0.88</span>
</td>
<td class="px-4 py-4 text-sm text-gray-400">3/10 15:35</td>
<td class="px-4 py-4">
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-white">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
</tr>
<!-- 表格行 5 -->
<tr class="hover:bg-[#252525]">
<td class="px-4 py-4">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
</td>
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
任务执行应采用「搜索→分析→输出」的递进式流程避免无效迭代和空转。纯文字生成的重复迭代应压缩或合并确认性回复应并入上一次响应。简单任务应在1-2次迭代内完成避免模型陷入重复思考或无意义的自我确认。
<div class="text-xs text-gray-500 mt-1">主体: 迭代效率 · 属性: 错误教训</div>
</td>
<td class="px-4 py-4">
<span class="text-emerald-400 font-medium">0.85</span>
</td>
<td class="px-4 py-4 text-sm text-gray-400">3/10 12:34</td>
<td class="px-4 py-4">
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-white">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
</tr>
<!-- 表格行 6 -->
<tr class="hover:bg-[#252525]">
<td class="px-4 py-4">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
</td>
<td class="px-4 py-4">
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
</td>
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
对于代码生成或文件写入类任务,应要求模型一次性完整输出或按模块批量输出,避免分批次的小幅输出导致迭代次数过多、执行效率低下。
<div class="text-xs text-gray-500 mt-1">主体: 代码生成策略 · 属性: 错误教训</div>
</td>
<td class="px-4 py-4">
<span class="text-amber-400 font-medium">0.80</span>
</td>
<td class="px-4 py-4 text-sm text-gray-400">3/10 12:34</td>
<td class="px-4 py-4">
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-white">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 底部文本区域 -->
<div class="mt-6 px-4 text-gray-400 text-sm">
<div class="mb-2">任务执行复盘发现问题:## 任务执行分析</div>
<div class="mb-2">### 问题诊断</div>
<div class="mb-2">**核心问题模型在9次迭代中未执行任何工具调用**</div>
<div>从迭代记录看前9次迭代工具调用数均为0说明模型一直在"空转"生成文字未实际搜索最新数据。直到第10次才产出完整报告包含2025年9月销量等最新数据说明最后一次才真正调用了搜索工具。</div>
</div>
</div>
</template>
<script setup>
// 这里可以添加交互逻辑
</script>
<style scoped>
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: #333;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #444;
}
</style>

View File

@@ -1,212 +0,0 @@
<!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.tailwindcss.com"></script>
<style>
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: #333;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #444;
}
</style>
</head>
<body class="bg-[#0f0f0f] text-gray-200 min-h-screen font-sans">
<!-- 顶部导航栏 -->
<header class="flex items-center justify-between px-4 py-2 border-b border-gray-800 bg-[#0a0a0a]">
<!-- 左侧状态区 -->
<div class="flex items-center space-x-3">
<span class="text-gray-300 font-medium">default</span>
<span class="flex items-center text-green-400 text-sm">
<span class="w-2 h-2 rounded-full bg-green-400 mr-1.5"></span>
运行中
</span>
<span class="bg-blue-600/20 text-blue-400 text-xs px-2 py-0.5 rounded">网页访问</span>
<span class="text-gray-400 text-sm">远程地址</span>
<span class="text-gray-400 text-sm">1 端点</span>
</div>
<!-- 右侧操作区 -->
<div class="flex items-center space-x-4">
<button class="flex items-center space-x-1 border border-blue-500 bg-[#0a0a0a] text-white px-3 py-1 rounded text-sm hover:bg-blue-900/20 transition">
<span>×</span>
<span>断开</span>
</button>
<button class="text-gray-400 hover:text-white transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
</button>
<button class="text-gray-400 hover:text-white transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path>
</svg>
</button>
<button class="text-gray-400 hover:text-white transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
</svg>
</button>
<div class="flex items-center ml-4">
<span class="text-gray-400 mr-2 text-sm">计划任务 已启用</span>
<button class="w-10 h-5 bg-green-500 rounded-full relative">
<span class="absolute right-0.5 top-0.5 w-4 h-4 bg-white rounded-full"></span>
</button>
</div>
</div>
</header>
<!-- 主体内容区 -->
<main class="px-6 py-5">
<!-- 标题与操作栏 -->
<div class="flex justify-between items-start mb-5">
<!-- 标题+标签页 -->
<div>
<h1 class="text-xl font-bold text-white mb-3">计划任务</h1>
<div class="flex items-center space-x-6">
<button class="text-blue-400 border-b-2 border-blue-500 px-1 py-2 font-medium text-sm -mb-[1px]">进行中 3</button>
<button class="text-gray-400 hover:text-white px-1 py-2 text-sm transition">已完成 0</button>
<button class="text-gray-400 hover:text-white px-1 py-2 text-sm transition">全部 3</button>
</div>
</div>
<!-- 右侧操作+搜索区 -->
<div class="flex flex-col items-end space-y-2">
<div class="flex items-center space-x-3">
<button class="flex items-center space-x-1 text-gray-400 hover:text-white text-sm transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span>刷新</span>
</button>
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded text-sm flex items-center space-x-1 transition">
<span>+</span>
<span>新建任务</span>
</button>
</div>
<div class="relative">
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
<input
type="text"
placeholder="搜索任务..."
class="w-full bg-[#0a0a0a] border border-gray-700 rounded-lg py-2 pl-10 pr-4 text-sm text-gray-200 focus:outline-none focus:border-gray-600"
>
</div>
</div>
</div>
<!-- 任务列表 -->
<div class="space-y-3">
<!-- 任务1活人感心跳 -->
<div class="bg-[#1a1a1a] rounded-lg p-4 hover:bg-[#1e1e1e] transition">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
<span class="w-2 h-2 rounded-full bg-green-500"></span>
<span class="text-white font-medium">活人感心跳</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">系统任务</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">Agent 任务</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">间隔重复</span>
</div>
<div class="flex items-center space-x-1">
<button class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-3 py-1 rounded transition">暂停</button>
<button class="text-gray-400 hover:text-white p-1 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
</div>
</div>
<div class="flex items-center flex-wrap gap-x-8 gap-y-1 text-xs text-gray-400 mb-2">
<span>状态: 已调度</span>
<span>触发方式: 间隔重复 30 分钟</span>
<span>下次执行: 2026/03/10 16:26</span>
<span>上次执行: 2026/03/10 15:56</span>
<span>通知通道: -</span>
<span>执行次数: 13</span>
</div>
<div class="text-sm text-gray-300">
检查是否需要发送主动消息 (问候/提醒/跟进)
</div>
</div>
<!-- 任务2记忆整理 -->
<div class="bg-[#1a1a1a] rounded-lg p-4 hover:bg-[#1e1e1e] transition">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
<span class="w-2 h-2 rounded-full bg-green-500"></span>
<span class="text-white font-medium">记忆整理</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">系统任务</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">Agent 任务</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">间隔重复</span>
</div>
<div class="flex items-center space-x-1">
<button class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-3 py-1 rounded transition">暂停</button>
<button class="text-gray-400 hover:text-white p-1 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
</div>
</div>
<div class="flex items-center flex-wrap gap-x-8 gap-y-1 text-xs text-gray-400 mb-2">
<span>状态: 已调度</span>
<span>触发方式: 间隔重复 3 小时</span>
<span>下次执行: 2026/03/10 18:35</span>
<span>上次执行: 2026/03/10 15:35</span>
<span>通知通道: -</span>
<span>执行次数: 2</span>
</div>
<div class="text-sm text-gray-300">
执行记忆整理: 整理对话历史, 提取精华记忆, 刷新 MEMORY.md
</div>
</div>
<!-- 任务3系统自检 -->
<div class="bg-[#1a1a1a] rounded-lg p-4 hover:bg-[#1e1e1e] transition">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-2">
<span class="w-2 h-2 rounded-full bg-green-500"></span>
<span class="text-white font-medium">系统自检</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">系统任务</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">Agent 任务</span>
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">每天</span>
</div>
<div class="flex items-center space-x-1">
<button class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-3 py-1 rounded transition">暂停</button>
<button class="text-gray-400 hover:text-white p-1 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
</div>
</div>
<div class="flex items-center flex-wrap gap-x-8 gap-y-1 text-xs text-gray-400 mb-2">
<span>状态: 已调度</span>
<span>触发方式: 每天 04:00</span>
<span>下次执行: 2026/03/11 04:00</span>
<span>上次执行: 从未</span>
<span>通知通道: -</span>
<span>执行次数: 0</span>
</div>
<div class="text-sm text-gray-300">
执行系统自检: 分析 ERROR 日志, 尝试修复工具问题, 生成报告
</div>
</div>
</div>
</main>
</body>
</html>

View File

@@ -1,874 +0,0 @@
# 多智能体群聊系统实现计划
## 项目概述
实现类似"一人公司"的多智能体群聊系统,支持多个 Agent 在群聊中讨论问题,类似于人类的团队会议。
### 核心特性
- **三阶段流程**: 观点提出 → 讨论完善 → CEO 总结决策
- **智能轮数**: AI 判断讨论是否充分,自动决定轮数
- **用户插话**: 用户可以随时打断发表意见
- **角色扮演**: 可配置不同角色的 AgentCEO、CTO、Designer 等)
- **复用架构**: 复用现有的 LLM、ToolRegistry、SessionManager
---
## 一、系统架构
### 1.1 整体架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ Agent Group Chat System │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ GroupChatManager ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ Presenter │ │ Discusser │ │ Summarizer │ ││
│ │ │ Controller │ │ Controller │ │ Controller │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
│ │ │ │ │ ││
│ │ └────────────────┴────────────────┘ ││
│ │ │ ││
│ │ SmartRoundController ││
│ └─────────────────────────────────────────────────────────────┘│
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CEO Agent │ │ CTO Agent │ │Designer Agent│ │
│ │ Participant │ │ Participant │ │ Participant │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────────┴──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │GroupContext │ │
│ │(共享上下文) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 核心组件
| 组件 | 职责 | 文件位置 |
|------|------|----------|
| **GroupChatManager** | 群聊管理器,控制整体流程 | `group_chat/manager.py` |
| **Participant** | 参与群聊的 Agent 实例 | `group_chat/participant.py` |
| **StageController** | 阶段控制器,管理各阶段 | `group_chat/stages/controller.py` |
| **PresenterStage** | 观点提出阶段 | `group_chat/stages/presenter.py` |
| **DiscusserStage** | 讨论完善阶段 | `group_chat/stages/discusser.py` |
| **SummarizerStage** | 总结决策阶段 | `group_chat/stages/summarizer.py` |
| **SmartRoundController** | 智能轮数控制 | `group_chat/round_controller.py` |
| **GroupContext** | 共享讨论上下文 | `group_chat/context.py` |
| **GroupMessage** | 群聊消息 | `group_chat/message.py` |
---
## 二、数据结构设计
### 2.1 消息定义
```python
# group_chat/message.py
from typing import Optional
from pydantic import BaseModel, Field
from datetime import datetime
from enum import Enum
class MessageStage(str, Enum):
"""消息所属阶段"""
PRESENT = "present" # 观点提出
DISCUSS = "discuss" # 讨论完善
SUMMARIZE = "summarize" # 总结决策
class GroupMessage(BaseModel):
"""群聊消息"""
id: str = Field(..., description="消息唯一标识")
agent_id: str = Field(..., description="Agent ID")
agent_name: str = Field(..., description="Agent 名称")
agent_role: str = Field(..., description="Agent 角色")
content: str = Field(..., description="消息内容")
timestamp: datetime = Field(default_factory=datetime.now)
stage: MessageStage = Field(..., description="所属阶段")
round: int = Field(default=1, description="轮数")
replying_to: Optional[str] = Field(default=None, description="回复的消息ID")
class UserInterruption(BaseModel):
"""用户插话"""
id: str
content: str
timestamp: datetime = Field(default_factory=datetime.now)
stage: MessageStage
round: int
```
### 2.2 共享上下文
```python
# group_chat/context.py
from typing import Optional
from pydantic import BaseModel, Field
from .message import GroupMessage, UserInterruption
class GroupContext(BaseModel):
"""群聊共享上下文"""
topic: str = Field(..., description="讨论主题")
stage: str = Field(default="present", description="当前阶段")
round: int = Field(default=1, description="当前轮数")
messages: list[GroupMessage] = Field(default_factory=list)
user_interruptions: list[UserInterruption] = Field(default_factory=list)
final_decision: Optional[str] = Field(default=None, description="最终决策")
status: str = Field(default="running", description="running/completed/failed")
class GroupState(BaseModel):
"""群聊状态"""
context: GroupContext
participants: dict[str, dict] # {agent_id: config}
config: dict # 群聊配置
```
---
## 三、核心实现
### 3.1 Agent 角色定义
```python
# group_chat/roles.py
from typing import Optional
# 默认角色配置
DEFAULT_ROLES = {
"ceo": {
"id": "ceo",
"name": "CEO",
"role": "首席执行官",
"system_prompt": """你是一家公司的 CEO负责制定公司战略方向和重大决策。
你的特点是:高瞻远瞩、战略思维、注重长远利益。
在讨论中,你应该:
- 从公司整体战略角度分析问题
- 权衡短期和长期利益
- 做最终决策并承担责任
- 善于总结和归纳各方观点""",
"stages": ["present", "summarize"],
"is_final_decider": True,
"priority": 1
},
"cto": {
"id": "cto",
"name": "CTO",
"role": "首席技术官",
"system_prompt": """你是一家公司的 CTO负责技术决策和技术团队管理。
你的特点是:技术深厚、注重可行性、关注技术趋势。
在讨论中,你应该:
- 从技术角度分析方案的可行性和风险
- 提供技术实现建议
- 评估技术选型
- 关注系统的可扩展性和稳定性""",
"stages": ["present", "discuss"],
"is_final_decider": False,
"priority": 2
},
"designer": {
"id": "designer",
"name": "Designer",
"role": "产品设计师",
"system_prompt": """你是公司的产品设计师,负责用户体验和界面设计。
你的特点是:创意丰富、注重用户体验、审美在线。
在讨论中,你应该:
- 从用户角度分析问题
- 提出用户体验优化建议
- 关注产品细节
- 平衡美观和实用性""",
"stages": ["present", "discuss"],
"is_final_decider": False,
"priority": 3
},
"analyst": {
"id": "analyst",
"name": "Analyst",
"role": "数据分析师",
"system_prompt": """你是公司的数据分析师,负责数据分析和决策支持。
你的特点是:数据敏感、逻辑严谨、注重证据。
在讨论中,你应该:
- 用数据支撑观点
- 提供数据分析结果
- 指出数据驱动的机会和风险
- 关注关键指标""",
"stages": ["present", "discuss"],
"is_final_decider": False,
"priority": 4
}
}
```
### 3.2 Participant参与者 Agent
```python
# group_chat/participant.py
import uuid
from datetime import datetime
from typing import Optional
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import HumanMessage, SystemMessage
from .message import GroupMessage, MessageStage
from .context import GroupContext
class Participant:
"""参与群聊的 Agent"""
def __init__(
self,
llm: BaseChatModel,
config: dict,
stage: str = "present"
):
self.llm = llm
self.config = config
self.id = config["id"]
self.name = config["name"]
self.role = config["role"]
self.system_prompt = config["system_prompt"]
self.stages = config.get("stages", ["present", "discuss", "summarize"])
self.is_final_decider = config.get("is_final_decider", False)
async def generate_message(
self,
context: GroupContext,
stage: MessageStage,
round_num: int,
replying_to: Optional[str] = None
) -> GroupMessage:
"""生成消息"""
# 构建 prompt
prompt = self._build_prompt(context, stage, round_num)
# 调用 LLM
response = await self.llm.ainvoke([
SystemMessage(content=prompt),
HumanMessage(content=f"请就「{context.topic}」发表你的观点。")
])
content = response.content if hasattr(response, 'content') else str(response)
# 创建消息
message = GroupMessage(
id=str(uuid.uuid4()),
agent_id=self.id,
agent_name=self.name,
agent_role=self.role,
content=content,
timestamp=datetime.now(),
stage=stage,
round=round_num,
replying_to=replying_to
)
return message
def _build_prompt(self, context: GroupContext, stage: MessageStage, round_num: int) -> str:
"""构建 prompt"""
base_prompt = self.system_prompt
# 添加上下文
context_info = f"""
## 当前讨论
主题:{context.topic}
阶段:{stage.value}
轮数:{round_num}
## 历史消息
"""
# 添加历史消息(限制数量)
recent_messages = context.messages[-10:] if context.messages else []
for msg in recent_messages:
context_info += f"\n{msg.agent_name}{msg.content}\n"
# 添加用户插话
if context.user_interruptions:
context_info += "\n## 用户插话\n"
for interruption in context.user_interruptions[-3:]:
context_info += f"\n【用户】{interruption.content}\n"
# 阶段特定的指令
stage_instruction = {
"present": "请简洁地提出你的观点和建议。",
"discuss": "请回应其他人的观点,进行讨论和完善。",
"summarize": "请总结各方观点,给出最终决策建议。"
}
full_prompt = f"{base_prompt}\n\n{context_info}\n\n{stage_instruction.get(stage.value, '')}"
return full_prompt
def can_participate(self, stage: MessageStage) -> bool:
"""判断是否可以参与当前阶段"""
return stage.value in self.stages
```
### 3.3 智能轮数控制器
```python
# group_chat/round_controller.py
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import SystemMessage, HumanMessage
from .context import GroupContext
from .message import MessageStage
class SmartRoundController:
"""智能轮数控制器"""
def __init__(self, llm: BaseChatModel, max_rounds: int = 3):
self.llm = llm
self.max_rounds = max_rounds
async def should_continue(
self,
context: GroupContext,
stage: MessageStage
) -> tuple[bool, str]:
"""
判断是否继续下一轮
Returns:
(是否继续, 原因)
"""
# 达到最大轮数
if context.round >= self.max_rounds:
return False, "max_rounds_reached"
# 构建判断 prompt
prompt = self._build_judge_prompt(context, stage)
# 调用 LLM 判断
response = await self.llm.ainvoke([
SystemMessage(content=prompt),
HumanMessage(content="请判断讨论是否已经充分,是否需要更多轮数。")
])
content = response.content.lower() if hasattr(response, 'content') else str(response)
# 解析判断结果
if "充分" in content or "足够" in content or "不需要" in content:
return False, "discussion_sufficient"
elif "不充分" in content or "不够" in content or "需要" in content:
return True, "need_more_discussion"
# 默认继续
return True, "default_continue"
def _build_judge_prompt(self, context: GroupContext, stage: MessageStage) -> str:
"""构建判断 prompt"""
messages_summary = "\n".join([
f"{msg.agent_name}{msg.content[:200]}..."
for msg in context.messages[-5:]
])
return f"""你是一个讨论质量评估专家。请判断当前的讨论是否已经充分。
## 讨论信息
主题:{context.topic}
当前阶段:{stage.value}
当前轮数:{context.round}
最大轮数:{self.max_rounds}
## 最近的讨论内容
{messages_summary}
## 判断标准
- 各方观点是否已经充分表达?
- 是否有建设性的讨论和回应?
- 是否已经形成明确的结论或方向?
## 请输出
如果讨论已经充分,请输出"充分",并简要说明原因。
如果还需要更多讨论,请输出"不充分",并说明需要讨论哪些方面。
"""
```
### 3.4 群聊管理器
```python
# group_chat/manager.py
import uuid
from typing import Optional
from datetime import datetime
from langchain_core.language_models import BaseChatModel
from .context import GroupContext
from .message import GroupMessage, MessageStage, UserInterruption
from .participant import Participant
from .roles import DEFAULT_ROLES
from .round_controller import SmartRoundController
class GroupChatManager:
"""群聊管理器"""
def __init__(
self,
llm: BaseChatModel,
roles: dict = None,
max_rounds: int = 3,
enable_user_interrupt: bool = True
):
self.llm = llm
self.roles = roles or DEFAULT_ROLES
self.max_rounds = max_rounds
self.enable_user_interrupt = enable_user_interrupt
# 初始化组件
self.round_controller = SmartRoundController(llm, max_rounds)
# 运行时状态
self.context: Optional[GroupContext] = None
self.participants: dict[str, Participant] = {}
async def start_chat(self, topic: str) -> dict:
"""开始群聊"""
# 创建上下文
self.context = GroupContext(
topic=topic,
stage="present",
round=1,
messages=[],
user_interruptions=[],
status="running"
)
# 创建参与者
self.participants = {
role_id: Participant(self.llm, config)
for role_id, config in self.roles.items()
}
# 按优先级排序参与者
sorted_participants = sorted(
self.participants.values(),
key=lambda p: p.config.get("priority", 999)
)
# 开始第一阶段
result = await self._run_stage(MessageStage.PRESENT, sorted_participants)
return result
async def _run_stage(
self,
stage: MessageStage,
participants: list[Participant]
) -> dict:
"""运行指定阶段"""
# 更新上下文
self.context.stage = stage.value
# 按阶段获取参与者
stage_participants = [p for p in participants if p.can_participate(stage)]
# 执行多轮
for round_num in range(1, self.max_rounds + 1):
self.context.round = round_num
# 当前阶段参与者发言
messages = await self._run_round(stage, stage_participants, round_num)
# 智能判断是否继续
should_continue, reason = await self.round_controller.should_continue(
self.context, stage
)
if not should_continue:
break
# 阶段完成后的处理
if stage == MessageStage.PRESENT:
# 进入讨论阶段
if any(p.can_participate(MessageStage.DISCUSS) for p in participants):
return await self._run_stage(MessageStage.DISCUSS, participants)
elif stage == MessageStage.DISCUSS:
# 进入总结阶段
summarizer = next(
(p for p in participants if p.is_final_decider),
participants[0]
)
return await self._run_stage(MessageStage.SUMMARIZE, [summarizer])
elif stage == MessageStage.SUMMARIZE:
# 完成
self.context.status = "completed"
return self._build_result()
async def _run_round(
self,
stage: MessageStage,
participants: list[Participant],
round_num: int
) -> list[GroupMessage]:
"""运行一轮发言"""
messages = []
for participant in participants:
# 获取前一轮的最新消息(用于回复)
replying_to = None
if self.context.messages:
last_message = self.context.messages[-1]
if last_message.agent_id != participant.id:
replying_to = last_message.id
# 生成消息
message = await participant.generate_message(
context=self.context,
stage=stage,
round_num=round_num,
replying_to=replying_to
)
# 保存消息
self.context.messages.append(message)
messages.append(message)
return messages
async def add_interruption(self, content: str) -> dict:
"""添加用户插话"""
if not self.enable_user_interrupt:
return {"error": "用户插话已禁用"}
interruption = UserInterruption(
id=str(uuid.uuid4()),
content=content,
timestamp=datetime.now(),
stage=MessageStage(self.context.stage),
round=self.context.round
)
self.context.user_interruptions.append(interruption)
return {"success": True, "interruption_id": interruption.id}
def get_messages(self) -> list[GroupMessage]:
"""获取所有消息"""
return self.context.messages if self.context else []
def get_result(self) -> dict:
"""获取结果"""
return self._build_result()
def _build_result(self) -> dict:
"""构建结果"""
return {
"topic": self.context.topic,
"status": self.context.status,
"messages": [
{
"agent_name": m.agent_name,
"agent_role": m.agent_role,
"content": m.content,
"stage": m.stage.value,
"round": m.round
}
for m in self.context.messages
],
"final_decision": self.context.final_decision,
"user_interruptions": [
{"content": i.content, "timestamp": i.timestamp.isoformat()}
for i in self.context.user_interruptions
]
}
```
---
## 四、阶段实现
### 4.1 Presenter Stage观点提出
```python
# group_chat/stages/presenter.py
from typing import list
from ..participant import Participant
from ..message import GroupMessage
class PresenterStage:
"""观点提出阶段"""
async def execute(
self,
participants: list[Participant],
context
) -> list[GroupMessage]:
"""执行观点提出"""
messages = []
# 按优先级顺序发言
for participant in participants:
if not participant.can_participate("present"):
continue
message = await participant.generate_message(
context=context,
stage="present",
round=context.round
)
messages.append(message)
context.messages.append(message)
return messages
```
### 4.2 Discusser Stage讨论完善
```python
# group_chat/stages/discusser.py
from ..participant import Participant
from ..message import GroupMessage
class DiscusserStage:
"""讨论完善阶段"""
async def execute(
self,
participants: list[Participant],
context
) -> list[GroupMessage]:
"""执行讨论完善"""
messages = []
# 获取上一轮的消息
last_round_messages = [
m for m in context.messages
if m.stage == "present" and m.round == context.round - 1
]
for participant in participants:
if not participant.can_participate("discuss"):
continue
# 选择要回复的消息
replying_to = None
for msg in reversed(last_round_messages):
if msg.agent_id != participant.id:
replying_to = msg.id
break
message = await participant.generate_message(
context=context,
stage="discuss",
round=context.round,
replying_to=replying_to
)
messages.append(message)
context.messages.append(message)
return messages
```
### 4.3 Summarizer Stage总结决策
```python
# group_chat/stages/summarizer.py
from ..participant import Participant
from ..message import GroupMessage
class SummarizerStage:
"""总结决策阶段"""
async def execute(
self,
participants: list[Participant],
context
) -> GroupMessage:
"""执行总结决策"""
# CEO 总结
summarizer = next(
(p for p in participants if p.is_final_decider),
participants[0]
)
message = await summarizer.generate_message(
context=context,
stage="summarize",
round=context.round
)
# 保存最终决策
context.final_decision = message.content
context.messages.append(message)
return message
```
---
## 五、与现有系统集成
### 5.1 复用现有组件
```python
# group_chat/integration.py
from typing import Optional
from app.llm.factory import LLMFactory
from app.agent.tools.registry import ToolRegistry
from app.agent.memory.session import SessionManager
from .manager import GroupChatManager
class GroupChatSystem:
"""群聊系统 - 集成现有组件"""
def __init__(
self,
llm_provider: str = "openai",
openai_api_key: Optional[str] = None,
anthropic_api_key: Optional[str] = None,
roles: dict = None,
max_rounds: int = 3,
enable_user_interrupt: bool = True
):
# 初始化 LLM Factory
self.llm_factory = LLMFactory(
provider=llm_provider,
openai_api_key=openai_api_key,
anthropic_api_key=anthropic_api_key
)
# 初始化 Tool Registry
self.tool_registry = ToolRegistry()
# 初始化 Session Manager
self.session_manager = SessionManager()
# 配置
self.roles = roles
self.max_rounds = max_rounds
self.enable_user_interrupt = enable_user_interrupt
async def start_group_chat(
self,
topic: str,
session_id: str = None
) -> dict:
"""开始群聊"""
# 获取 LLM
llm = self.llm_factory.get_llm()
# 创建群聊管理器
manager = GroupChatManager(
llm=llm,
roles=self.roles,
max_rounds=self.max_rounds,
enable_user_interrupt=self.enable_user_interrupt
)
# 开始群聊
result = await manager.start_chat(topic)
# 保存到 session
if session_id:
self.session_manager.add_message(session_id, "user", topic)
self.session_manager.add_message(
session_id,
"assistant",
result.get("final_decision", str(result))
)
return result
async def add_message(
self,
message: str,
session_id: str
) -> dict:
"""添加用户消息(插话)"""
# 获取 session 中的 manager
# ... 实现获取逻辑
return manager.add_interruption(message)
```
---
## 六、文件结构
```
agent/app/agent/multi/
├── group_chat/
│ ├── __init__.py
│ ├── roles.py # Agent 角色定义
│ ├── message.py # 消息类型
│ ├── context.py # 共享上下文
│ ├── participant.py # 参与者 Agent
│ ├── manager.py # 群聊管理器
│ ├── round_controller.py # 智能轮数控制器
│ ├── stages/
│ │ ├── __init__.py
│ │ ├── controller.py # 阶段控制器
│ │ ├── presenter.py # 观点提出阶段
│ │ ├── discusser.py # 讨论完善阶段
│ │ └── summarizer.py # 总结决策阶段
│ └── integration.py # 与现有系统集成
```
---
## 七、实现顺序
1. **Phase 1: 基础架构**
- 定义数据类型 (message.py, context.py)
- 创建角色配置 (roles.py)
2. **Phase 2: 核心组件**
- 实现 Participant (participant.py)
- 实现 SmartRoundController (round_controller.py)
3. **Phase 3: 阶段实现**
- 实现 PresenterStage
- 实现 DiscusserStage
- 实现 SummarizerStage
4. **Phase 4: 集成**
- 实现 GroupChatManager
- 与现有系统集成
---
## 八、测试计划
1. **单元测试**: 测试各 Participant 的消息生成
2. **集成测试**: 测试完整的群聊流程
3. **轮数控制测试**: 测试智能轮数判断
4. **用户插话测试**: 测试插话机制
5. **端到端测试**: 模拟真实群聊场景

View File

@@ -1,80 +0,0 @@
# Notes: 多智能体群聊系统研究
## 核心概念
### 群聊系统 vs 之前的 Supervisor 系统
| 特性 | Supervisor 系统 | 群聊系统 |
|------|----------------|----------|
| 流程 | 线性:规划 → 执行 → 汇总 | 多阶段循环 |
| Agent 关系 | 层级Supervisor 管理 Workers | 平等协作 |
| 通信方式 | 单向:任务分发 | 多向:互相讨论 |
| 决策方式 | Supervisor 决定 | CEO 最终决策 |
| 用户参与 | 旁观 | 可插话 |
### 设计模式
#### 1. 流水线模式
```
Stage 1 (Presenter) → Stage 2 (Discusser) → Stage 3 (Summarizer)
```
#### 2. 消息传递
- 每个 Stage 维护一个消息队列
- Agent 的输出成为下一个 Agent 的输入
- 使用 Shared Context 存储共享状态
#### 3. 智能轮数
```python
class SmartRoundController:
def should_continue(self, stage, round_num, messages):
# 使用 LLM 判断是否继续
prompt = f"""
当前阶段: {stage}
当前轮数: {round_num}
讨论内容: {messages}
讨论是否已经充分?是否需要更多轮数?
"""
return llm.judge(prompt)
```
## 复用现有架构
### 可复用的组件
1. **LLM Factory** - 语言模型
2. **Tool Registry** - 工具注册
3. **Session Manager** - 会话管理
4. **Agent Executor** - Agent 执行逻辑(部分)
### 需要新增的组件
1. **GroupChatManager** - 群聊管理器
2. **Participant** - 参与者 Agent
3. **Stage Controller** - 阶段控制器
4. **SmartRoundController** - 智能轮数控制器
## 关键数据结构
### GroupMessage
```python
class GroupMessage(BaseModel):
id: str
agent_id: str
agent_name: str
content: str
timestamp: datetime
stage: str # presenter/discusser/summarizer
round: int
replying_to: Optional[str] # 回复的消息 ID
```
### GroupContext
```python
class GroupContext(BaseModel):
topic: str # 讨论主题
stage: str # 当前阶段
round: int # 当前轮数
messages: list[GroupMessage] # 所有消息
user_interruptions: list[str] # 用户插话
final_decision: Optional[str] # 最终决策
```

View File

@@ -1,32 +0,0 @@
# Task Plan: 多智能体群聊系统实现计划
## Goal
实现类似"一人公司"的多智能体群聊系统,支持头脑风暴、任务协作、角色扮演等多模式讨论。
## Phases
- [x] Phase 1: 基础架构设计和核心组件规划
- [ ] Phase 2: 群聊管理器 (GroupChatManager) 实现
- [ ] Phase 3: 参与 Agent (Participant) 实现
- [ ] Phase 4: 三个阶段实现 (Presenter/Discusser/Summarizer)
- [ ] Phase 5: 智能轮数控制实现
- [ ] Phase 6: 用户插话机制实现
- [ ] Phase 7: 与现有系统集成和 API 接口
## Key Questions
1. 如何复用现有的 Supervisor + Workers 架构?
2. 如何实现智能轮数控制?
3. 如何处理用户插话?
## Decisions Made
- 架构:任务流水线模式(观点提出 → 讨论完善 → 总结决策)
- 决策机制CEO Agent 最终决策
- 轮数控制AI 智能判断
- 用户参与:插话模式
- 复用策略:复用现有 LLM、ToolRegistry、SessionManager
## Status
**Currently in Phase 1** - 系统架构设计和核心组件规划已完成
## 实现计划文件
- `group_chat_implementation_plan.md` - 详细实现计划
- `group_chat_notes.md` - 研究笔记

View File

@@ -1,709 +0,0 @@
# 多智能体联动系统实现计划
## 项目概述
基于 LangGraph 实现类似 OpenClaw 的多智能体协作系统,采用 Supervisor + Workers 层级架构。
### 核心特性
- **任务规划**: Supervisor 分析任务并生成执行计划
- **动态分发**: LLM 自主决策调用哪个 Worker
- **并行执行**: 支持多个 Worker 同时处理任务
- **结果汇总**: Supervisor 汇总所有 Worker 结果
- **迭代优化**: 支持 Review 机制和迭代重试
---
## 一、系统架构
### 1.1 整体架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ MultiAgentSystem │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Supervisor Agent │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Planner │ │ Dispatcher │ │ Aggregator │ │ │
│ │ │ (任务规划) │ │ (任务分发) │ │ (结果汇总) │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Research │ │ Coder │ │ Review │ │
│ │ Worker │ │ Worker │ │ Worker │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────────┴──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Shared State │ │
│ │ (共享状态) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 核心组件
| 组件 | 职责 | 文件位置 |
|------|------|----------|
| **SupervisorAgent** | 任务分析、规划、分发、汇总 | `agent/multi/supervisor.py` |
| **BaseWorker** | Worker 基类,定义执行接口 | `agent/multi/workers/base.py` |
| **ResearchWorker** | 信息搜索和调研 | `agent/multi/workers/research.py` |
| **CoderWorker** | 代码编写和修改 | `agent/multi/workers/coder.py` |
| **ReviewWorker** | 结果检查和评审 | `agent/multi/workers/review.py` |
| **SharedState** | 跨 Agent 共享状态 | `agent/multi/state.py` |
| **TaskQueue** | 任务队列管理 | `agent/multi/queue.py` |
| **MultiAgentGraph** | LangGraph 流程编排 | `agent/multi/graph.py` |
---
## 二、数据结构设计
### 2.1 Agent State 定义
```python
# agent/multi/types.py
from typing import TypedDict, Annotated, Optional
from operator import add
from pydantic import BaseModel
class TaskItem(BaseModel):
"""单个任务项"""
id: str
description: str
assigned_agent: str # research / coder / review
status: str # pending / running / completed / failed
result: Optional[dict] = None
error: Optional[str] = None
retry_count: int = 0
class AgentState(TypedDict):
"""贯穿整个图的 Agent 状态"""
# 用户输入
original_task: str # 原始任务描述
# 任务规划
task_plan: list[TaskItem] # 分解后的任务列表
current_task_index: int # 当前执行的任务索引
# 执行结果
results: dict # {task_id: result}
# 流程控制
iteration: int # 当前迭代次数
next_node: str # 下一个节点名称
# 共享上下文
shared_context: dict # Agent 间共享的数据
# 最终输出
final_output: str
status: str # running / completed / failed
```
### 2.2 Supervisor 输出结构
```python
# Supervisor 的结构化输出
class SupervisorDecision(BaseModel):
"""Supervisor 的决策"""
analysis: str # 任务分析
task_plan: list[TaskItem] # 任务计划
need_aggregation: bool # 是否需要汇总
next_worker: str # 下一个执行的 Worker
```
---
## 三、核心实现
### 3.1 Supervisor Agent
```python
# agent/multi/supervisor.py
from langchain_core.language_models import BaseChatModel
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel
from typing import Type
from .types import AgentState, TaskItem, SupervisorDecision
from .prompts import SUPERVISOR_SYSTEM_PROMPT
class SupervisorAgent:
"""Supervisor Agent - 负责任务规划和分发"""
def __init__(
self,
llm: BaseChatModel,
max_iterations: int = 3
):
self.llm = llm
self.max_iterations = max_iterations
self.output_parser = PydanticOutputParser(pydantic_object=SupervisorDecision)
def create_node(self):
"""创建 Supervisor 节点"""
return self._supervisor_node
async def _supervisor_node(self, state: AgentState) -> dict:
"""Supervisor 节点逻辑"""
# 首次调用:分析任务并生成计划
if not state.get("task_plan"):
decision = await self._plan_tasks(state["original_task"])
return {
"task_plan": decision.task_plan,
"next_node": decision.next_worker,
"current_task_index": 0,
"shared_context": {"task_analysis": decision.analysis}
}
# 检查是否需要继续
current_task = state["task_plan"][state["current_task_index"]]
if current_task["status"] == "completed":
# 当前任务完成,检查是否还有更多任务
if state["current_task_index"] + 1 < len(state["task_plan"]):
next_index = state["current_task_index"] + 1
next_task = state["task_plan"][next_index]
return {
"current_task_index": next_index,
"next_node": next_task["assigned_agent"]
}
else:
# 所有任务完成,进入汇总
return {"next_node": "aggregate"}
elif current_task["status"] == "failed":
# 任务失败,检查是否超过最大重试
if current_task["retry_count"] >= self.max_iterations:
return {"next_node": "aggregate", "status": "failed"}
else:
# 重试
return {"next_node": current_task["assigned_agent"]}
return {"next_node": state.get("next_node", "aggregate")}
async def _plan_tasks(self, task: str) -> SupervisorDecision:
"""调用 LLM 生成任务计划"""
prompt = SUPERVISOR_SYSTEM_PROMPT.format(task=task)
response = await self.llm.ainvoke([
{"role": "system", "content": prompt},
{"role": "user", "content": "请分析任务并制定执行计划。"}
])
# 解析 LLM 输出为结构化决策
# ... (实现解析逻辑)
return decision
```
### 3.2 Worker 基类
```python
# agent/multi/workers/base.py
from abc import ABC, abstractmethod
from typing import Any
from langchain_core.language_models import BaseChatModel
from ..types import AgentState
class BaseWorker(ABC):
"""Worker Agent 基类"""
def __init__(
self,
llm: BaseChatModel,
name: str,
system_prompt: str,
tools: list = None
):
self.llm = llm
self.name = name
self.system_prompt = system_prompt
self.tools = tools or []
@abstractmethod
async def execute(self, task: TaskItem, context: dict) -> dict:
"""执行任务"""
pass
def create_node(self):
"""创建 LangGraph 节点"""
async def node(state: AgentState) -> dict:
task = state["task_plan"][state["current_task_index"]]
result = await self.execute(task, state.get("shared_context", {}))
# 更新状态
return {
"results": {task.id: result},
"task_plan": self._update_task_status(
state["task_plan"],
task.id,
"completed" if result.get("success") else "failed"
),
"shared_context": {**state.get("shared_context", {}), **result.get("context", {})}
}
return node
def _update_task_status(self, tasks: list, task_id: str, status: str) -> list:
"""更新任务状态"""
return [
{**task, "status": status} if task["id"] == task_id else task
for task in tasks
]
```
### 3.3 任务队列(可选:支持并行执行)
```python
# agent/multi/queue.py
import asyncio
from typing import Any, Callable
from dataclasses import dataclass
from enum import Enum
class TaskStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class QueuedTask:
id: str
agent_name: str
task_data: Any
status: TaskStatus = TaskStatus.PENDING
result: Any = None
error: str = None
class TaskQueue:
"""任务队列 - 支持并行执行多个 Worker"""
def __init__(self, max_concurrent: int = 3):
self.max_concurrent = max_concurrent
self.queue: asyncio.Queue = asyncio.Queue()
self.results: dict = {}
self._running = 0
async def add_task(self, task: QueuedTask):
"""添加任务到队列"""
await self.queue.put(task)
async def execute_all(self, worker_factory: Callable):
"""执行所有任务"""
async def worker():
while True:
try:
task = self.queue.get_nowait()
except asyncio.QueueEmpty:
break
self._running += 1
task.status = TaskStatus.Running
try:
worker_instance = worker_factory(task.agent_name)
task.result = await worker_instance.execute(task.task_data)
task.status = TaskStatus.COMPLETED
except Exception as e:
task.status = TaskStatus.FAILED
task.error = str(e)
finally:
self._running -= 1
self.results[task.id] = task
# 启动多个 worker 协程
workers = [asyncio.create_task(worker()) for _ in range(self.max_concurrent)]
await asyncio.gather(*workers)
return self.results
```
### 3.4 LangGraph 流程编排
```python
# agent/multi/graph.py
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from .types import AgentState
from .supervisor import SupervisorAgent
from .workers.research import ResearchWorker
from .workers.coder import CoderWorker
from .workers.review import ReviewWorker
from .aggregator import ResultAggregator
def create_multi_agent_graph(
llm,
tool_registry,
max_iterations: int = 3
) -> StateGraph:
"""创建多 Agent 流程图"""
# 初始化组件
supervisor = SupervisorAgent(llm, max_iterations)
research_worker = ResearchWorker(llm, tool_registry)
coder_worker = CoderWorker(llm, tool_registry)
review_worker = ReviewWorker(llm, tool_registry)
aggregator = ResultAggregator(llm)
# 创建图
graph = StateGraph(AgentState)
# 添加节点
graph.add_node("supervisor", supervisor.create_node())
graph.add_node("research", research_worker.create_node())
graph.add_node("coder", coder_worker.create_node())
graph.add_node("review", review_worker.create_node())
graph.add_node("aggregate", aggregator.create_node())
# 设置入口
graph.set_entry_point("supervisor")
# 添加边
graph.add_edge("supervisor", "research")
graph.add_edge("research", "review")
graph.add_edge("coder", "review")
# 条件边:从 review 回到 supervisor
def should_continue(state: AgentState) -> str:
if state.get("status") == "failed":
return "aggregate"
if state.get("iteration", 0) >= max_iterations:
return "aggregate"
if state.get("current_task_index", 0) >= len(state.get("task_plan", [])):
return "aggregate"
return "supervisor"
graph.add_conditional_edges(
"review",
should_continue,
{
"supervisor": "supervisor",
"aggregate": "aggregate"
}
)
# 结束节点
graph.add_edge("aggregate", END)
return graph.compile()
```
---
## 四、Prompt 设计
### 4.1 Supervisor System Prompt
```python
# agent/multi/prompts.py
SUPERVISOR_SYSTEM_PROMPT = """你是一个任务规划专家Supervisor。你的职责是将复杂任务分解为可执行的子任务并分配给合适的执行 Agent。
## 可用的 Worker Agent
- **research**: 信息搜索和调研
- **coder**: 代码编写、修改和调试
- **review**: 结果检查、质量评审
## 任务
{task}
## 请按以下步骤执行
### 步骤 1: 任务分析
分析任务的性质,确定需要哪些步骤来完成。
### 步骤 2: 任务分解
将任务分解为独立的子任务。每个子任务应该:
- 描述清晰
- 可以由单个 Agent 完成
- 有明确的完成标准
### 步骤 3: 分配 Agent
为每个子任务选择最合适的执行 Agent。
### 步骤 4: 确定执行顺序
如果有依赖关系,确定正确的执行顺序。
## 输出格式
请以 JSON 格式输出你的决策:
```json
{{
"analysis": "任务分析...",
"task_plan": [
{{
"id": "task_1",
"description": "子任务描述",
"assigned_agent": "research"
}},
{{
"id": "task_2",
"description": "子任务描述",
"assigned_agent": "coder"
}}
],
"need_aggregation": true,
"next_worker": "research"
}}
```
## 注意
- 如果任务很简单,可以只分配给一个 Agent
- 如果任务需要迭代优化,确保有 review 环节
- 考虑任务之间的依赖关系
"""
```
### 4.2 Review Worker Prompt
```python
REVIEW_SYSTEM_PROMPT = """你是一个代码和结果评审专家Reviewer。你的职责是检查任务执行结果是否符合要求。
## 任务描述
{task_description}
## 执行结果
{execution_result}
## 检查标准
1. 结果是否完整解决了原始任务?
2. 输出格式是否正确?
3. 是否存在明显的错误或遗漏?
4. 代码是否有潜在问题?
## 请输出评审结果
```json
{{
"passed": true/false,
"issues": [
{{"severity": "high/medium/low", "description": "问题描述"}}
],
"suggestions": ["改进建议"]
}}
```
"""
```
---
## 五、迭代控制
### 5.1 迭代逻辑
```python
# agent/multi/iteration.py
from typing import Optional
class IterationController:
"""迭代控制器"""
def __init__(self, max_iterations: int = 3):
self.max_iterations = max_iterations
def should_continue(
self,
iteration: int,
task_status: str,
review_result: dict
) -> tuple[bool, str]:
"""
判断是否继续迭代
Returns:
(是否继续, 原因)
"""
# 超过最大迭代次数
if iteration >= self.max_iterations:
return False, "max_iterations_reached"
# 任务成功完成
if task_status == "completed" and review_result.get("passed"):
return False, "task_completed"
# 任务失败且不可重试
if task_status == "failed" and not review_result.get("retryable"):
return False, "task_failed"
# 需要重试
if review_result.get("issues") and review_result.get("passed") is False:
return True, "needs_retry"
return True, "continue"
def get_next_action(
self,
review_result: dict,
current_worker: str
) -> str:
"""确定下一步动作"""
if review_result.get("passed"):
return "supervisor" # 返回 Supervisor
# 根据问题类型决定下一步
issues = review_result.get("issues", [])
high_severity = any(i.get("severity") == "high" for i in issues)
if high_severity:
# 严重问题,重新执行相同任务
return current_worker
else:
# 轻微问题,可以继续
return "supervisor"
```
---
## 六、与现有系统集成
### 6.1 复用现有组件
```python
# agent/multi/integration.py
from app.agent.core.agent import AgentManager
from app.agent.tools.registry import ToolRegistry
from app.agent.memory.session import SessionManager
from app.llm.factory import LLMFactory
class MultiAgentSystem:
"""多智能体系统 - 集成现有组件"""
def __init__(self, config: dict):
# 复用现有 LLM Factory
self.llm_factory = LLMFactory(
provider=config.get("llm_provider", "openai"),
openai_api_key=config.get("openai_api_key"),
anthropic_api_key=config.get("anthropic_api_key")
)
# 复用现有 Tool Registry
self.tool_registry = ToolRegistry()
self._register_default_tools()
# 复用现有 Session Manager
self.session_manager = SessionManager()
# 配置
self.max_iterations = config.get("max_iterations", 3)
def _register_default_tools(self):
"""注册默认工具"""
# 从现有 Agent 复制工具注册逻辑
from app.agent.tools.impl import search, calculator
self.tool_registry.register(
name="search",
func=search.search_web,
description="Search the web",
security_level="safe"
)
# ... 其他工具
async def execute(self, task: str, session_id: str = None) -> dict:
"""执行多 Agent 任务"""
# 创建 LangGraph
from .graph import create_multi_agent_graph
llm = self.llm_factory.get_llm()
graph = create_multi_agent_graph(
llm=llm,
tool_registry=self.tool_registry,
max_iterations=self.max_iterations
)
# 初始化状态
initial_state = {
"original_task": task,
"task_plan": [],
"current_task_index": 0,
"results": {},
"iteration": 0,
"next_node": "supervisor",
"shared_context": {},
"final_output": "",
"status": "running"
}
# 执行
result = await graph.ainvoke(initial_state)
# 保存到 session
if session_id:
self.session_manager.add_message(session_id, "user", task)
self.session_manager.add_message(session_id, "assistant", result["final_output"])
return result
```
---
## 七、文件结构
```
agent/
├── __init__.py
├── multi/
│ ├── __init__.py
│ ├── types.py # 数据类型定义
│ ├── prompts.py # Prompt 模板
│ ├── supervisor.py # Supervisor Agent
│ ├── graph.py # LangGraph 流程图
│ ├── iteration.py # 迭代控制
│ ├── integration.py # 与现有系统集成
│ ├── queue.py # 任务队列(可选)
│ └── workers/
│ ├── __init__.py
│ ├── base.py # Worker 基类
│ ├── research.py # Research Worker
│ ├── coder.py # Coder Worker
│ └── review.py # Review Worker
```
---
## 八、实现顺序
1. **Phase 1: 基础架构**
- 定义数据类型 (types.py)
- 创建 Prompt 模板 (prompts.py)
2. **Phase 2: Supervisor**
- 实现 SupervisorAgent
- 实现任务规划和分发逻辑
3. **Phase 3: Workers**
- 实现 BaseWorker
- 实现 ResearchWorker
- 实现 CoderWorker
- 实现 ReviewWorker
4. **Phase 4: 流程编排**
- 实现 LangGraph 流程图
- 添加条件边和迭代控制
5. **Phase 5: 集成**
- 与现有 Agent 系统集成
- 添加 API 接口
---
## 九、测试计划
1. **单元测试**: 测试各 Worker 的执行逻辑
2. **集成测试**: 测试完整的 Supervisor + Workers 流程
3. **迭代测试**: 测试重试和迭代逻辑
4. **端到端测试**: 模拟真实任务执行

View File

@@ -1,107 +0,0 @@
# Notes: LangGraph 多智能体研究
## 核心概念
### LangGraph 基础
- **StateGraph**: 有向无环图DAG节点是 Agent/函数,边是流转逻辑
- **State**: 贯穿整个图流动的状态对象
- **Node**: 执行单元(可以是 Agent、函数、条件判断
- **Edge**: 连接节点的边支持条件边conditional edges
### Supervisor + Workers 模式参考
#### 1. LangChain 官方 Supervisor 示例
```python
from langgraph.prebuilt import create_react_agent
from langgraph.graph import StateGraph, END
# 定义 Workers
research_agent = create_react_agent(llm, tools=[search])
coder_agent = create_react_agent(llm, tools=[write_file])
# 定义 Supervisor 节点
def supervisor_node(state):
# LLM 决定下一步调用哪个 Agent
response = llm.with_structured_output(SupervisorOutput).invoke(
[SystemMessage(content=SUPERVISOR_PROMPT)] + state["messages"]
)
return {"next": response.next_agent}
# 构建图
graph = StateGraph(AgentState)
graph.add_node("supervisor", supervisor_node)
graph.add_node("research", research_agent)
graph.add_node("code", coder_agent)
```
#### 2. 状态定义
```python
from typing import TypedDict, Annotated
import operator
class AgentState(TypedDict):
messages: Annotated[list, operator.add]
task: str
plan: list
results: dict
iteration: int
next: str # 控制下一步流向
```
#### 3. 条件边实现
```python
def should_continue(state):
if state["iteration"] >= MAX_ITERATIONS:
return "end"
if state.get("task_complete"):
return "end"
return "continue"
graph.add_conditional_edges(
"review",
should_continue,
{
"continue": "supervisor",
"end": END
}
)
```
## 设计决策
### 架构优势
1. **清晰的分层**: Supervisor 负责任务规划Workers 负责执行
2. **可扩展**: 容易添加新的 Worker 类型
3. **可控**: 迭代次数全局配置
4. **灵活**: 支持条件分支和循环
### 需要解决的问题
1. **Supervisor 如何做规划**: 需要设计 prompt 让 LLM 生成任务列表
2. **任务队列**: 需要支持并行分发多个 Worker
3. **共享上下文**: 需要设计数据结构在 Agent 间共享状态
4. **Review 机制**: 需要定义检查标准和重试逻辑
## 关键 Prompt 设计
### Supervisor System Prompt
```
你是一个任务规划专家Supervisor。用户的任务是{task}
请按以下步骤执行:
1. 分析任务需求和约束
2. 将任务分解为可执行的子任务
3. 为每个子任务选择合适的执行 Agent
- research: 信息搜索和调研
- coder: 代码编写和修改
- review: 结果检查和评审
4. 确定执行顺序和依赖关系
当前任务进度:{progress}
共享上下文:{context}
请输出你的决策,格式如下:
- 需要执行的子任务列表
- 每个任务的执行 Agent
- 任务执行顺序
- 是否需要汇总结果
```

View File

@@ -1,33 +0,0 @@
# Task Plan: 多智能体联动系统实现计划
## Goal
基于 LangGraph 实现类似 OpenClaw 的多智能体联动系统,支持任务规划、动态分发、结果汇总和迭代优化。
## Phases
- [x] Phase 1: 系统架构设计和核心组件规划
- [ ] Phase 2: Supervisor Agent 实现
- [ ] Phase 3: Worker Agent 实现
- [ ] Phase 4: 任务队列和共享上下文实现
- [ ] Phase 5: State Machine 流程控制实现
- [ ] Phase 6: 迭代控制和 Review 机制实现
- [ ] Phase 7: 与现有 Agent 系统集成
## Key Questions
1. 如何用 LangGraph 实现 Supervisor + Workers 架构?
2. 如何设计任务队列支持并行执行?
3. 如何实现共享上下文在 Agent 间传递?
4. 如何控制迭代次数和流程分支?
## Decisions Made
- 架构Supervisor + Workers 层级模式
- 协作方式LLM 自主决策任务分配
- 通信共享内存Shared Context
- 迭代控制:全局最大迭代次数配置
- Workers 定义:复用现有 tool_registry
## Status
**Currently in Phase 1** - 系统架构设计和核心组件规划已完成
## 实现计划文件
- `implementation_plan.md` - 详细的实现计划
- `notes.md` - LangGraph 研究笔记

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

View File

@@ -1,137 +0,0 @@
# AI-Core 文档解析服务 API 对接文档
## 服务地址
```
localhost:50051
```
## VLM 配置(可选)
VLM 用于提升图片文件的解析效果。如果不配置 VLM则使用默认的 MarkItDown 解析。
### 方式一:环境变量
```bash
# 设置环境变量
export VLM_API_KEY="your-api-key"
export VLM_PROVIDER="openai" # openai / anthropic / qwen
export VLM_MODEL="gpt-4o"
```
### 方式二:配置文件
`ai-core/config.yaml` 中配置:
```yaml
vlm:
enabled: true
provider: "openai" # openai / anthropic / qwen
model: "gpt-4o" # 模型名称
api_key: "sk-xxx" # API Key
base_url: "" # 自定义 API 地址(可选)
prompt: "" # 自定义提示词(可选)
```
### 支持的 VLM 提供商
| 提供商 | 示例模型 |
|--------|----------|
| openai | gpt-4o, gpt-4o-mini |
| anthropic | claude-3-opus, claude-3-sonnet |
| qwen | qwen-vl-max, qwen2-vl-72b |
---
## gRPC API 定义
### 1. ParseDocument - 解析文档
**请求 (ParseRequest)**
```protobuf
message ParseRequest {
string file_url = 1; // 文件 URL必填
string file_name = 2; // 文件名,带扩展名(必填)
string file_type = 3; // 文件类型(可选,自动检测)
string parser_engine = 4; // 解析引擎(可选)
map<string, string> engine_overrides = 5; // 引擎配置
// VLM 配置(可选,优先级高于全局配置)
VLMConfig vlm_config = 6;
}
message VLMConfig {
bool enabled = 1;
string provider = 2;
string model = 3;
string api_key = 4;
string base_url = 5;
string prompt = 6;
}
```
**响应 (ParseResponse)**
```protobuf
message ParseResponse {
bool success = 1;
string content = 2; // Markdown 内容
string message = 3;
int32 content_length = 4;
string file_type = 5;
string parser_engine = 6;
}
```
---
## Golang 对接示例
### 基础调用(无 VLM 配置时使用 MarkItDown
```go
req := &pb.ParseRequest{
FileUrl: "https://example.com/document.pdf",
FileName: "document.pdf",
}
resp, client.ParseDocument(ctx, req)
```
### 带 VLM 配置调用
```go
req := &pb.ParseRequest{
FileUrl: "https://example.com/image.png",
FileName: "image.png",
VlmConfig: &pb.VLMConfig{
Enabled: Provider: "open true,
ai",
Model: "gpt-4o",
ApiKey: "sk-xxx",
},
}
resp, err := client.ParseDocument(ctx, req)
```
---
## 解析逻辑
1. **图片文件** (jpg, png, webp 等)
- 如果配置了 VLM → 使用 VLM 解析
- 如果没有配置 VLM → 使用 MarkItDown 解析
2. **PDF/DOCX/PPTX 等文档**
- 使用 MarkItDown 解析
3. **VLM 优先级**
- gRPC 请求中的 vlm_config > 全局配置config.yaml/环境变量)
---
## 注意事项
1. **文件 URL**: 必须是可直接访问的 URL
2. **文件名**: 必须带扩展名(如 `.pdf`, `.png`
3. **返回内容**: Markdown 格式文本

View File

@@ -1,20 +0,0 @@
# AI 服务需求 TODO
## 2026年3月
### 2026-03-09
- [ ] **AI-Core 文档解析服务对接**
- 服务ai-core (gRPC, 端口 50051)
- 功能将文档PDF/DOCX/PPTX/图片等)转换为 Markdown
- 对接方式gRPC 调用
- 详细需求:[ai-core-api.md](./ai-core-api.md)
- [ ] **VLM 调用支持**
- 支持 OpenAI GPT-4o、Anthropic Claude、阿里 Qwen VL
- 通过 vlm_config 配置启用
- 适用场景图片文件jpg, png 等)自动使用 VLM 解析
---
> 需求完成后请完成者打 ✔

View File

@@ -1,18 +0,0 @@
# API 接口文档
## 目录
### Database 相关
- [检查数据库连接并获取表结构](database-check.md)
- [创建数据库配置](database-create.md)
- [获取数据库列表](database-list.md)
- [获取子表列表](subtable-list.md)
### Neo4j 相关
- [Neo4j 连接测试](neo4j-check.md)
---
> 接口如有更新,请同步更新此文档

View File

@@ -1,200 +0,0 @@
# 检查数据库连接并获取表结构
## 接口地址
```
POST /database/check
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| db_type | string | 是 | 数据库类型:`mysql``postgres``neo4j` |
| host | string | 是 | 数据库主机 |
| port | int | 是 | 数据库端口 |
| username | string | 是 | 用户名 |
| password | string | 否 | 密码 |
| database | string | 是 | 数据库名 |
| charset | string | 否 | 字符集,默认 `utf8mb4` |
| ssl_mode | string | 否 | SSL 模式 |
| database_id | string | 否 | 已存在的数据库ID用于恢复字段映射 |
| uri | string | 否 | Neo4j 连接地址(如 bolt://localhost:7687Neo4j 类型必填 |
## 请求示例MySQL/PostgreSQL
```json
{
"db_type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "students",
"charset": "utf8mb4",
"database_id": "xxx-xxx-xxx"
}
```
## 请求示例Neo4j
```json
{
"db_type": "neo4j",
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"database": "neo4j"
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否连接成功 |
| message | string | 消息 |
| database | string | 数据库名 |
| tables | array | 表结构列表MySQL/PostgreSQL |
| graphs | object | 图谱数据Neo4j |
### tables[] 详情(关系型数据库)
| 参数 | 类型 | 说明 |
|------|------|------|
| table_name | string | 表名 |
| table_comment | string | 表注释 |
| ddl | string | 建表 DDL带 COMMENT 的映射后 DDL |
| columns | array | 列信息列表 |
### columns[] 详情
| 参数 | 类型 | 说明 |
|------|------|------|
| column_name | string | 列名 |
| data_type | string | 数据类型 |
| column_type | string | 完整列类型 |
| is_nullable | string | 是否可空YES/NO |
| default_value | string | 默认值 |
| column_key | string | 主键标识PRI/MUL/UNI |
| extra | string | 额外信息(如 auto_increment |
| column_comment | string | 列注释 |
| mapped_name | string | 字段中文映射名(已保存的映射) |
### graphs 详情Neo4j
| 参数 |类型| 说明 |
|------|------|------|
| labels | array | 标签列表 |
| relationshipTypes | array | 关系类型列表 |
| nodes | array | 节点属性定义 |
| relationships | array | 关系属性定义 |
### graphs.labels[]
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 标签名称 |
| count | int | 节点数量 |
### graphs.relationshipTypes[]
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 关系类型名称 |
| count | int | 关系数量 |
### graphs.nodes[]
| 参数 | 类型 | 说明 |
|------|------|------|
| label | string | 节点标签名 |
| properties | array | 属性列表 |
### graphs.relationships[]
| 参数 | 类型 | 说明 |
|------|------|------|
| type | string | 关系类型名 |
| startLabel | string | 起始节点标签 |
| endLabel | string | 目标节点标签 |
| properties | array | 属性列表 |
## 返回示例MySQL/PostgreSQL
```json
{
"success": true,
"message": "connection successful",
"database": "students",
"tables": [
{
"table_name": "users",
"table_comment": "用户表",
"ddl": "CREATE TABLE `users` (\n `id` int(10) unsigned NOT NULL COMMENT '用户ID'\n ...\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"columns": [
{
"column_name": "id",
"data_type": "int",
"column_type": "int(10) unsigned",
"is_nullable": "NO",
"default_value": "",
"column_key": "PRI",
"extra": "auto_increment",
"column_comment": "",
"mapped_name": "用户ID"
}
]
}
]
}
```
## 返回示例Neo4j
```json
{
"success": true,
"message": "connection successful",
"database": "neo4j",
"graphs": {
"labels": [
{"name": "User", "count": 100},
{"name": "Order", "count": 50}
],
"relationshipTypes": [
{"name": "KNOWS", "count": 30},
{"name": "OWNS", "count": 20}
],
"nodes": [
{
"label": "User",
"properties": [
{"name": "id", "type": "string"},
{"name": "name", "type": "string"}
]
}
],
"relationships": [
{
"type": "KNOWS",
"startLabel": "User",
"endLabel": "User",
"properties": [
{"name": "since", "type": "date"}
]
}
]
}
}
```
## 使用场景
1. **关系型数据库**
- 首次连接:不传 `database_id`,获取实时表结构
- 恢复映射:传入 `database_id`,返回已保存的 `mapped_name``ddl`
2. **Neo4j 图数据库**
- 连接 Neo4j 并获取图谱概览数据(标签、关系类型、属性定义)
- 用于前端图可视化展示

View File

@@ -1,104 +0,0 @@
# 创建数据库配置
## 接口地址
```
POST /database/add
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 数据库名称 |
| description | string | 否 | 描述 |
| db_type | string | 是 | 数据库类型 |
| host | string | 是 | 主机 |
| port | int | 是 | 端口 |
| username | string | 是 | 用户名 |
| password | string | 否 | 密码 |
| database | string | 是 | 数据库名 |
| charset | string | 否 | 字符集 |
| ssl_mode | string | 否 | SSL 模式 |
| sub_tables | array | 否 | 子表配置列表 |
### sub_tables[] 详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| parent_table | string | 是 | 原始表名 |
| sub_table_name | string | 是 | 子表别名 |
| sub_table_comment | string | 否 | 子表注释 |
| mapping_type | string | 否 | 映射类型 |
| relation_field | string | 否 | 关联字段 |
| relation_type | string | 否 | 关联类型 |
| fields | array | 否 | 字段映射列表 |
### fields[] 详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| column_name | string | 是 | 列名 |
| mapped_name | string | 是 | 中文映射名 |
## 请求示例
```json
{
"name": "学生数据库",
"description": "用于存储学生信息",
"db_type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "students",
"charset": "utf8mb4",
"sub_tables": [
{
"parent_table": "users",
"sub_table_name": "用户表",
"sub_table_comment": "用户信息",
"fields": [
{"column_name": "id", "mapped_name": "用户ID"},
{"column_name": "name", "mapped_name": "用户名"}
]
}
]
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| id | string | 数据库记录ID |
| name | string | 数据库名称 |
| db_type | string | 数据库类型 |
| host | string | 主机 |
| port | int | 端口 |
| ... | ... | 其他字段 |
## 返回示例
```json
{
"id": "xxx-xxx-xxx",
"name": "学生数据库",
"description": "用于存储学生信息",
"db_type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "students",
"table_count": 1,
"charset": "utf8mb4",
"created_at": "2026-03-06T15:00:00Z"
}
```
## 说明
- 创建时会自动连接数据库获取表结构 DDL
- 如果传入了 `fields`(字段映射),会自动生成带 COMMENT 的新 DDL 并存储

View File

@@ -1,51 +0,0 @@
# 获取数据库列表
## 接口地址
```
GET /database/list
```
## 请求参数
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| list | array | 数据库列表 |
### list[] 详情
| 参数 | 类型 | 说明 |
|------|------|------|
| id | string | 数据库ID |
| name | string | 数据库名称 |
| description | string | 描述 |
| db_type | string | 数据库类型 |
| host | string | 主机 |
| port | int | 端口 |
| database | string | 数据库名 |
| table_count | int | 子表数量 |
| created_at | string | 创建时间 |
## 返回示例
```json
{
"list": [
{
"id": "xxx-xxx",
"name": "学生数据库",
"description": "用于存储学生信息",
"db_type": "mysql",
"host": "localhost",
"port": 3306,
"database": "students",
"table_count": 5,
"created_at": "2026-03-06T15:00:00Z"
}
]
}
```

View File

@@ -1,268 +0,0 @@
# 知识库 API
## 基础信息
| 项目 | 说明 |
|------|------|
| 基础URL | `http://localhost:8082` |
## 接口列表
### 1. 创建知识库
**请求**
```
POST /api/knowledge/create
Content-Type: application/json
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | String | 是 | 知识库名称 |
| description | String | 否 | 知识库描述 |
| llm_model_id | String | 是 | LLM 模型 ID |
| embedding_model_id | String | 是 | Embedding 模型 ID |
| parsing_config | Object | 是 | 解析配置 |
| - engine | String | 是 | 解析引擎markitdown / docling |
| - docling_url | String | 条件 | Docling URLengine=docling 时必填) |
| - enable_pdf | Boolean | 否 | 是否启用 PDF 解析 |
| - pandoc | Boolean | 否 | 是否启用 Pandoc |
| storage_config | Object | 否 | 存储配置,不传则使用全局配置 |
| - type | String | 否 | 存储模式local / minio |
| - endpoint | String | 否 | MinIO endpoint |
| - bucket | String | 否 | MinIO bucket |
| - access_key | String | 否 | MinIO access key |
| - secret_key | String | 否 | MinIO secret key |
| - use_ssl | Boolean | 否 | MinIO 是否使用 SSL |
**响应**
```json
{
"success": true,
"id": "kb_xxx",
"message": "Knowledge base created successfully"
}
```
---
### 2. 获取知识库列表
**请求**
```
GET /api/knowledge/list
```
**响应**
```json
{
"success": true,
"data": [
{
"id": "kb_001",
"name": "产品文档知识库",
"description": "用于存储产品手册",
"llm_model_id": "model_001",
"embedding_model_id": "model_002",
"status": "active",
"document_count": 15,
"chunk_count": 156,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
]
}
```
---
### 3. 获取知识库详情
**请求**
```
GET /api/knowledge/:id
```
**响应**
```json
{
"success": true,
"data": {
"id": "kb_001",
"name": "产品文档知识库",
"description": "用于存储产品手册",
"llm_model_id": "model_001",
"embedding_model_id": "model_002",
"parsing_config": {
"engine": "markitdown",
"enable_pdf": true,
"pandoc": true
},
"status": "active",
"document_count": 15,
"chunk_count": 156,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
```
---
### 4. 删除知识库
**请求**
```
DELETE /api/knowledge/:id
```
**响应**
```json
{
"success": true,
"message": "Knowledge base deleted"
}
```
---
### 5. 获取知识库下的文档列表
**请求**
```
GET /api/knowledge/:id/documents
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| status | String | 否 | 过滤状态all / parsed / parsing / failed |
**响应**
```json
{
"success": true,
"data": [
{
"id": "doc_001",
"knowledge_base_id": "kb_001",
"name": "产品手册_v2.0.pdf",
"file_key": "abc123.pdf",
"file_url": "http://localhost:8082/files/abc123.pdf",
"file_size": 2516582,
"status": "parsed",
"chunk_count": 156,
"uploaded_at": "2024-01-15T10:30:00Z"
}
]
}
```
---
### 6. 上传文档到知识库
**请求**
```
POST /api/knowledge/:id/documents
Content-Type: multipart/form-data
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | File | 是 | 要上传的文件 |
**响应**
```json
{
"success": true,
"id": "doc_001",
"url": "http://localhost:8082/files/abc123.pdf",
"document": {
"id": "doc_001",
"knowledge_base_id": "kb_001",
"name": "产品手册_v2.0.pdf",
"file_size": 2516582,
"status": "parsing",
"chunk_count": 0,
"uploaded_at": "2024-01-15T10:30:00Z"
},
"message": "Document uploaded"
}
```
---
### 7. 删除知识库文档
**请求**
```
DELETE /api/knowledge/:id/documents/:doc_id
```
**响应**
```json
{
"success": true,
"message": "Document deleted"
}
```
---
### 8. 重新解析文档
**请求**
```
POST /api/knowledge/:id/documents/:doc_id/reparse
```
**响应**
```json
{
"success": true,
"message": "Document reparse started"
}
```
---
### 9. 获取文档预览内容
**请求**
```
GET /api/knowledge/:id/documents/:doc_id/preview
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | Number | 否 | 页码(默认 1 |
**响应**
```json
{
"success": true,
"data": {
"total_pages": 3,
"current_page": 1,
"content": "第一章 产品介绍..."
}
}
```

View File

@@ -1,208 +0,0 @@
# Model Settings 接口文档
## 接口列表
### 1. 获取模型列表
**接口地址:** `GET /model/list`
**返回参数:**
```json
{
"list": [
{
"id": "xxx-xxx-xxx",
"name": "OpenAI",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions",
"status": "active",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
]
}
```
---
### 2. 获取模型详情
**接口地址:** `GET /model/:id`
**返回参数:**
```json
{
"id": "xxx-xxx-xxx",
"name": "OpenAI",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions",
"status": "active",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
```
---
### 3. 创建模型
**接口地址:** `POST /model/add`
**请求参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 模型名称 |
| model_type | string | 是 | 模型类型chat/embedding/rerank/vlm |
| provider | string | 是 | 提供商OpenAI/Ollama |
| model | string | 是 | 模型标识,如 gpt-4o |
| api_key | string | 是 | API 密钥 |
| base_url | string | 是 | 基础 URL |
| api_endpoint | string | 否 | API 端点路径 |
**请求示例:**
```json
{
"name": "OpenAI",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions"
}
```
**返回参数:**
```json
{
"id": "xxx-xxx-xxx",
"name": "OpenAI",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions",
"status": "active",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
```
---
### 4. 更新模型
**接口地址:** `PUT /model/:id`
**请求参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 否 | 模型名称 |
| model_type | string | 否 | 模型类型chat/embedding/rerank/vlm |
| provider | string | 否 | 提供商OpenAI/Ollama |
| model | string | 否 | 模型标识 |
| api_key | string | 否 | API 密钥 |
| base_url | string | 否 | 基础 URL |
| api_endpoint | string | 否 | API 端点路径 |
| status | string | 否 | 状态active/inactive |
**请求示例:**
```json
{
"name": "OpenAI Updated",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions",
"status": "active"
}
```
---
### 5. 删除模型
**接口地址:** `DELETE /model/:id`
**返回参数:**
```json
{
"success": true
}
```
---
### 6. 测试连接
**接口地址:** `POST /model/test`
**请求参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| provider | string | 是 | 提供商OpenAI/Ollama |
| model | string | 是 | 模型标识 |
| api_key | string | 是 | API 密钥 |
| base_url | string | 是 | 基础 URL |
| api_endpoint | string | 否 | API 端点路径 |
**请求示例:**
```json
{
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions"
}
```
**返回参数:**
```json
{
"success": true,
"message": "Connection successful"
}
```
或失败时:
```json
{
"success": false,
"message": "HTTP 401: Unauthorized"
}
```
---
## 数据结构
### ModelInfo 模型信息
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 主键 UUID |
| name | string | 模型名称 |
| model_type | string | 模型类型chat/embedding/rerank/vlm |
| provider | string | 提供商OpenAI/Ollama |
| model | string | 模型标识 |
| api_key | string | API 密钥 |
| base_url | string | 基础 URL |
| api_endpoint | string | API 端点路径 |
| status | string | 状态active/inactive |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |

View File

@@ -1,265 +0,0 @@
# Neo4j 连接测试
## 接口地址
```
POST /neo4j/check
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| host | string | 是 | Neo4j 主机 |
| port | int | 是 | Neo4j 端口(默认 7687 |
| username | string | 是 | 用户名(默认 neo4j |
| password | string | 是 | 密码 |
| database | string | 否 | 数据库名(默认 neo4j |
| name | string | 否 | 数据库名称,用于保存记录 |
| uri | string | 否 | Neo4j 连接地址bolt://host:port |
| description | string | 否 | 数据库描述 |
## 请求示例
```json
{
"host": "localhost",
"port": 7687,
"username": "neo4j",
"password": "password",
"database": "neo4j",
"name": "My Neo4j Database"
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否连接成功 |
| message | string | 消息 |
| version | string | Neo4j 版本 |
| databases | array | 数据库列表 |
| databaseId | string | 数据库记录 ID新增 |
| name | string | 数据库名称(新增) |
| description | string | 数据库描述(新增) |
## 返回示例
```json
{
"success": true,
"message": "connection successful",
"version": "5.14.0",
"databases": ["neo4j", "system"],
"databaseId": "abc-123-def",
"name": "Neo4j-neo4j",
"description": "Neo4j neo4j@localhost:7687"
}
```
> **说明**:连接成功时,后端会自动检查数据库记录是否存在,不存在则创建并返回 `databaseId`、`name` 和 `description`。前端可使用这些信息进行后续保存图谱操作。
---
# Neo4j 获取图谱概览数据(核心接口)
获取所有标签(Labels)和关系类型(Relationship Types)的统计信息,用于前端图谱可视化。
## 接口地址
```
POST /neo4j/graphs
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
| database | string | 否 | 数据库名(默认 neo4j |
## 请求示例
```json
{
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"database": "neo4j"
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否成功 |
| graphs | object | 图谱数据 |
### graphs 对象
| 参数 | 类型 | 说明 |
|------|
| labels------|------| | array | 标签列表 |
| relationshipTypes | array | 关系类型列表 |
### labels 数组项
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 标签名称 |
| count | int | 该标签的节点数量 |
### relationshipTypes 数组项
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 关系类型名称 |
| count | int | 该关系的数量 |
## 返回示例
```json
{
"success": true,
"graphs": {
"labels": [
{"name": "User", "count": 1523},
{"name": "Order", "count": 856},
{"name": "Product", "count": 2341},
{"name": "Category", "count": 45},
{"name": "Review", "count": 5678}
],
"relationshipTypes": [
{"name": "KNOWS", "count": 2341},
{"name": "BOUGHT", "count": 5678},
{"name": "BELONGS_TO", "count": 2341},
{"name": "HAS_REVIEW", "count": 5678},
{"name": "LOCATED_IN", "count": 1523}
]
}
}
```
## 前端使用说明
前端使用 ECharts 力导向图谱展示:
- `labels` 生成图谱节点count 决定节点大小
- `relationshipTypes` 生成图谱边
- 建议返回至少 5-10 个关系类型以便生成丰富图谱
---
# Neo4j 获取节点详情
## 接口地址
```
POST /neo4j/nodes
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
| database | string | 否 | 数据库名 |
| label | string | 是 | 节点标签名 |
| limit | int | 否 | 返回数量限制,默认 10 |
## 请求示例
```json
{
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"label": "User",
"limit": 10
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否成功 |
| message | string | 消息 |
| nodes | array | 节点数据列表 |
## 返回示例
```json
{
"success": true,
"message": "success",
"nodes": [
{"id": "1", "name": "张三", "email": "zhangsan@example.com"},
{"id": "2", "name": "李四", "email": "lisi@example.com"}
]
}
```
---
# Neo4j 获取关系详情
## 接口地址
```
POST /neo4j/relationships
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| uri | string | 是 | Neo4j 连接地址 |
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
| database | string | 否 | 数据库名 |
| relationship_type | string | 是 | 关系类型名 |
| limit | int | 否 | 返回数量限制,默认 10 |
## 请求示例
```json
{
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"relationship_type": "KNOWS",
"limit": 10
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否成功 |
| message | string | 消息 |
| relationships | array | 关系数据列表 |
## 返回示例
```json
{
"success": true,
"message": "success",
"relationships": [
{
"startId": "1",
"endId": "2",
"startLabels": ["User"],
"endLabels": ["User"],
"since": "2020-01-01"
}
]
}
```

View File

@@ -1,75 +0,0 @@
# Neo4j 图谱保存接口需求
## 需求说明
前端需要保存 Neo4j 图谱的连接信息,以便后续快速加载和查看。
---
## 接口地址
```
POST /database/graph/save
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| databaseId | string | 是 | 数据库 ID |
| databaseName | string | 是 | 数据库名称 |
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
| username | string | 是 | 用户名 |
| labels | array | 是 | 标签列表 |
| relationshipTypes | array | 是 | 关系类型列表 |
| selectedLabel | string | 否 | 当前选中的标签 |
## 请求示例
```json
{
"databaseId": "123",
"databaseName": "neo4j",
"uri": "bolt://10.10.10.189:7687",
"username": "neo4j",
"labels": ["User", "Order", "Product"],
"relationshipTypes": ["KNOWS", "BOUGHT", "BELONGS_TO"],
"selectedLabel": "User"
}
```
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否成功 |
| message | string | 消息 |
## 返回示例
```json
{
"success": true,
"message": "保存成功"
}
```
---
## 前端调用示例
```javascript
fetch('/database/graph/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
databaseId: '123',
databaseName: 'neo4j',
uri: 'bolt://10.10.10.189:7687',
username: 'neo4j',
labels: ['User', 'Order', 'Product'],
relationshipTypes: ['KNOWS', 'BOUGHT', 'BELONGS_TO'],
selectedLabel: 'User',
}),
})
```

View File

@@ -1,75 +0,0 @@
# 获取子表列表
## 接口地址
```
GET /sub-table/database/:database_id
```
## 路径参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| database_id | string | 是 | 数据库ID |
## 返回参数
| 参数 | 类型 | 说明 |
|------|------|------|
| list | array | 子表列表 |
### list[] 详情
| 参数 | 类型 | 说明 |
|------|------|------|
| id | string | 子表ID |
| database_id | string | 关联的数据库ID |
| parent_table | string | 原始表名 |
| sub_table_name | string | 子表别名 |
| sub_table_comment | string | 子表注释 |
| mapping_type | string | 映射类型 |
| relation_field | string | 关联字段 |
| relation_type | string | 关联类型 |
| fields | array | 字段映射列表 |
| ddl | string | 建表 DDL带 COMMENT |
| created_at | string | 创建时间 |
### fields[] 详情
| 参数 | 类型 | 说明 |
|------|------|------|
| column_name | string | 列名 |
| mapped_name | string | 中文映射名 |
## 返回示例
```json
{
"list": [
{
"id": "xxx-xxx",
"database_id": "database-xxx",
"parent_table": "users",
"sub_table_name": "用户表",
"sub_table_comment": "用户信息",
"mapping_type": "horizontal",
"relation_field": "id",
"relation_type": "one_to_many",
"fields": [
{"column_name": "id", "mapped_name": "用户ID"},
{"column_name": "name", "mapped_name": "用户名"}
],
"ddl": "CREATE TABLE `users` (\n `id` int(10) unsigned NOT NULL COMMENT '用户ID'\n ...\n)",
"created_at": "2026-03-06T15:00:00Z"
}
]
}
```
## 使用场景
用于恢复映射状态:
1. 用户点击已存在的数据库的 "Map Tables" 按钮
2. 调用此接口获取已保存的子表信息
3. 根据 `parent_table` 勾选已选择的表
4. 根据 `fields` 恢复字段映射

View File

@@ -1,96 +0,0 @@
# 文件上传 API
## 基础信息
| 项目 | 说明 |
|------|------|
| 基础URL | `http://localhost:8082` |
| 上传模式 | local / minio配置决定 |
## 配置说明
```yaml
# config.yaml
upload_mode: "local" # 上传模式local 或 minio
upload_local_path: "resource/files" # 本地存储路径
server_base_url: "http://localhost:8082" # 服务器基础URL
# MinIO 配置upload_mode 为 minio 时需要)
minio_endpoint: "localhost:9000"
minio_access_key: "your-access-key"
minio_secret_key: "your-secret-key"
minio_bucket: "x-agents"
minio_use_ssl: false
```
## 接口列表
### 1. 上传文件
**请求**
```
POST /api/file_upload
Content-Type: multipart/form-data
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | File | 是 | 要上传的文件 |
**响应**
```json
{
"success": true,
"url": "http://localhost:8082/files/abc123.pdf",
"fileKey": "abc123",
"message": "Upload successful"
}
```
**错误响应**
```json
{
"success": false,
"message": "File too large (max 100MB)"
}
```
---
### 2. 删除文件
**请求**
```
DELETE /api/file_upload/:filename
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| filename | String | 是 | 文件名(不含路径) |
**响应**
```json
{
"success": true,
"message": "File deleted"
}
```
---
### 3. 访问文件(仅本地模式)
文件上传后,本地模式下可通过以下 URL 直接访问:
```
GET /files/{filename}
```
例如:`http://localhost:8082/files/abc123.pdf`
> 注意MinIO 模式返回的是预签名 URL有效期 24 小时。

View File

@@ -1,92 +0,0 @@
# 后端需求 - 表结构返回 columns 数据
## 问题描述
前端在 Edit Mapping 页面需要展示表的列信息字段名、类型、COMMENT等但前端自行解析 DDL 存在困难。
## 需求
后端在获取表结构列表时,需要同时返回:
1. **DDL 语句**(已有的需求,继续保留)
2. **结构化的 columns 数据**(新增)
### 返回数据结构
```json
{
"success": true,
"tables": [
{
"table_name": "exam_scores",
"table_comment": "考试成绩表",
"ddl": "CREATE TABLE `exam_scores` (...)",
"columns": [
{
"column_name": "id",
"data_type": "int",
"column_type": "int(10) unsigned",
"is_nullable": "NO",
"default_value": null,
"column_key": "PRI",
"extra": "auto_increment",
"column_comment": ""
},
{
"column_name": "student_id",
"data_type": "int",
"column_type": "int(10) unsigned",
"is_nullable": "NO",
"default_value": null,
"column_key": "",
"extra": "",
"column_comment": ""
},
{
"column_name": "subject",
"data_type": "varchar",
"column_type": "varchar(50)",
"is_nullable": "NO",
"default_value": null,
"column_key": "",
"extra": "",
"column_comment": "科目"
},
{
"column_name": "score",
"data_type": "double",
"column_type": "double",
"is_nullable": "YES",
"default_value": null,
"column_key": "",
"extra": "",
"column_comment": "分数"
}
]
}
]
}
```
### 字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| column_name | string | 列名 |
| data_type | string | 数据类型(如 int, varchar, double |
| column_type | string | 完整列类型(如 int(10) unsigned |
| is_nullable | string | 是否可空YES/NO |
| default_value | string | 默认值 |
| column_key | string | 主键标识PRI/MUL/UNI |
| extra | string | 额外信息(如 auto_increment |
| column_comment | string | 列注释 |
## 影响范围
- 文件:`server/internal/service/database_service.go`
- 函数:`getMySQLTables`, `getPostgresTables`
- 数据模型:`server/internal/model/sub_table_info.go``ColumnInfo` 结构体
## 优先级
高 - 前端 Edit Mapping 页面字段映射功能依赖此数据

View File

@@ -1,66 +0,0 @@
# 后端需求 - DDL 编辑功能
## 问题描述
前端 Database 页面的 Table Mapping 功能已从"字段映射"改为"DDL 编辑"模式。后端需要支持保存和读取 DDL 数据。
## 需求
### 1. 保存 DDL
前端保存时发送 `ddl` 字段而非 `fields` 字段。
请求结构:
```json
{
"name": "数据库名",
"sub_tables": [
{
"parent_table": "users",
"sub_table_name": "用户表",
"sub_table_comment": "用户表",
"ddl": "CREATE TABLE users (...)"
}
]
}
```
### 2. 后端处理
- `CreateSubTableRequest` 已有 `DDL string` 字段(已添加)
- `UpdateSubTableRequest` 已有 `DDL string` 字段(已添加)
- `UpdateDatabaseRequest` 已有 `SubTables []CreateSubTableRequest` 字段(已添加)
### 3. Service 层修改
**sub_table_service.go**
1. `Create` 函数 - 添加 DDL 字段赋值:
```go
info := &model.SubTableInfo{
// ... 现有字段
DDL: req.DDL,
}
```
2. `Update` 函数 - 添加 DDL 更新逻辑:
```go
// 更新 DDL
info.DDL = req.DDL
```
**database_service.go 或 handler**
`Update` 方法中处理 `SubTables` 字段:
- 当前端传入 `sub_tables` 时,需要创建或更新对应的子表记录(包括 DDL
- 遍历 `sub_tables`,调用 `SubTableService.Create``SubTableService.Update`
## 影响范围
- `server/internal/service/sub_table_service.go` - Create/Update 方法
- `server/internal/service/database_service.go` 或 handler - Update 方法处理 SubTables
## 状态
- [x] 前端修改完成
- [x] 后端修改已完成

View File

@@ -1,89 +0,0 @@
# 后端需求 - 字段映射保存与读取
## 问题描述
前端 Edit Mapping 页面中用户输入的字段中文映射名mapped_name在保存后第二次打开时丢失了。
## 原因分析
1. **保存时**:前端只保存了表级别信息,没有保存字段的中文映射
2. **加载时**:前端每次都从 `/database/check` 重新获取表结构,没有读取已保存的映射数据
## 需求
### 1. 保存字段映射
前端保存时需要传递每个字段的中文映射名,后端需要存储这些数据。
请求结构:
```json
{
"name": "数据库名",
"sub_tables": [
{
"parent_table": "users",
"sub_table_name": "用户表",
"sub_table_comment": "用户表",
"fields": [
{
"column_name": "id",
"mapped_name": "编号"
},
{
"column_name": "username",
"mapped_name": "用户名"
}
]
}
]
}
```
### 2. 返回字段映射
后端在返回表结构时,需要同时返回已保存的字段映射信息。
返回结构:
```json
{
"success": true,
"tables": [
{
"table_name": "users",
"table_comment": "用户表",
"ddl": "...",
"columns": [
{
"column_name": "id",
"data_type": "int",
"column_type": "int(10)",
"column_comment": "",
"mapped_name": "编号"
},
{
"column_name": "username",
"data_type": "varchar",
"column_type": "varchar(50)",
"column_comment": "用户名",
"mapped_name": "用户名"
}
]
}
]
}
```
### 3. 数据存储
- 可以在 `sub_table_info` 表中增加 `fields` JSON 字段存储字段映射
- 或者创建新的关联表 `sub_table_fields`
## 影响范围
- `server/internal/service/database_service.go` - Create/Update 方法
- `server/internal/model/` - 数据模型修改
- 子表映射的数据存储结构
## 优先级
高 - 用户输入的映射数据丢失影响使用体验

View File

@@ -1,375 +0,0 @@
# 知识库创建 API
## 基础信息
| 项目 | 说明 |
|------|------|
| 基础URL | `http://localhost:8082` |
| 前端页面 | Knowledge Base 创建弹窗 |
## 接口列表
### 1. 创建知识库
**请求**
```
POST /api/knowledge/create
Content-Type: application/json
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | String | 是 | 知识库名称 |
| description | String | 否 | 知识库描述 |
| llm_model_id | String | 是 | LLM 模型 ID来自 model 表) |
| embedding_model_id | String | 是 | Embedding 模型 ID来自 model 表) |
| parsing_config | Object | 是 | 解析配置 |
| - engine | String | 是 | 解析引擎markitdown / docling |
| - docling_url | String | 条件必填 | Docling 服务 URLengine=docling 时必填) |
| - enable_pdf | Boolean | 否 | 是否启用 PDF 解析(默认 true |
| - pandoc | Boolean | 否 | 是否启用 Pandoc默认 true |
| storage_config | Object | 否 | 存储配置(默认 local |
| - type | String | 是 | 存储类型local / minio / s3 |
| - endpoint | String | 否 | MinIO Endpoint如 minio:9000 |
| - access_key_id | String | 否 | MinIO Access Key ID |
| - secret_access_key | String | 否 | MinIO Secret Access Key |
| - bucket | String | 否 | MinIO Bucket 名称 |
**请求示例**
本地存储:
```json
{
"name": "产品文档知识库",
"description": "用于存储产品手册和文档",
"llm_model_id": "model_001",
"embedding_model_id": "model_002",
"parsing_config": {
"engine": "markitdown",
"enable_pdf": true,
"pandoc": true
},
"storage_config": {
"type": "local"
}
}
```
使用 Docling + MinIO
```json
{
"name": "产品文档知识库",
"description": "用于存储产品手册和文档",
"llm_model_id": "model_001",
"embedding_model_id": "model_002",
"parsing_config": {
"engine": "docling",
"docling_url": "http://localhost:8501",
"enable_pdf": true,
"pandoc": true
},
"storage_config": {
"type": "minio",
"endpoint": "localhost:9000",
"access_key_id": "minioadmin",
"secret_access_key": "minioadmin",
"bucket": "x-agents"
}
}
```
**成功响应**
```json
{
"success": true,
"id": "kb_abc123",
"message": "Knowledge base created successfully"
}
```
**错误响应**
```json
{
"success": false,
"message": "LLM model not found"
}
```
---
### 2. 获取知识库列表
**请求**
```
GET /api/knowledge/list
```
**响应**
```json
{
"success": true,
"data": [
{
"id": "kb_001",
"name": "产品文档知识库",
"description": "用于存储产品手册",
"llm_model_id": "model_001",
"embedding_model_id": "model_002",
"status": "active",
"document_count": 15,
"chunk_count": 156,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
]
}
```
---
### 3. 获取知识库详情
**请求**
```
GET /api/knowledge/:id
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
**响应**
```json
{
"success": true,
"data": {
"id": "kb_001",
"name": "产品文档知识库",
"description": "用于存储产品手册",
"llm_model_id": "model_001",
"embedding_model_id": "model_002",
"parsing_config": {
"engine": "markitdown",
"enable_pdf": true,
"pandoc": true
},
"status": "active",
"document_count": 15,
"chunk_count": 156,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
```
---
### 4. 删除知识库
**请求**
```
DELETE /api/knowledge/:id
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
**响应**
```json
{
"success": true,
"message": "Knowledge base deleted"
}
```
---
### 5. 获取知识库下的文档列表
**请求**
```
GET /api/knowledge/:id/documents
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| status | String | 否 | 过滤状态all / parsed / parsing / failed |
**响应**
```json
{
"success": true,
"data": [
{
"id": "doc_001",
"knowledge_base_id": "kb_001",
"name": "产品手册_v2.0.pdf",
"file_key": "abc123.pdf",
"file_url": "http://localhost:8082/files/abc123.pdf",
"file_size": 2516582,
"status": "parsed",
"chunk_count": 156,
"uploaded_at": "2024-01-15T10:30:00Z"
}
]
}
```
---
### 6. 上传文档到知识库
**请求**
```
POST /api/knowledge/:id/documents
Content-Type: multipart/form-data
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
| file | File | 是 | 要上传的文件 |
**响应**
```json
{
"success": true,
"dataid": "doc_001",
": {
" "name": "产品手册_v2.0.pdf",
"status": "parsing"
}
}
```
---
### 7. 删除知识库文档
**请求**
```
DELETE /api/knowledge/:id/documents/:doc_id
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
| doc_id | String | 是 | 文档 ID |
**响应**
```json
{
"success": true,
"message": "Document deleted"
}
```
---
### 8. 重新解析文档
**请求**
```
POST /api/knowledge/:id/documents/:doc_id/reparse
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
| doc_id | String | 是 | 文档 ID |
**响应**
```json
{
"success": true,
"message": "Document reparse started"
}
```
---
### 9. 获取文档预览内容
**请求**
```
GET /api/knowledge/:id/documents/:doc_id/preview
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String | 是 | 知识库 ID |
| doc_id | String | 是 | 文档 ID |
| page | Number | 否 | 页码(默认 1 |
**响应**
```json
{
"success": true,
"data": {
"total_pages": 3,
"current_page": 1,
"content": "第一章 产品介绍\n\n欢迎使用我们的产品手册..."
}
}
```
---
## 数据库表设计(参考)
### knowledge_base 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 主键 |
| name | String | 知识库名称 |
| description | Text | 描述 |
| llm_model_id | String | LLM 模型 ID |
| embedding_model_id | String | Embedding 模型 ID |
| parsing_config | JSON | 解析配置 |
| storage_config | JSON | 存储配置(包含 type, endpoint, access_key_id, secret_access_key, bucket |
| status | String | 状态active / inactive |
| document_count | Integer | 文档数量 |
| chunk_count | Integer | 切片数量 |
| created_at | Timestamp | 创建时间 |
| updated_at | Timestamp | 更新时间 |
### knowledge_document 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 主键 |
| knowledge_base_id | String | 知识库 ID |
| name | String | 文档名称 |
| file_key | String | 文件存储 key |
| file_url | String | 文件访问 URL本地路径或 MinIO 预签名 URL |
| file_size | BigInteger | 文件大小 |
| status | String | 状态parsing / parsed / failed |
| chunk_count | Integer | 切片数量 |
| uploaded_at | Timestamp | 上传时间 |

View File

@@ -1,43 +0,0 @@
# 后端需求 - 保存和恢复映射状态
## 问题描述
用户第一次选择表并设置字段映射后,第二次点击 "Map Tables" 按钮进入界面时,之前选择的表和设置的字段映射都丢失了。
## 需求
前端打开已存在的数据库映射时,需要恢复以下状态:
### 1. 已选择的表列表
后端需要在数据库记录中保存用户选择了哪些表(不仅仅是子表信息),或者在查询时返回该数据库关联的所有子表。
### 2. 字段映射
每个子表保存的字段映射mapped_name需要在前端重新加载时显示。
## 期望的行为
1. 用户点击已存在的数据库的 "Map Tables" 按钮
2. 前端获取实时表结构
3. 同时加载该数据库已保存的子表信息(包括选择的表和字段映射)
4. 前端合并数据,显示:
- 已选择的表(勾选状态)
- 每个字段之前设置的 mapped_name
## 技术实现建议
在数据库表中增加或利用已有字段:
- `sub_table_info` 表已包含 `Fields` JSON 字段存储字段映射
- 需要在创建/更新数据库时保存选择的表列表
- 或者在查询时返回该数据库下所有已创建的子表
## 影响范围
- 数据库创建/更新接口
- 子表映射查询接口
## 优先级
高 - 影响用户体验,第二次进入无法看到之前的工作成果

View File

@@ -1,193 +0,0 @@
# Model Settings 需求文档
## 需求概述
Model Settings 页面用于管理 AI 模型配置,支持添加、编辑、删除和测试模型连接。
## 功能列表
### 1. 模型列表展示
展示已配置的模型列表,包含以下字段:
- Model Name模型名称
- Model Type模型类型Chat / Embedding / Rerank / VLM
- API EndpointAPI 端点)
- Status状态Active / Inactive
### 2. 添加新模型
点击 "Add New Model" 按钮,弹出表单包含以下字段:
- Model Name必填
- Model Type必选Chat / Embedding / Rerank / VLM
- Provider必选OpenAI / Ollama
- Model必填模型名称如 gpt-4o
- API Key必填
- Base URL必填
- API Endpoint可选
### 3. 测试连接
在添加模型表单中提供 "Test Connection" 按钮,用于验证模型连接是否可用。
### 4. 编辑模型
点击编辑按钮,弹出编辑表单,可修改模型信息。
### 5. 删除模型
点击删除按钮,确认后删除模型记录。
---
## 后端接口需求
### 1. 获取模型列表
**接口地址:** `GET /api/models``GET /model/list`
**返回参数:**
```json
{
"list": [
{
"id": "1",
"name": "OpenAI",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions",
"status": "active",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
]
}
```
### 2. 添加模型
**接口地址:** `POST /api/models``POST /model/add`
**请求参数:**
```json
{
"name": "OpenAI",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions"
}
```
**返回参数:**
```json
{
"id": "1",
"name": "OpenAI",
"model_type": "chat",
...
}
```
### 3. 更新模型
**接口地址:** `PUT /api/models/{id}``PUT /model/{id}`
**请求参数:**
```json
{
"name": "OpenAI Updated",
"model_type": "chat",
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions",
"status": "active"
}
```
### 4. 删除模型
**接口地址:** `DELETE /api/models/{id}``DELETE /model/{id}`
**返回参数:**
```json
{
"success": true
}
```
### 5. 测试连接
**接口地址:** `POST /api/models/test``POST /model/test`
**请求参数:**
```json
{
"provider": "OpenAI",
"model": "gpt-4o",
"api_key": "sk-xxx",
"base_url": "https://api.openai.com",
"api_endpoint": "/v1/chat/completions"
}
```
**返回参数:**
```json
{
"success": true,
"message": "Connection successful"
}
```
---
## 数据结构
### Model 表结构(参考)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 主键 UUID |
| name | string | 模型名称 |
| model_type | string | 模型类型chat/embedding/rerank/vlm |
| provider | string | 提供商OpenAI/Ollama |
| model | string | 模型标识 |
| api_key | string | API 密钥(加密存储) |
| base_url | string | 基础 URL |
| api_endpoint | string | API 端点路径 |
| status | string | 状态active/inactive |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
---
## 前端组件状态
### 表单数据 (newModelForm)
```typescript
{
name: string,
apiKey: string,
apiEndpoint: string,
baseUrl: string,
provider: string,
model: string,
modelType: string
}
```
### 模型类型选项 (modelTypeOptions)
- Chat
- Embedding
- Rerank
- VLM
### 提供商选项 (providerOptions)
- OpenAI
- Ollama

View File

@@ -1,110 +0,0 @@
# Neo4j 接口后端需求
## 需求说明
前端 Neo4j 图谱功能已完成,后端接口需要匹配前端调用。
---
## 1. 新增 `/neo4j/graphs` 接口
### 接口地址
```
POST /neo4j/graphs
```
### 请求参数
```json
{
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"database": "neo4j"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
| database | string | 否 | 数据库名(默认 neo4j |
### 返回参数
```json
{
"success": true,
"graphs": {
"labels": [
{"name": "User", "count": 1523},
{"name": "Order", "count": 856}
],
"relationshipTypes": [
{"name": "KNOWS", "count": 2341},
{"name": "BOUGHT", "count": 5678}
]
}
}
```
---
## 2. 修改路由路径
### 当前状态
- `/database/neo4j/nodes` → 需要改为 → `/neo4j/nodes`
- `/database/neo4j/relationships` → 需要改为 → `/neo4j/relationships`
---
## 总结
后端需要修改以下内容:
1. **新增** `/neo4j/graphs` 接口
2. **修改** `/database/neo4j/nodes``/neo4j/nodes`
3. **修改** `/database/neo4j/relationships``/neo4j/relationships`
---
## 附:前端 API 调用示例
```javascript
// 获取图谱概览
fetch('/neo4j/graphs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uri: 'bolt://10.10.10.189:7687',
username: 'neo4j',
password: 'neo4j',
database: 'neo4j'
})
})
// 获取节点详情
fetch('/neo4j/nodes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uri: 'bolt://10.10.10.189:7687',
username: 'neo4j',
password: 'neo4j',
label: 'User',
limit: 10
})
})
// 获取关系详情
fetch('/neo4j/relationships', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uri: 'bolt://10.10.10.189:7687',
username: 'neo4j',
password: 'neo4j',
relationship_type: 'KNOWS',
limit: 10
})
})
```

View File

@@ -1,99 +0,0 @@
# Neo4j 连接成功后返回数据库 ID
## 需求说明
当前端 Connect 测试 Neo4j 连接成功后,后端需要返回数据库的 ID以便前端保存图谱配置。
---
## 问题
当前 `/neo4j/check` 接口返回:
```json
{
"success": true,
"message": "connection successful",
"version": "5.14.0",
"databases": ["neo4j", "system"]
}
```
**没有返回 `databaseId`**,导致后续保存图谱时缺少 `databaseId`
---
## 需求内容
修改 `/neo4j/check` 接口,在连接成功时:
1. **检查数据库是否已存在** - 根据 URIbolt://host:port、username、database 查询
2. **如果存在** - 返回已有的 `databaseId`
3. **如果不存在** - 自动创建一条数据库记录,并返回新的 `databaseId`
### 返回格式
```json
{
"success": true,
"message": "connection successful",
"version": "5.14.0",
"databases": ["neo4j", "system"],
"databaseId": "xxx-xxx-xxx",
"name": "Neo4j-neo4j",
"description": "Neo4j neo4j@10.10.10.189:7687"
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| success | bool | 是否成功 |
| message | string | 消息 |
| version | string | Neo4j 版本 |
| databases | array | 数据库列表 |
| databaseId | string | 数据库记录 ID |
| name | string | 数据库名称 |
| description | string | 数据库描述 |
### 请求参数
当前 `/neo4j/check` 请求:
```json
{
"db_type": "Neo4j",
"host": "10.10.10.189",
"port": 7687,
"username": "neo4j",
"password": "neo4j",
"database": "neo4j"
}
```
后端需要增加 `name` 字段用于数据库名称(可选):
```json
{
"name": "My Neo4j Database"
}
```
---
## 涉及文件
- `server/internal/service/neo4j_service.go`
- 函数:`Check()` - 第 81-128 行
- 函数:`ensureNeo4jDatabase()` - 第 131-175 行(已有代码但可能有问题)
- `server/internal/model/neo4j_info.go`
- 结构体:`Neo4jCheckResponse` - 需要确保 `databaseId` 字段正确返回
---
## 前端使用
前端代码已实现兼容处理:
```javascript
const dbId = result.databaseId || result.id || result.database_id || ''
```
所以后端返回 `databaseId``id``database_id` 都可以被正确识别。

View File

@@ -1,196 +0,0 @@
# 后端需求 - Neo4j 图谱数据获取(完善版)
## 需求描述
Neo4j 连接成功后,需要获取图谱数据供前端可视化展示。前端使用 ECharts 力导向图谱展示科幻风格效果。
## Neo4j 图谱核心概念
Neo4j 是图数据库,与关系型数据库概念不同:
- **Node节点** - 类似于表,但不需要固定结构
- **Label标签** - 类似于表的类型名(如 User, Order
- **Relationship关系** - 节点之间的边
- **Relationship Type关系类型** - 关系的类型(如 KNOWS, OWNS
## 后端需要提供的接口
### 1. 获取图谱概览数据(核心接口)
返回所有 Label标签和 Relationship Type关系类型的统计信息。这是前端图谱可视化的核心数据来源。
**接口地址:** `POST /database/check` (复用现有接口,在 db_type 为 Neo4j 时返回图谱数据)
**请求参数:**
```json
{
"db_type": "Neo4j",
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"database": "neo4j"
}
```
**返回参数:**
```json
{
"success": true,
"graphs": {
"labels": [
{"name": "User", "count": 1523},
{"name": "Order", "count": 856},
{"name": "Product", "count": 2341},
{"name": "Category", "count": 45},
{"name": "Review", "count": 5678}
],
"relationshipTypes": [
{"name": "KNOWS", "count": 2341},
{"name": "BOUGHT", "count": 5678},
{"name": "BELONGS_TO", "count": 2341},
{"name": "HAS_REVIEW", "count": 5678},
{"name": "LOCATED_IN", "count": 1523}
]
}
}
```
### 2. 获取节点详情(可选,用于点击显示)
点击某个 Label 节点时,获取该类型节点的样本数据用于详情展示。
**接口地址:** `POST /database/neo4j/nodes`
**请求参数:**
```json
{
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"database": "neo4j",
"label": "User",
"limit": 5
}
```
**返回参数:**
```json
{
"success": true,
"nodes": [
{"id": "1", "name": "张三", "email": "zhangsan@example.com", "created_at": "2024-01-01"},
{"id": "2", "name": "李四", "email": "lisi@example.com", "created_at": "2024-01-02"}
],
"properties": [
{"name": "id", "type": "string"},
{"name": "name", "type": "string"},
{"name": "email", "type": "string"},
{"name": "created_at", "type": "datetime"}
]
}
```
### 3. 获取关系详情(可选)
获取两个节点之间的关系数据。
**接口地址:** `POST /database/neo4j/relationships`
**请求参数:**
```json
{
"uri": "bolt://localhost:7687",
"username": "neo4j",
"password": "password",
"database": "neo4j",
"relationshipType": "KNOWS",
"limit": 10
}
```
**返回参数:**
```json
{
"success": true,
"relationships": [
{
"id": "rel-1",
"source": "1",
"target": "2",
"properties": {"since": "2020-01-01"}
}
]
}
```
## 数据结构说明
### graphs.labels[] - 标签列表(前端图谱节点)
| 字段 | 类型 | 说明 | 用途 |
|------|------|------|------|
| name | string | 标签名称(如 User, Order | 作为图谱节点显示 |
| count | int | 该标签的节点数量 | 计算节点大小 symbolSize |
### graphs.relationshipTypes[] - 关系类型列表(前端图谱边)
| 字段 | 类型 | 说明 | 用途 |
|------|------|------|------|
| name | string | 关系类型(如 KNOWS, OWNS | 作为图谱边的标签 |
| count | int | 该关系的数量 | 可能影响边的粗细 |
### nodes[] - 节点详情
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 节点唯一标识 |
| (其他) | any | 节点的其他属性 |
### relationships[] - 关系详情
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 关系唯一标识 |
| source | string | 起始节点ID |
| target | string | 目标节点ID |
| properties | object | 关系属性 |
## 前端图谱展示逻辑
前端使用 ECharts 力导向图谱force-directed graph展示方式如下
1. **节点生成**
- 根据 `graphs.labels` 数组生成节点
- `name` 作为节点显示名称
- `count` 决定节点大小symbolSize = log2(count+1) * 12
- 节点颜色按索引分配科幻配色(紫、蓝、绿、橙、粉、青)
- 节点带发光效果shadowBlur: 20
2. **边生成**
- 根据 `graphs.relationshipTypes` 生成边
- 边 label 显示关系类型名称
- 曲线连接curveness: 0.2
- 带箭头
3. **交互效果**
- 弹簧物理效果force layout
- 节点可拖拽
- 滚轮缩放
- hover 相邻节点高亮
## 优先级
高 - Neo4j 可视化的核心数据
## 注意事项
1. Neo4j 连接使用官方 Go 驱动:`github.com/neo4j/neo4j-go-driver`
2. 注意处理连接超时和认证失败的情况
3. 大数据量时需要限制返回数量limit 参数)
4. **建议返回足够多的关系类型**(建议至少 5-10 个),以便前端生成丰富的图谱连接
5. 如果关系类型少于节点数,可以创建额外连接让图谱更美观
## 前端缓存策略
### 方案设计
- **首次加载**:获取数据并缓存
- **第二次展示**:直接使用缓存,秒开
- **刷新按钮**:用户手动点击刷新获取最新数据
### 实现说明
前端会缓存图谱数据,第二次进入时直接展示缓存数据,提升用户体验。同时提供"刷新"按钮供用户手动刷新。

View File

@@ -1,42 +0,0 @@
# 后端需求 - 支持 Neo4j 图数据库
## 需求描述
添加 Neo4j 图数据库类型支持。
## Neo4j 连接参数
Neo4j 连接需要以下参数:
| 参数 | 类型 | 必填 | 说明 | 默认值 |
|------|------|------|------|--------|
| uri | string | 是 | 连接地址 | bolt://localhost:7687 |
| username | string | 是 | 用户名 | neo4j |
| password | string | 是 | 密码 | - |
| database | string | 否 | 数据库名 | neo4j默认数据库 |
### 连接示例
- `bolt://localhost:7687`
- `neo4j://localhost:7687`
- `bolt://192.168.1.100:7687`
## 需要修改的地方
### 1. 数据库类型列表
在前端和后端添加 "Neo4j" 选项
### 2. 连接表单
Neo4j 只需要 3-4 个字段:
- URI连接地址
- Username用户名
- Password密码
- Database数据库名可选
### 3. 数据库服务
- `server/internal/service/database_service.go`
- 新增 `connectNeo4j` 方法
- 新增 `getNeo4jTables` 方法
## 优先级
中 - 扩展数据库类型支持

View File

@@ -1,35 +0,0 @@
# 后端需求 - 编辑数据库时正确处理 sub_tables
## 问题描述
用户点击 Action 修改数据库,进入 Map Tables 页面后:
1. 初始显示 2 个已选中的表
2. 用户取消选中 1 个表,只保留 1 个
3. 点击 Save Mapping 保存
4. 再次点击 Map Tables 查看时,仍然显示 2 个表,而不是修改后的 1 个
## 原因
可能的问题:
1. 编辑时没有正确从数据库加载已保存的 sub_tables 数据
2. Save Mapping 时没有正确更新 sub_tables可能只是创建新的没有删除已取消的
## 需求
### 1. 加载数据库时返回 sub_tables 数据
在获取数据库详情时,需要返回该数据库已保存的子表映射信息(包括 parent_table 等),以便前端正确显示已选中的表。
### 2. 保存时正确处理子表
- 新增的子表:创建新记录
- 保留的子表:更新记录
- 取消的子表:删除对应记录
或者使用更简单的方案:
- 保存时删除该数据库所有的旧 sub_tables
- 重新创建新的 sub_tables 记录
## 状态
- [ ] 后端修改待实现

View File

@@ -1,22 +0,0 @@
# 后端需求 - 编辑数据库时更新 table_count
## 问题描述
用户点击 Action 修改数据库,从 2 个子表修改为 1 个子表并保存后,数据库列表中的 Tables 列没有更新为新的数量。
## 原因
编辑数据库并保存 sub_tables 时,后端没有更新 `table_count` 字段。
## 需求
在 UpdateDatabaseRequest 处理 `SubTables` 字段时,需要同步更新数据库记录的 `table_count` 字段为当前 sub_tables 的数量。
### 修改位置
- `server/internal/service/database_service.go` 或 handler
- 在 Update 方法中处理 SubTables 时更新 table_count
## 状态
- [x] 后端修改已完成

View File

@@ -1,30 +0,0 @@
# 后端需求 - 保存映射时更新 table_count
## 问题描述
用户在 Database 列表页面看到 Table Mapping 选中 2 个表并保存后,表格的 Tables 列仍然显示 1没有更新为实际选中的表数量。
## 原因
保存 Table Mapping 时,后端没有更新数据库的 `table_count` 字段。
## 需求
在保存子表映射时,需要同时更新数据库的 `table_count` 字段为实际保存的子表数量。
### 修改位置
- `server/internal/service/database_service.go` 或 handler
- 在处理 `SubTables` 保存逻辑后,更新 `database_info` 表的 `table_count` 字段
### 逻辑
```go
// 保存 sub_tables 后,更新 table_count
tableCount := len(subTables)
// 更新数据库记录的 table_count 字段
```
## 状态
- [x] 后端修改已完成

View File

@@ -1,52 +0,0 @@
# Web 前端需求 TODO
## 2026年3月
### 2026-03-06
- [x] **DDL 获取功能** - 后端需在获取表结构时返回 DDL 语句 ✔
- 相关文件:`server/internal/service/database_service.go`
- 函数:`getMySQLTables`, `getPostgresTables`
- 详细需求:[ddl-fetch.md](./ddl-fetch.md)
- [x] **返回结构化 columns 数据** - 后端需返回完整的列信息column_name, data_type, column_type, is_nullable, default_value, column_key, extra, column_comment
- 相关文件:`server/internal/service/database_service.go`
- 函数:`getMySQLTables`, `getPostgresTables`
- 详细需求:[columns-api.md](./columns-api.md)
- [x] **保存和读取字段映射** - 后端需支持保存/读取字段的中文映射名mapped_name
- 相关文件:`server/internal/service/database_service.go`, `server/internal/model/`
- 详细需求:[field-mapping.md](./field-mapping.md)
- [x] **保存和恢复映射状态** - 第二次进入 Map Tables 时需恢复之前选择的表和字段映射 ✔
- 相关文件:`server/internal/service/database_service.go`, `server/internal/model/`
- 详细需求:[mapping-state.md](./mapping-state.md)
- [x] **Neo4j 图谱数据获取** - 前端已完成 ECharts 科幻风格图谱,后端需提供图谱数据接口 ✔
- 前端:使用 ECharts force-directed graph力导向弹簧效果可拖拽hover 高亮
- 详细需求:[neo4j-graphs.md](./neo4j-graphs.md), [neo4j-support.md](./neo4j-support.md)
---
- [x] **Neo4j 接口路由修改** - 后端已完成 ✔
- 新增 `/neo4j/graphs` 接口
- 修改 `/database/neo4j/nodes``/neo4j/nodes`
- 修改 `/database/neo4j/relationships``/neo4j/relationships`
- 详细需求:[neo4j-api-requirement.md](./neo4j-api-requirement.md)
---
### 2026-03-07
- [x] **Neo4j 图谱保存接口** - 后端已完成 ✔
- 接口地址:`POST /database/graph/save`
- 详细需求:[neo4j-graph-save.md](./neo4j-graph-save.md)
- [x] **Neo4j 连接成功后返回 databaseId** - 后端已完成 ✔
- 问题Connect 测试连接成功后没有保存数据库记录,导致后续保存图谱时缺少 databaseId
- 解决方案:/neo4j/check 成功时检查数据库是否已存在,不存在则自动创建并返回 databaseId
- 详细需求:[neo4j-check-return-id.md](./neo4j-check-return-id.md)
---
> 需求完成后请完成者打 ✔

View File

@@ -1,45 +0,0 @@
# Web 前端需求 TODO
## 2026年3月
### 2026-03-08
- [x] **知识库Knowledge BaseAPI** - 后端已完成 ✔
- 创建知识库、获取列表、获取详情、删除
- 上传文档、删除文档、重新解析
- 获取文档预览内容
- 详细需求:[knowledge-base-api.md](./knowledge-base-api.md)
- [x] **编辑时正确处理 sub_tables** - 后端已完成 ✔
- 问题:取消选中 1 个表后保存,再次进入仍显示 2 个表
- 详细需求:[sub-tables-edit.md](./sub-tables-edit.md)
- [x] **知识库存储配置 (MinIO/S3)** - 后端已完成 ✔
- 前端已完成:添加 storage_config 参数传递
- 后端已完成KnowledgeBase 模型添加 storage_config 字段
- 上传文件时使用知识库的 storage_config而非全局配置
- 详细需求:[knowledge-base-api.md](./knowledge-base-api.md)
- [x] **文档列表返回 file_url** - 后端已完成 ✔
- 问题:重新进入知识库后 PDF 无法预览
- 已确认API 返回的 file_url 字段有值
---
### 2026-03-07
- [x] **DDL 编辑功能** - 后端已完成 ✔
- 前端只发送 ddl 字段,不再发送 fields 字段
- 详细需求:[ddl-edit.md](./ddl-edit.md)
- [x] **保存映射时更新 table_count** - 后端已完成 ✔
- 问题:用户保存 2 个子表后Tables 列仍显示 1
- 详细需求:[table-count-update.md](./table-count-update.md)
- [x] **编辑数据库时更新 table_count** - 后端已完成 ✔
- 问题:用户从 2 个子表修改为 1 个后Tables 列没有更新
- 详细需求:[table-count-update-edit.md](./table-count-update-edit.md)
---
> 需求完成后请完成者打 ✔