Compare commits
2 Commits
5b50d6ff9a
...
a07cc4498d
| Author | SHA1 | Date | |
|---|---|---|---|
| a07cc4498d | |||
| 5dc2e403e9 |
@@ -147,7 +147,28 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 自动迁移表
|
// 3. 自动迁移表
|
||||||
db.AutoMigrate(&model.DatabaseInfo{}, &model.SubTableInfo{}, &model.ModelInfo{}, &model.KnowledgeBase{}, &model.KnowledgeDocument{}, &model.User{}, &model.Role{}, &model.Tool{}, &model.MCP{}, &model.Skill{}, &model.Agent{}, &model.AgentSkill{}, &model.AgentKnowledgeBase{}, &model.AgentMemory{}, &model.AgentTeam{}, &model.AgentTask{})
|
if err := db.AutoMigrate(&model.DatabaseInfo{}, &model.SubTableInfo{}, &model.ModelInfo{}, &model.KnowledgeBase{}, &model.KnowledgeDocument{}, &model.User{}, &model.Role{}, &model.Tool{}, &model.MCP{}, &model.Skill{}, &model.Agent{}, &model.AgentSkill{}, &model.AgentKnowledgeBase{}, &model.AgentMemory{}, &model.AgentTeam{}, &model.AgentTask{}).Error; err != nil {
|
||||||
|
log.Printf("Warning: AutoMigrate error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2 确保 agents 表存在(使用 SQL 强制创建)
|
||||||
|
db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS agents (
|
||||||
|
id VARCHAR(191) PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
owner_id VARCHAR(50) NOT NULL,
|
||||||
|
INDEX idx_owner_id (owner_id),
|
||||||
|
skills TEXT,
|
||||||
|
role_description TEXT,
|
||||||
|
model_provider VARCHAR(50),
|
||||||
|
model_name VARCHAR(100),
|
||||||
|
is_supervisor TINYINT(1) DEFAULT 0,
|
||||||
|
is_active TINYINT(1) DEFAULT 1,
|
||||||
|
created_at DATETIME(3),
|
||||||
|
updated_at DATETIME(3)
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
// 3.1 确保 users 和 roles 表存在(使用 SQL 强制创建)
|
// 3.1 确保 users 和 roles 表存在(使用 SQL 强制创建)
|
||||||
db.Exec(`
|
db.Exec(`
|
||||||
@@ -297,7 +318,7 @@ func main() {
|
|||||||
toolService := service.NewToolService(toolRepo)
|
toolService := service.NewToolService(toolRepo)
|
||||||
mcpService := service.NewMCPService(mcpRepo)
|
mcpService := service.NewMCPService(mcpRepo)
|
||||||
skillService := service.NewSkillService(skillRepo)
|
skillService := service.NewSkillService(skillRepo)
|
||||||
agentService := service.NewAgentService(cfg.PythonServiceURL, modelRepo)
|
agentService := service.NewAgentService(cfg.PythonServiceURL, modelRepo, agentRepo)
|
||||||
memoryService := service.NewMemoryService(agentRepo)
|
memoryService := service.NewMemoryService(agentRepo)
|
||||||
|
|
||||||
// 4.2 初始化默认工具
|
// 4.2 初始化默认工具
|
||||||
@@ -502,6 +523,9 @@ func main() {
|
|||||||
{
|
{
|
||||||
agentGroup.GET("/list", agentHandler.ListAgents)
|
agentGroup.GET("/list", agentHandler.ListAgents)
|
||||||
agentGroup.POST("/create", agentHandler.CreateAgent)
|
agentGroup.POST("/create", agentHandler.CreateAgent)
|
||||||
|
agentGroup.PUT("/:id/status", agentHandler.UpdateAgentStatus)
|
||||||
|
agentGroup.PUT("/:id", agentHandler.UpdateAgent)
|
||||||
|
agentGroup.DELETE("/:id", agentHandler.DeleteAgent)
|
||||||
agentGroup.POST("/chat", agentHandler.Chat)
|
agentGroup.POST("/chat", agentHandler.Chat)
|
||||||
agentGroup.POST("/chat/stream", agentHandler.ChatStream)
|
agentGroup.POST("/chat/stream", agentHandler.ChatStream)
|
||||||
agentGroup.POST("/team/chat", agentHandler.TeamChat)
|
agentGroup.POST("/team/chat", agentHandler.TeamChat)
|
||||||
|
|||||||
@@ -243,3 +243,103 @@ func (h *AgentHandler) ListAgents(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
c.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAgentStatusRequest 更新状态请求
|
||||||
|
type UpdateAgentStatusRequest struct {
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAgentStatus 更新智能体状态
|
||||||
|
// @Summary 更新智能体状态
|
||||||
|
// @Tags 智能体管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Agent ID"
|
||||||
|
// @Param request body UpdateAgentStatusRequest true "状态请求"
|
||||||
|
// @Success 200 {object} gin.H
|
||||||
|
// @Router /api/agent/{id}/status [put]
|
||||||
|
func (h *AgentHandler) UpdateAgentStatus(c *gin.Context) {
|
||||||
|
agentID := c.Param("id")
|
||||||
|
if agentID == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req UpdateAgentStatusRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.agentService.UpdateAgentStatus(agentID, req.IsActive)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "status updated successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAgent 删除智能体
|
||||||
|
// @Summary 删除智能体
|
||||||
|
// @Tags 智能体管理
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Agent ID"
|
||||||
|
// @Success 200 {object} gin.H
|
||||||
|
// @Router /api/agent/{id} [delete]
|
||||||
|
func (h *AgentHandler) DeleteAgent(c *gin.Context) {
|
||||||
|
agentID := c.Param("id")
|
||||||
|
if agentID == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.agentService.DeleteAgent(agentID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "agent deleted successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAgentRequest 更新智能体请求
|
||||||
|
type UpdateAgentRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Skills []string `json:"skills"`
|
||||||
|
RoleDescription string `json:"role_description"`
|
||||||
|
ModelProvider string `json:"model_provider"`
|
||||||
|
ModelName string `json:"model_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAgent 更新智能体
|
||||||
|
// @Summary 更新智能体
|
||||||
|
// @Tags 智能体管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Agent ID"
|
||||||
|
// @Param request body UpdateAgentRequest true "更新请求"
|
||||||
|
// @Success 200 {object} gin.H
|
||||||
|
// @Router /api/agent/{id} [put]
|
||||||
|
func (h *AgentHandler) UpdateAgent(c *gin.Context) {
|
||||||
|
agentID := c.Param("id")
|
||||||
|
if agentID == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "agent id is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req UpdateAgentRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.agentService.UpdateAgent(agentID, req.Name, req.Description, req.Skills, req.RoleDescription, req.ModelProvider, req.ModelName)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "agent updated successfully"})
|
||||||
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ func (h *SkillHandler) Create(c *gin.Context) {
|
|||||||
SkillType: skillType,
|
SkillType: skillType,
|
||||||
SkillDesc: skillDesc,
|
SkillDesc: skillDesc,
|
||||||
Path: filepath.Join(skillPath, "SKILL.md"),
|
Path: filepath.Join(skillPath, "SKILL.md"),
|
||||||
Status: "active",
|
Status: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录创建者
|
// 记录创建者
|
||||||
@@ -386,7 +386,7 @@ func (h *SkillHandler) Update(c *gin.Context) {
|
|||||||
skillName := req.SkillName
|
skillName := req.SkillName
|
||||||
skillDesc := req.SkillDesc
|
skillDesc := req.SkillDesc
|
||||||
skillType := req.SkillType
|
skillType := req.SkillType
|
||||||
status := req.Status
|
var status int
|
||||||
|
|
||||||
if skillName == "" {
|
if skillName == "" {
|
||||||
skillName = existingSkill.SkillName
|
skillName = existingSkill.SkillName
|
||||||
@@ -397,8 +397,18 @@ func (h *SkillHandler) Update(c *gin.Context) {
|
|||||||
if skillType == "" {
|
if skillType == "" {
|
||||||
skillType = existingSkill.SkillType
|
skillType = existingSkill.SkillType
|
||||||
}
|
}
|
||||||
if status == "" {
|
// 处理 status:如果是空字符串则使用现有值,否则转换为整数
|
||||||
|
if req.Status == "" {
|
||||||
status = existingSkill.Status
|
status = existingSkill.Status
|
||||||
|
} else {
|
||||||
|
// 支持传入数字或字符串
|
||||||
|
if req.Status == "1" || req.Status == "active" {
|
||||||
|
status = 1
|
||||||
|
} else if req.Status == "0" || req.Status == "inactive" {
|
||||||
|
status = 0
|
||||||
|
} else {
|
||||||
|
status = existingSkill.Status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取项目根目录
|
// 获取项目根目录
|
||||||
|
|||||||
@@ -15,24 +15,18 @@ const (
|
|||||||
|
|
||||||
// Agent 智能体
|
// Agent 智能体
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
ID string `json:"id" gorm:"primaryKey"`
|
ID string `json:"id" gorm:"primaryKey"`
|
||||||
Name string `json:"name" gorm:"size:100;not null"`
|
Name string `json:"name" gorm:"size:100;not null"`
|
||||||
Description string `json:"description" gorm:"type:text"`
|
Description string `json:"description" gorm:"type:text"`
|
||||||
OwnerID string `json:"owner_id" gorm:"size:50;not null;index"`
|
OwnerID string `json:"owner_id" gorm:"size:50;not null;index"`
|
||||||
|
|
||||||
// Agent能力配置
|
// 技能列表(JSON数组)
|
||||||
Capabilities []string `json:"capabilities" gorm:"type:text"` // JSON数组,可用工具列表
|
Skills []string `json:"skills" gorm:"type:text;serializer:json"`
|
||||||
MemoryLimit int64 `json:"memory_limit" gorm:"default:134217728"` // 128MB
|
|
||||||
Timeout int `json:"timeout" gorm:"default:60"` // 60秒
|
|
||||||
|
|
||||||
// 安全配置
|
// 角色描述和模型配置
|
||||||
SecurityLevel SecurityLevel `json:"security_level" gorm:"size:20;default:'safe'"`
|
|
||||||
AllowDangerousTools bool `json:"allow_dangerous_tools" gorm:"default:false"`
|
|
||||||
|
|
||||||
// 扩展字段:角色描述和模型配置
|
|
||||||
RoleDescription string `json:"role_description" gorm:"type:text"` // 角色描述 (System Prompt)
|
RoleDescription string `json:"role_description" gorm:"type:text"` // 角色描述 (System Prompt)
|
||||||
ModelProvider string `json:"model_provider" gorm:"size:50"` // 模型提供商: openai/anthropic
|
ModelProvider string `json:"model_provider" gorm:"size:50"` // 模型提供商: openai/anthropic
|
||||||
ModelName string `json:"model_name" gorm:"size:100"` // 模型名称
|
ModelName string `json:"model_name" gorm:"size:100"` // 模型名称
|
||||||
|
|
||||||
// 协作模式
|
// 协作模式
|
||||||
IsSupervisor bool `json:"is_supervisor" gorm:"default:false"` // 是否为主智能体
|
IsSupervisor bool `json:"is_supervisor" gorm:"default:false"` // 是否为主智能体
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ type Skill struct {
|
|||||||
SkillType string `json:"skill_type" gorm:"size:20;not null"` // system / user
|
SkillType string `json:"skill_type" gorm:"size:20;not null"` // system / user
|
||||||
SkillDesc string `json:"skill_desc" gorm:"type:text"`
|
SkillDesc string `json:"skill_desc" gorm:"type:text"`
|
||||||
Path string `json:"path" gorm:"size:500"` // skill 文件路径
|
Path string `json:"path" gorm:"size:500"` // skill 文件路径
|
||||||
Status string `json:"status" gorm:"size:20;default:'active'"`
|
Status int `json:"status" gorm:"default:1"` // 1: active, 0: inactive
|
||||||
CreatedBy string `json:"created_by" gorm:"size:100"` // 创建者用户名
|
CreatedBy string `json:"created_by" gorm:"size:100"` // 创建者用户名
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ func (r *ModelRepository) FindByID(id string) (*model.ModelInfo, error) {
|
|||||||
return &model, nil
|
return &model, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindByName 根据名称获取模型
|
||||||
|
func (r *ModelRepository) FindByName(name string) (*model.ModelInfo, error) {
|
||||||
|
var model model.ModelInfo
|
||||||
|
err := r.db.Where("name = ?", name).First(&model).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create 创建模型
|
// Create 创建模型
|
||||||
func (r *ModelRepository) Create(info *model.ModelInfo) error {
|
func (r *ModelRepository) Create(info *model.ModelInfo) error {
|
||||||
return r.db.Create(info).Error
|
return r.db.Create(info).Error
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (r *SkillRepository) Update(skill *model.Skill) error {
|
|||||||
if skill.SkillType != "" {
|
if skill.SkillType != "" {
|
||||||
updates["skill_type"] = skill.SkillType
|
updates["skill_type"] = skill.SkillType
|
||||||
}
|
}
|
||||||
if skill.Status != "" {
|
if skill.Status != 0 {
|
||||||
updates["status"] = skill.Status
|
updates["status"] = skill.Status
|
||||||
}
|
}
|
||||||
if skill.Path != "" {
|
if skill.Path != "" {
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"x-agents/server/internal/model"
|
||||||
"x-agents/server/internal/repository"
|
"x-agents/server/internal/repository"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AgentChatRequest Python Agent 对话请求
|
// AgentChatRequest Python Agent 对话请求
|
||||||
@@ -63,16 +65,18 @@ type AgentService struct {
|
|||||||
pythonURL string
|
pythonURL string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
modelRepo *repository.ModelRepository
|
modelRepo *repository.ModelRepository
|
||||||
|
agentRepo *repository.AgentRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAgentService 创建 Agent 服务
|
// NewAgentService 创建 Agent 服务
|
||||||
func NewAgentService(pythonURL string, modelRepo *repository.ModelRepository) *AgentService {
|
func NewAgentService(pythonURL string, modelRepo *repository.ModelRepository, agentRepo *repository.AgentRepository) *AgentService {
|
||||||
return &AgentService{
|
return &AgentService{
|
||||||
pythonURL: pythonURL,
|
pythonURL: pythonURL,
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: 120 * time.Second, // Agent 可能需要较长时间
|
Timeout: 120 * time.Second, // Agent 可能需要较长时间
|
||||||
},
|
},
|
||||||
modelRepo: modelRepo,
|
modelRepo: modelRepo,
|
||||||
|
agentRepo: agentRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,16 +286,16 @@ func (s *AgentService) ChatStream(c interface{}, agentID int, message, sessionID
|
|||||||
|
|
||||||
// CreateAgentRequest 创建智能体请求
|
// CreateAgentRequest 创建智能体请求
|
||||||
type CreateAgentRequest struct {
|
type CreateAgentRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
SkillsMode string `json:"skills_mode"`
|
SkillsMode string `json:"skillsMode"`
|
||||||
Skills []string `json:"skills"`
|
Skills []string `json:"skills"`
|
||||||
Knowledge string `json:"knowledge"`
|
Knowledge string `json:"knowledge"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
ModelProvider string `json:"model_provider"`
|
ModelProvider string `json:"model_provider"`
|
||||||
ModelName string `json:"model_name"`
|
ModelName string `json:"model_name"`
|
||||||
UserID int `json:"user_id"`
|
UserID int `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAgentResponse 创建智能体响应
|
// CreateAgentResponse 创建智能体响应
|
||||||
@@ -303,56 +307,54 @@ type CreateAgentResponse struct {
|
|||||||
|
|
||||||
// CreateAgent 创建智能体
|
// CreateAgent 创建智能体
|
||||||
func (s *AgentService) CreateAgent(req CreateAgentRequest, userID int) (*CreateAgentResponse, error) {
|
func (s *AgentService) CreateAgent(req CreateAgentRequest, userID int) (*CreateAgentResponse, error) {
|
||||||
url := fmt.Sprintf("%s/agent/create", s.pythonURL)
|
if s.agentRepo == nil {
|
||||||
|
log.Printf("[AgentService] CreateAgent: agentRepo is nil!")
|
||||||
// 构建请求体
|
return nil, fmt.Errorf("agent repository not initialized")
|
||||||
pythonReq := CreateAgentRequest{
|
|
||||||
Name: req.Name,
|
|
||||||
Description: req.Description,
|
|
||||||
Avatar: req.Avatar,
|
|
||||||
SkillsMode: req.SkillsMode,
|
|
||||||
Skills: req.Skills,
|
|
||||||
Knowledge: req.Knowledge,
|
|
||||||
Prompt: req.Prompt,
|
|
||||||
ModelProvider: req.ModelProvider,
|
|
||||||
ModelName: req.ModelName,
|
|
||||||
UserID: userID,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonData, err := json.Marshal(pythonReq)
|
log.Printf("[AgentService] CreateAgent: %s (userID: %d), skillsMode: %s, skills: %v", req.Name, userID, req.SkillsMode, req.Skills)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
// 处理 skills:根据 skillsMode 决定
|
||||||
|
var skills []string
|
||||||
|
if req.SkillsMode == "all" {
|
||||||
|
// "all" 模式用 "*" 表示
|
||||||
|
skills = []string{"*"}
|
||||||
|
} else {
|
||||||
|
skills = req.Skills
|
||||||
}
|
}
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
// 创建 Agent 模型并保存到数据库
|
||||||
if err != nil {
|
agent := &model.Agent{
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
ID: uuid.New().String(),
|
||||||
}
|
Name: req.Name,
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
Description: req.Description,
|
||||||
|
OwnerID: fmt.Sprintf("%d", userID),
|
||||||
resp, err := s.client.Do(httpReq)
|
Skills: skills,
|
||||||
if err != nil {
|
RoleDescription: req.Prompt,
|
||||||
return nil, fmt.Errorf("failed to call python agent: %w", err)
|
ModelProvider: req.ModelProvider,
|
||||||
}
|
ModelName: req.ModelName,
|
||||||
defer resp.Body.Close()
|
IsActive: true,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
body, err := io.ReadAll(resp.Body)
|
UpdatedAt: time.Now(),
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
log.Printf("[AgentService] CreateAgent: calling Create, agent = %+v", agent)
|
||||||
return nil, fmt.Errorf("python agent error: %s", string(body))
|
if err := s.agentRepo.Create(agent); err != nil {
|
||||||
|
log.Printf("[AgentService] CreateAgent: Create error: %v", err)
|
||||||
|
return nil, fmt.Errorf("failed to create agent: %w", err)
|
||||||
}
|
}
|
||||||
|
log.Printf("[AgentService] CreateAgent: created successfully")
|
||||||
|
|
||||||
var result CreateAgentResponse
|
log.Printf("[AgentService] Agent created in database: %s (ID: %s)", agent.Name, agent.ID)
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[AgentService] Agent created: %s (ID: %d)", result.Name, result.AgentID)
|
// 解析 agent ID 为整数返回
|
||||||
|
agentIDInt := int(time.Now().Unix()) % 100000
|
||||||
|
|
||||||
return &result, nil
|
return &CreateAgentResponse{
|
||||||
|
AgentID: agentIDInt,
|
||||||
|
Name: agent.Name,
|
||||||
|
Message: "Agent created successfully",
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAgentsResponse 获取智能体列表响应
|
// ListAgentsResponse 获取智能体列表响应
|
||||||
@@ -360,37 +362,120 @@ type ListAgentsResponse struct {
|
|||||||
Agents []interface{} `json:"agents"`
|
Agents []interface{} `json:"agents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAgents 获取智能体列表
|
// ListAgents 获取智能体列表(从数据库获取)
|
||||||
func (s *AgentService) ListAgents() (*ListAgentsResponse, error) {
|
func (s *AgentService) ListAgents() (*ListAgentsResponse, error) {
|
||||||
url := fmt.Sprintf("%s/agent/list", s.pythonURL)
|
if s.agentRepo == nil {
|
||||||
|
log.Printf("[AgentService] ListAgents: agentRepo is nil!")
|
||||||
|
return nil, fmt.Errorf("agent repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("GET", url, nil)
|
log.Printf("[AgentService] ListAgents: calling FindAll")
|
||||||
|
agents, err := s.agentRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
log.Printf("[AgentService] ListAgents: FindAll error: %v", err)
|
||||||
}
|
return nil, fmt.Errorf("failed to list agents: %w", err)
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
resp, err := s.client.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to call python agent: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
log.Printf("[AgentService] ListAgents: found %d agents", len(agents))
|
||||||
return nil, fmt.Errorf("python agent error: %s", string(body))
|
|
||||||
|
// 转换为 interface{} 切片
|
||||||
|
agentsList := make([]interface{}, len(agents))
|
||||||
|
for i, agent := range agents {
|
||||||
|
agentsList[i] = agent
|
||||||
|
log.Printf("[AgentService] ListAgents: agent[%d] = %+v", i, agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result ListAgentsResponse
|
return &ListAgentsResponse{
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
Agents: agentsList,
|
||||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[AgentService] Listed agents: %d", len(result.Agents))
|
// UpdateAgentStatus 更新智能体状态
|
||||||
|
func (s *AgentService) UpdateAgentStatus(agentID string, isActive bool) error {
|
||||||
return &result, nil
|
if s.agentRepo == nil {
|
||||||
|
return fmt.Errorf("agent repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[AgentService] UpdateAgentStatus: id=%s, isActive=%v", agentID, isActive)
|
||||||
|
|
||||||
|
// 检查是否存在
|
||||||
|
agent, err := s.agentRepo.FindByID(agentID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("agent not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.IsActive = isActive
|
||||||
|
agent.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := s.agentRepo.Update(agent); err != nil {
|
||||||
|
return fmt.Errorf("failed to update agent: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[AgentService] Agent status updated: id=%s, isActive=%v", agentID, isActive)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAgent 删除智能体
|
||||||
|
func (s *AgentService) DeleteAgent(agentID string) error {
|
||||||
|
if s.agentRepo == nil {
|
||||||
|
return fmt.Errorf("agent repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[AgentService] DeleteAgent: id=%s", agentID)
|
||||||
|
|
||||||
|
// 检查是否存在
|
||||||
|
_, err := s.agentRepo.FindByID(agentID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("agent not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.agentRepo.Delete(agentID); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete agent: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[AgentService] Agent deleted: id=%s", agentID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAgent 更新智能体
|
||||||
|
func (s *AgentService) UpdateAgent(agentID, name, description string, skills []string, roleDescription, modelProvider, modelName string) error {
|
||||||
|
if s.agentRepo == nil {
|
||||||
|
return fmt.Errorf("agent repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[AgentService] UpdateAgent: id=%s, name=%s", agentID, name)
|
||||||
|
|
||||||
|
// 检查是否存在
|
||||||
|
agent, err := s.agentRepo.FindByID(agentID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("agent not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字段
|
||||||
|
if name != "" {
|
||||||
|
agent.Name = name
|
||||||
|
}
|
||||||
|
if description != "" {
|
||||||
|
agent.Description = description
|
||||||
|
}
|
||||||
|
if skills != nil {
|
||||||
|
agent.Skills = skills
|
||||||
|
}
|
||||||
|
if roleDescription != "" {
|
||||||
|
agent.RoleDescription = roleDescription
|
||||||
|
}
|
||||||
|
if modelProvider != "" {
|
||||||
|
agent.ModelProvider = modelProvider
|
||||||
|
}
|
||||||
|
if modelName != "" {
|
||||||
|
agent.ModelName = modelName
|
||||||
|
}
|
||||||
|
agent.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := s.agentRepo.Update(agent); err != nil {
|
||||||
|
return fmt.Errorf("failed to update agent: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[AgentService] Agent updated: id=%s", agentID)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,14 +166,11 @@ func (s *ChatService) ListAgents(userID string) ([]model.Agent, error) {
|
|||||||
// CreateAgent 创建新的 Agent
|
// CreateAgent 创建新的 Agent
|
||||||
func (s *ChatService) CreateAgent(userID string, name, description string) (*model.Agent, error) {
|
func (s *ChatService) CreateAgent(userID string, name, description string) (*model.Agent, error) {
|
||||||
agent := &model.Agent{
|
agent := &model.Agent{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: description,
|
Description: description,
|
||||||
OwnerID: userID,
|
OwnerID: userID,
|
||||||
SecurityLevel: model.SecurityLevelSafe,
|
IsActive: true,
|
||||||
IsActive: true,
|
|
||||||
Timeout: 60,
|
|
||||||
MemoryLimit: 134217728, // 128MB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.agentRepo.Create(agent); err != nil {
|
if err := s.agentRepo.Create(agent); err != nil {
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ func (s *ModelService) GetByID(id string) (*model.ModelInfo, error) {
|
|||||||
|
|
||||||
// Create 创建模型
|
// Create 创建模型
|
||||||
func (s *ModelService) Create(req model.CreateModelRequest) (*model.ModelInfo, error) {
|
func (s *ModelService) Create(req model.CreateModelRequest) (*model.ModelInfo, error) {
|
||||||
|
// 检查模型名称是否已存在
|
||||||
|
existing, err := s.repo.FindByName(req.Name)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("model with name '%s' already exists", req.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 如果没有提供状态,默认设置为 inactive
|
// 如果没有提供状态,默认设置为 inactive
|
||||||
status := req.Status
|
status := req.Status
|
||||||
if status == "" {
|
if status == "" {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([
|
|||||||
SkillType: skillType,
|
SkillType: skillType,
|
||||||
SkillDesc: skillInfo.SkillDesc,
|
SkillDesc: skillInfo.SkillDesc,
|
||||||
Path: skillPath,
|
Path: skillPath,
|
||||||
Status: "active",
|
Status: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
skills = append(skills, skill)
|
skills = append(skills, skill)
|
||||||
|
|||||||
@@ -16,26 +16,8 @@ const showSidebar = computed(() => route.path !== '/login' && route.path !== '/'
|
|||||||
|
|
||||||
<!-- 右侧内容区 -->
|
<!-- 右侧内容区 -->
|
||||||
<main :class="showSidebar ? 'ml-64' : ''" class="flex-1 min-h-screen">
|
<main :class="showSidebar ? 'ml-64' : ''" class="flex-1 min-h-screen">
|
||||||
<router-view class="page-content" />
|
<router-view />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</ElConfigProvider>
|
</ElConfigProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* 页面进入动画 */
|
|
||||||
.page-content {
|
|
||||||
animation: page-enter 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes page-enter {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import './database/database.css'
|
import './database/database.css'
|
||||||
|
|
||||||
const { getCurrentUser, logout } = useAuth()
|
const { getCurrentUser, logout } = useAuth()
|
||||||
@@ -261,7 +262,7 @@ const statusClass = (status: string) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-sm text-gray-500">
|
<div class="mt-4 text-sm text-gray-500">
|
||||||
Member since: {{ currentUser?.created_at ? new Date(currentUser.created_at).toLocaleDateString() : 'N/A' }}
|
Member since: {{ currentUser?.created_at ? formatDate(currentUser.created_at, 'YYYY/MM/DD HH:mm') : 'N/A' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,7 +342,7 @@ const statusClass = (status: string) => {
|
|||||||
<span class="capitalize text-sm">{{ user.status }}</span>
|
<span class="capitalize text-sm">{{ user.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ user.createdAt }}</td>
|
<td class="px-5 py-4 text-gray-400 text-sm">{{ formatDate(user.createdAt, 'YYYY/MM/DD HH:mm') }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
|
import { Play, Pause, Edit, Trash2 } from 'lucide-vue-next'
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:8082'
|
const API_BASE = 'http://localhost:8082'
|
||||||
|
|
||||||
@@ -33,11 +35,10 @@ const fetchAgents = async () => {
|
|||||||
description: agent.description || '',
|
description: agent.description || '',
|
||||||
accentColor: '#f97316',
|
accentColor: '#f97316',
|
||||||
gradient: 'from-orange-500/20 to-amber-500/20',
|
gradient: 'from-orange-500/20 to-amber-500/20',
|
||||||
status: 'stopped' as const,
|
status: agent.is_active ? 'active' : 'inactive',
|
||||||
framework: agent.skills?.length > 0 ? agent.skills.join(', ') : 'None',
|
skills: agent.skills?.length > 0 ? agent.skills.join(', ') : 'None',
|
||||||
model: agent.model_name || 'claude-sonnet-4-20250514',
|
model: agent.model_name || 'None',
|
||||||
mcpServers: 0,
|
createdAt: agent.created_at ? formatDate(agent.created_at, 'YYYY/MM/DD HH:mm') : formatDate(new Date(), 'YYYY/MM/DD HH:mm'),
|
||||||
createdAt: new Date().toISOString().split('T')[0],
|
|
||||||
}))
|
}))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch agents:', error)
|
console.error('Failed to fetch agents:', error)
|
||||||
@@ -56,6 +57,7 @@ const newAgent = ref({
|
|||||||
knowledge: '',
|
knowledge: '',
|
||||||
prompt: '',
|
prompt: '',
|
||||||
avatar: '🤖',
|
avatar: '🤖',
|
||||||
|
modelId: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Skills 选择器状态
|
// Skills 选择器状态
|
||||||
@@ -94,6 +96,20 @@ const handleSkillsModeClick = (mode: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理编辑弹窗中的技能模式点击
|
||||||
|
const handleSkillsModeClickEdit = (mode: string) => {
|
||||||
|
if (mode === 'all' || mode === 'include' || mode === 'exclude') {
|
||||||
|
editingAgent.value.skillsMode = mode
|
||||||
|
if (mode === 'all') {
|
||||||
|
editingAgent.value.selectedSkills = []
|
||||||
|
}
|
||||||
|
showSkillsDropdown.value = false
|
||||||
|
if (mode === 'include' || mode === 'exclude') {
|
||||||
|
showSubSkillsDropdown.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 切换子下拉框
|
// 切换子下拉框
|
||||||
const toggleSubSkillsDropdown = () => {
|
const toggleSubSkillsDropdown = () => {
|
||||||
showSubSkillsDropdown.value = !showSubSkillsDropdown.value
|
showSubSkillsDropdown.value = !showSubSkillsDropdown.value
|
||||||
@@ -137,6 +153,37 @@ const avatarOptions = [
|
|||||||
const skillsList = ref<Skill[]>([])
|
const skillsList = ref<Skill[]>([])
|
||||||
const skillsLoading = ref(false)
|
const skillsLoading = ref(false)
|
||||||
|
|
||||||
|
// Models 列表
|
||||||
|
interface ModelOption {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
provider: string
|
||||||
|
model: string
|
||||||
|
}
|
||||||
|
const modelsList = ref<ModelOption[]>([])
|
||||||
|
const modelsLoading = ref(false)
|
||||||
|
|
||||||
|
// 获取 models 列表
|
||||||
|
const fetchModels = async () => {
|
||||||
|
modelsLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/model/list`)
|
||||||
|
const result = await response.json()
|
||||||
|
if (result.list) {
|
||||||
|
modelsList.value = result.list.map((m: any) => ({
|
||||||
|
id: m.id,
|
||||||
|
name: m.name,
|
||||||
|
provider: m.provider,
|
||||||
|
model: m.model
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch models:', error)
|
||||||
|
} finally {
|
||||||
|
modelsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取 skills 列表
|
// 获取 skills 列表
|
||||||
const fetchSkills = async () => {
|
const fetchSkills = async () => {
|
||||||
skillsLoading.value = true
|
skillsLoading.value = true
|
||||||
@@ -242,6 +289,7 @@ const handleClickOutside = (e: MouseEvent) => {
|
|||||||
// 页面加载时获取 skills 和 agents
|
// 页面加载时获取 skills 和 agents
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchSkills()
|
fetchSkills()
|
||||||
|
fetchModels()
|
||||||
fetchAgents()
|
fetchAgents()
|
||||||
document.addEventListener('click', handleClickOutside)
|
document.addEventListener('click', handleClickOutside)
|
||||||
})
|
})
|
||||||
@@ -259,17 +307,21 @@ const openCreateModal = () => {
|
|||||||
selectedSkills: [],
|
selectedSkills: [],
|
||||||
knowledge: '',
|
knowledge: '',
|
||||||
prompt: '',
|
prompt: '',
|
||||||
avatar: '🤖'
|
avatar: '🤖',
|
||||||
|
modelId: ''
|
||||||
}
|
}
|
||||||
showCreateModal.value = true
|
showCreateModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建智能体
|
// 创建智能体
|
||||||
const createAgent = async () => {
|
const createAgent = async () => {
|
||||||
if (!newAgent.value.name || (newAgent.value.skillsMode !== 'all' && newAgent.value.selectedSkills.length === 0) || !newAgent.value.knowledge) {
|
if (!newAgent.value.name || (newAgent.value.skillsMode !== 'all' && newAgent.value.selectedSkills.length === 0)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取选中的模型信息
|
||||||
|
const selectedModel = modelsList.value.find(m => m.id === newAgent.value.modelId)
|
||||||
|
|
||||||
isCreating.value = true
|
isCreating.value = true
|
||||||
try {
|
try {
|
||||||
// 调用后端 API 创建智能体
|
// 调用后端 API 创建智能体
|
||||||
@@ -282,10 +334,11 @@ const createAgent = async () => {
|
|||||||
name: newAgent.value.name,
|
name: newAgent.value.name,
|
||||||
description: newAgent.value.description,
|
description: newAgent.value.description,
|
||||||
avatar: newAgent.value.avatar,
|
avatar: newAgent.value.avatar,
|
||||||
skills_mode: newAgent.value.skillsMode,
|
skillsMode: newAgent.value.skillsMode,
|
||||||
skills: newAgent.value.selectedSkills,
|
skills: newAgent.value.selectedSkills,
|
||||||
knowledge: newAgent.value.knowledge,
|
|
||||||
prompt: newAgent.value.prompt,
|
prompt: newAgent.value.prompt,
|
||||||
|
model_provider: selectedModel?.provider || '',
|
||||||
|
model_name: selectedModel?.model || '',
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -307,10 +360,9 @@ const createAgent = async () => {
|
|||||||
accentColor: '#f97316',
|
accentColor: '#f97316',
|
||||||
gradient: 'from-orange-500/20 to-amber-500/20',
|
gradient: 'from-orange-500/20 to-amber-500/20',
|
||||||
status: 'stopped',
|
status: 'stopped',
|
||||||
framework: skillsLabels || 'None',
|
skills: skillsLabels || 'None',
|
||||||
model: knowledgeOptions.find(k => k.value === newAgent.value.knowledge)?.label || newAgent.value.knowledge,
|
knowledge: knowledgeOptions.find(k => k.value === newAgent.value.knowledge)?.label || newAgent.value.knowledge,
|
||||||
mcpServers: 0,
|
createdAt: formatDate(new Date(), 'YYYY/MM/DD HH:mm'),
|
||||||
createdAt: new Date().toISOString().split('T')[0],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
showCreateModal.value = false
|
showCreateModal.value = false
|
||||||
@@ -330,7 +382,7 @@ const filterStatus = ref('all')
|
|||||||
const filteredAgents = computed(() => {
|
const filteredAgents = computed(() => {
|
||||||
return agents.value.filter(agent => {
|
return agents.value.filter(agent => {
|
||||||
const matchSearch = agent.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
const matchSearch = agent.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||||
agent.framework.toLowerCase().includes(searchQuery.value.toLowerCase())
|
agent.skills.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||||
const matchStatus = filterStatus.value === 'all' || agent.status === filterStatus.value
|
const matchStatus = filterStatus.value === 'all' || agent.status === filterStatus.value
|
||||||
return matchSearch && matchStatus
|
return matchSearch && matchStatus
|
||||||
})
|
})
|
||||||
@@ -339,27 +391,158 @@ const filteredAgents = computed(() => {
|
|||||||
// 统计数据
|
// 统计数据
|
||||||
const stats = computed(() => ({
|
const stats = computed(() => ({
|
||||||
total: agents.value.length,
|
total: agents.value.length,
|
||||||
running: agents.value.filter(a => a.status === 'running').length,
|
running: agents.value.filter(a => a.status === 'active').length,
|
||||||
stopped: agents.value.filter(a => a.status === 'stopped').length,
|
stopped: agents.value.filter(a => a.status === 'inactive').length,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 状态颜色
|
// 状态颜色
|
||||||
const statusClass = (status: string) => {
|
const statusClass = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'running': return 'bg-primary-success'
|
case 'active': return 'bg-green-500'
|
||||||
case 'stopped': return 'bg-gray-500'
|
case 'inactive': return 'bg-gray-500'
|
||||||
default: return 'bg-gray-500'
|
default: return 'bg-gray-500'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换状态
|
// 切换状态
|
||||||
const toggleStatus = (agent: any) => {
|
const toggleStatus = async (agent: any) => {
|
||||||
agent.status = agent.status === 'running' ? 'stopped' : 'running'
|
const newStatus = agent.status === 'active' ? false : true
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/agent/${agent.id}/status`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ is_active: newStatus })
|
||||||
|
})
|
||||||
|
if (response.ok) {
|
||||||
|
agent.status = newStatus ? 'active' : 'inactive'
|
||||||
|
} else {
|
||||||
|
ElMessage.error('Failed to update status')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update status:', error)
|
||||||
|
ElMessage.error('Failed to update status')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除 Agent
|
// 删除 Agent
|
||||||
const deleteAgent = (id: number) => {
|
const deleteAgent = async (id: string) => {
|
||||||
agents.value = agents.value.filter(a => a.id !== id)
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/agent/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
if (response.ok) {
|
||||||
|
agents.value = agents.value.filter(a => a.id !== id)
|
||||||
|
ElMessage.success('Agent deleted successfully')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('Failed to delete agent')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete agent:', error)
|
||||||
|
ElMessage.error('Failed to delete agent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑弹窗状态
|
||||||
|
const showEditModal = ref(false)
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const editingAgent = ref({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
avatar: '🤖',
|
||||||
|
skillsMode: 'all' as 'all' | 'include' | 'exclude',
|
||||||
|
selectedSkills: [] as string[],
|
||||||
|
modelId: '',
|
||||||
|
prompt: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 打开编辑弹窗
|
||||||
|
const openEdit = (agent: any) => {
|
||||||
|
// 解析 skills
|
||||||
|
let selectedSkills: string[] = []
|
||||||
|
let skillsMode: 'all' | 'include' | 'exclude' = 'all'
|
||||||
|
if (agent.skills === '*') {
|
||||||
|
skillsMode = 'all'
|
||||||
|
selectedSkills = []
|
||||||
|
} else if (agent.skills && agent.skills !== 'None') {
|
||||||
|
skillsMode = 'include'
|
||||||
|
selectedSkills = agent.skills.split(',').map((s: string) => s.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找对应的 modelId
|
||||||
|
const model = modelsList.value.find(m => m.name === agent.model)
|
||||||
|
|
||||||
|
editingAgent.value = {
|
||||||
|
id: agent.id,
|
||||||
|
name: agent.name,
|
||||||
|
description: agent.description || '',
|
||||||
|
avatar: agent.avatar || '🤖',
|
||||||
|
skillsMode,
|
||||||
|
selectedSkills,
|
||||||
|
modelId: model?.id || '',
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
showEditModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存编辑
|
||||||
|
const saveEdit = async () => {
|
||||||
|
if (!editingAgent.value.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 skills
|
||||||
|
let skills: string[] = []
|
||||||
|
if (editingAgent.value.skillsMode === 'all') {
|
||||||
|
skills = ['*']
|
||||||
|
} else {
|
||||||
|
skills = editingAgent.value.selectedSkills
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取选中的模型信息
|
||||||
|
const selectedModel = modelsList.value.find(m => m.id === editingAgent.value.modelId)
|
||||||
|
|
||||||
|
isEditing.value = true
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/agent/${editingAgent.value.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: editingAgent.value.name,
|
||||||
|
description: editingAgent.value.description,
|
||||||
|
skills: skills,
|
||||||
|
role_description: editingAgent.value.prompt,
|
||||||
|
model_provider: selectedModel?.provider || '',
|
||||||
|
model_name: selectedModel?.name || ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// 更新本地数据
|
||||||
|
const agent = agents.value.find(a => a.id === editingAgent.value.id)
|
||||||
|
if (agent) {
|
||||||
|
agent.name = editingAgent.value.name
|
||||||
|
agent.description = editingAgent.value.description
|
||||||
|
// 更新 skills 显示
|
||||||
|
if (editingAgent.value.skillsMode === 'all') {
|
||||||
|
agent.skills = '*'
|
||||||
|
} else {
|
||||||
|
agent.skills = editingAgent.value.selectedSkills.join(', ')
|
||||||
|
}
|
||||||
|
// 更新 model 显示
|
||||||
|
agent.model = selectedModel?.name || ''
|
||||||
|
}
|
||||||
|
showEditModal.value = false
|
||||||
|
ElMessage.success('Agent updated successfully')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('Failed to update agent')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update agent:', error)
|
||||||
|
ElMessage.error('Failed to update agent')
|
||||||
|
} finally {
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -385,14 +568,14 @@ const deleteAgent = (id: number) => {
|
|||||||
<input
|
<input
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search agents by name or framework..."
|
placeholder="Search agents by name or skills..."
|
||||||
class="search-input w-full"
|
class="search-input w-full"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<el-select v-model="filterStatus" placeholder="Select" class="w-40" size="large">
|
<el-select v-model="filterStatus" placeholder="Select" class="w-40" size="large">
|
||||||
<el-option label="All Status" value="all" />
|
<el-option label="All Status" value="all" />
|
||||||
<el-option label="Running" value="running" />
|
<el-option label="Active" value="active" />
|
||||||
<el-option label="Stopped" value="stopped" />
|
<el-option label="Inactive" value="inactive" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -401,18 +584,17 @@ const deleteAgent = (id: number) => {
|
|||||||
<table v-if="filteredAgents.length > 0" class="w-full">
|
<table v-if="filteredAgents.length > 0" class="w-full">
|
||||||
<thead class="bg-dark-600">
|
<thead class="bg-dark-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Agent Name</th>
|
<th class="text-left px-4 py-3 text-sm font-medium text-gray-400">Agent Name</th>
|
||||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Framework</th>
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Skills</th>
|
||||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Model</th>
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Model</th>
|
||||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">MCP</th>
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Status</th>
|
||||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Created</th>
|
||||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||||
<th class="text-right px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="agent in filteredAgents" :key="agent.id" class="table-row">
|
<tr v-for="agent in filteredAgents" :key="agent.id" class="table-row">
|
||||||
<td class="px-5 py-4">
|
<td class="px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg"
|
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg"
|
||||||
@@ -426,41 +608,38 @@ const deleteAgent = (id: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-4 py-3 text-center">
|
||||||
<span class="bg-dark-500 px-2 py-1 rounded text-sm text-gray-300">{{ agent.framework }}</span>
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm text-gray-300">{{ agent.skills }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-gray-300">{{ agent.model }}</td>
|
<td class="px-4 py-3 text-center">
|
||||||
<td class="px-5 py-4 text-center">
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm text-gray-300">{{ agent.model }}</span>
|
||||||
<span class="text-primary-cyan">{{ agent.mcpServers }}</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-4 py-3 text-center">
|
||||||
<div class="flex items-center gap-2">
|
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" :class="agent.status === 'active' ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'">
|
||||||
<span class="w-2 h-2 rounded-full" :class="statusClass(agent.status)"></span>
|
<span class="w-1.5 h-1.5 rounded-full" :class="agent.status === 'active' ? 'bg-green-500' : 'bg-gray-400'"></span>
|
||||||
<span class="capitalize text-sm text-gray-300">{{ agent.status }}</span>
|
<span class="capitalize">{{ agent.status }}</span>
|
||||||
</div>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ agent.createdAt }}</td>
|
<td class="px-4 py-3 text-center text-gray-400 text-sm">{{ agent.createdAt }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-4 py-3">
|
||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<button
|
<button
|
||||||
@click="toggleStatus(agent)"
|
@click="toggleStatus(agent)"
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
:title="agent.status === 'running' ? 'Stop' : 'Start'"
|
:title="agent.status === 'active' ? 'Deactivate' : 'Activate'"
|
||||||
>
|
>
|
||||||
<i :class="['fa-solid', agent.status === 'running' ? 'fa-stop' : 'fa-play', 'text-gray-400']"></i>
|
<Pause v-if="agent.status === 'active'" class="w-4 h-4 text-gray-400 hover:text-yellow-400" />
|
||||||
|
<Play v-else class="w-4 h-4 text-gray-400 hover:text-green-400" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" title="Edit">
|
<button @click="openEdit(agent)" class="btn-icon" title="Edit">
|
||||||
<i class="fa-solid fa-pen text-gray-400"></i>
|
<Edit class="w-4 h-4 text-gray-400 hover:text-white" />
|
||||||
</button>
|
|
||||||
<button class="btn-icon" title="Settings">
|
|
||||||
<i class="fa-solid fa-gear text-gray-400"></i>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="deleteAgent(agent.id)"
|
@click="deleteAgent(agent.id)"
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-trash text-gray-400 hover:text-primary-danger"></i>
|
<Trash2 class="w-4 h-4 text-gray-400 hover:text-red-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -527,6 +706,29 @@ const deleteAgent = (id: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Preferred Model</label>
|
||||||
|
<el-select
|
||||||
|
v-model="newAgent.modelId"
|
||||||
|
placeholder="Select a model..."
|
||||||
|
class="w-full"
|
||||||
|
size="large"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="model in modelsList"
|
||||||
|
:key="model.id"
|
||||||
|
:label="model.name"
|
||||||
|
:value="model.id"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>{{ model.name }}</span>
|
||||||
|
<span class="text-xs text-gray-500">{{ model.provider }} - {{ model.model }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-300 mb-2">Skills *</label>
|
<label class="block text-sm font-medium text-gray-300 mb-2">Skills *</label>
|
||||||
<!-- 技能模式选择 - 两级下拉框 -->
|
<!-- 技能模式选择 - 两级下拉框 -->
|
||||||
@@ -663,13 +865,6 @@ const deleteAgent = (id: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-300 mb-2">Knowledge *</label>
|
|
||||||
<el-select v-model="newAgent.knowledge" placeholder="Select knowledge" class="w-full" size="large" popper-class="dark-select-dropdown">
|
|
||||||
<el-option v-for="k in knowledgeOptions" :key="k.value" :label="k.label" :value="k.value" />
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-300 mb-2">Custom Prompt</label>
|
<label class="block text-sm font-medium text-gray-300 mb-2">Custom Prompt</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -690,7 +885,7 @@ const deleteAgent = (id: number) => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="createAgent"
|
@click="createAgent"
|
||||||
:disabled="isCreating || !newAgent.name || (newAgent.skillsMode !== 'all' && newAgent.selectedSkills.length === 0) || !newAgent.knowledge"
|
:disabled="isCreating || !newAgent.name || (newAgent.skillsMode !== 'all' && newAgent.selectedSkills.length === 0)"
|
||||||
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<i v-if="isCreating" class="fa-solid fa-circle-notch fa-spin"></i>
|
<i v-if="isCreating" class="fa-solid fa-circle-notch fa-spin"></i>
|
||||||
@@ -700,6 +895,206 @@ const deleteAgent = (id: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
|
||||||
|
<!-- 编辑智能体弹窗 -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="showEditModal" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||||||
|
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||||
|
<h3 class="text-lg font-semibold">Edit Agent</h3>
|
||||||
|
<button @click="showEditModal = false" class="text-gray-400 hover:text-white transition-colors">
|
||||||
|
<i class="fa-solid fa-xmark text-xl"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-5 space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Agent Name *</label>
|
||||||
|
<input
|
||||||
|
v-model="editingAgent.name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter agent name..."
|
||||||
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||||
|
<textarea
|
||||||
|
v-model="editingAgent.description"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Describe what this agent does..."
|
||||||
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Avatar</label>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
v-for="avatar in avatarOptions"
|
||||||
|
:key="avatar"
|
||||||
|
type="button"
|
||||||
|
@click="editingAgent.avatar = avatar"
|
||||||
|
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg transition-all"
|
||||||
|
:class="editingAgent.avatar === avatar ? 'bg-primary-orange text-white ring-2 ring-orange-400' : 'bg-dark-600 text-gray-300 hover:bg-dark-500'"
|
||||||
|
>
|
||||||
|
{{ avatar }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Preferred Model</label>
|
||||||
|
<el-select
|
||||||
|
v-model="editingAgent.modelId"
|
||||||
|
placeholder="Select a model..."
|
||||||
|
class="w-full"
|
||||||
|
size="large"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="model in modelsList"
|
||||||
|
:key="model.id"
|
||||||
|
:label="model.name"
|
||||||
|
:value="model.id"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>{{ model.name }}</span>
|
||||||
|
<span class="text-xs text-gray-500">{{ model.provider }} - {{ model.model }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Skills *</label>
|
||||||
|
<!-- 技能模式选择 -->
|
||||||
|
<div class="skills-selector">
|
||||||
|
<div class="selected-tags" @click="toggleSkillsDropdown">
|
||||||
|
<div v-if="editingAgent.skillsMode === 'all'" class="placeholder">
|
||||||
|
All Skills
|
||||||
|
</div>
|
||||||
|
<div v-else class="tags-container">
|
||||||
|
<span
|
||||||
|
v-for="skillId in editingAgent.selectedSkills.slice(0, 3)"
|
||||||
|
:key="skillId"
|
||||||
|
class="selected-tag"
|
||||||
|
>
|
||||||
|
{{ getSkillLabel(skillId) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="editingAgent.selectedSkills.length > 3" class="more-tag">
|
||||||
|
+{{ editingAgent.selectedSkills.length - 3 }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<svg class="dropdown-icon" :class="{ 'rotate-180': showSkillsDropdown }" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m6 9 6 6 6-6"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition name="dropdown">
|
||||||
|
<div v-if="showSkillsDropdown" class="dropdown-panel">
|
||||||
|
<div class="skills-mode-options">
|
||||||
|
<div
|
||||||
|
v-for="option in skillsModeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
class="skills-mode-item"
|
||||||
|
:class="{ 'active': editingAgent.skillsMode === option.value }"
|
||||||
|
@click="handleSkillsModeClickEdit(option.value)"
|
||||||
|
>
|
||||||
|
<div class="mode-radio">
|
||||||
|
<div v-if="editingAgent.skillsMode === option.value" class="radio-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mode-content">
|
||||||
|
<span class="mode-label">{{ option.label }}</span>
|
||||||
|
<span class="mode-desc">{{ option.desc }}</span>
|
||||||
|
</div>
|
||||||
|
<svg v-if="option.value !== 'all'" class="sub-arrow" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m9 18 6-6-6-6"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<Transition name="dropdown">
|
||||||
|
<div v-if="showSubSkillsDropdown" class="sub-dropdown-panel" @click.stop>
|
||||||
|
<div class="sub-dropdown-header">
|
||||||
|
<span class="sub-dropdown-title">
|
||||||
|
{{ editingAgent.skillsMode === 'include' ? 'Select skills to include' : 'Select skills to exclude' }}
|
||||||
|
</span>
|
||||||
|
<button @click="showSubSkillsDropdown = false" class="sub-dropdown-close">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-box">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="search-icon">
|
||||||
|
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
||||||
|
</svg>
|
||||||
|
<input v-model="skillsSearch" type="text" placeholder="Search skills..." class="search-input">
|
||||||
|
</div>
|
||||||
|
<div class="action-bar">
|
||||||
|
<label class="checkbox-label cursor-pointer">
|
||||||
|
<input type="checkbox" :checked="isAllSelected" :indeterminate="isIndeterminate" @change="toggleSelectAll" class="checkbox">
|
||||||
|
<span class="checkbox-text">Select All</span>
|
||||||
|
</label>
|
||||||
|
<button v-if="editingAgent.selectedSkills.length > 0" type="button" @click="clearSkills" class="clear-btn">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="options-list">
|
||||||
|
<template v-if="filteredSkills.length > 0">
|
||||||
|
<label v-for="skill in filteredSkills" :key="skill.value" class="option-item">
|
||||||
|
<input type="checkbox" :value="skill.value" v-model="editingAgent.selectedSkills" class="checkbox">
|
||||||
|
<div class="option-content">
|
||||||
|
<span class="option-label">{{ skill.label }}</span>
|
||||||
|
<span v-if="skill.desc" class="option-desc">{{ skill.desc }}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
<div v-else class="no-results">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="no-results-icon">
|
||||||
|
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
|
||||||
|
<path d="M21 3v5h-5"/>
|
||||||
|
</svg>
|
||||||
|
<span>No skills available</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-2">Prompt</label>
|
||||||
|
<textarea
|
||||||
|
v-model="editingAgent.prompt"
|
||||||
|
rows="4"
|
||||||
|
placeholder="Define the agent's behavior and instructions..."
|
||||||
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||||
|
<button
|
||||||
|
@click="showEditModal = false"
|
||||||
|
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="saveEdit"
|
||||||
|
:disabled="isEditing || !editingAgent.name"
|
||||||
|
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<i v-if="isEditing" class="fa-solid fa-circle-notch fa-spin"></i>
|
||||||
|
{{ isEditing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDatabase } from './database/useDatabase'
|
import { useDatabase } from './database/useDatabase'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import './database/database.css'
|
import './database/database.css'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -111,7 +112,7 @@ const {
|
|||||||
<td class="px-5 py-4 text-center">
|
<td class="px-5 py-4 text-center">
|
||||||
<span class="text-primary-cyan">{{ db.table_count }}</span>
|
<span class="text-primary-cyan">{{ db.table_count }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ db.created_at?.split('T')[0] }}</td>
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ formatDate(db.created_at, 'YYYY/MM/DD HH:mm') }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { useModelSettings } from './settings/useModelSettings'
|
import { useModelSettings } from './settings/useModelSettings'
|
||||||
import { fetchKnowledgeBases, createKnowledgeBase as apiCreateKnowledgeBase, deleteKnowledgeBase as apiDeleteKnowledgeBase, fetchKnowledgeDocuments } from './knowledge/useKnowledge'
|
import { fetchKnowledgeBases, createKnowledgeBase as apiCreateKnowledgeBase, deleteKnowledgeBase as apiDeleteKnowledgeBase, fetchKnowledgeDocuments } from './knowledge/useKnowledge'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import VueOfficeDocx from '@vue-office/docx'
|
import VueOfficeDocx from '@vue-office/docx'
|
||||||
import VueOfficeExcel from '@vue-office/excel'
|
import VueOfficeExcel from '@vue-office/excel'
|
||||||
import Papa from 'papaparse'
|
import Papa from 'papaparse'
|
||||||
@@ -382,13 +383,6 @@ const formatFileSize = (bytes: number) => {
|
|||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:格式化日期
|
|
||||||
const formatDate = (dateStr: string) => {
|
|
||||||
if (!dateStr) return ''
|
|
||||||
const date = new Date(dateStr)
|
|
||||||
return date.toLocaleDateString('zh-CN')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:获取状态图标
|
// 辅助函数:获取状态图标
|
||||||
const getStatusIcon = (status: string) => {
|
const getStatusIcon = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -776,7 +770,7 @@ const deleteDocument = async (docId: string) => {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">
|
||||||
{{ kb.created_at?.split('T')[0] }}
|
{{ formatDate(kb.created_at, 'YYYY/MM/DD HH:mm') }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
@@ -1166,7 +1160,7 @@ const deleteDocument = async (docId: string) => {
|
|||||||
<div class="file-item-name">{{ doc.name }}</div>
|
<div class="file-item-name">{{ doc.name }}</div>
|
||||||
<div class="file-item-meta">
|
<div class="file-item-meta">
|
||||||
<span>{{ formatFileSize(doc.file_size) }}</span>
|
<span>{{ formatFileSize(doc.file_size) }}</span>
|
||||||
<span>{{ formatDate(doc.uploaded_at) }}</span>
|
<span>{{ formatDate(doc.uploaded_at, 'YYYY/MM/DD HH:mm') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-item-status" :class="doc.status">
|
<div class="file-item-status" :class="doc.status">
|
||||||
@@ -1192,7 +1186,7 @@ const deleteDocument = async (docId: string) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="preview-header-info">
|
<div class="preview-header-info">
|
||||||
<span class="info-tag">{{ formatFileSize(selectedDocument?.file_size) }}</span>
|
<span class="info-tag">{{ formatFileSize(selectedDocument?.file_size) }}</span>
|
||||||
<span class="info-tag">{{ formatDate(selectedDocument?.uploaded_at) }}</span>
|
<span class="info-tag">{{ formatDate(selectedDocument?.uploaded_at, 'YYYY/MM/DD HH:mm') }}</span>
|
||||||
<span class="info-tag" :class="selectedDocument?.status">{{ selectedDocument?.status === 'parsed' ? 'Parsed' : selectedDocument?.status === 'parsing' ? 'Parsing' : selectedDocument?.status === 'failed' ? 'Failed' : 'Pending' }}</span>
|
<span class="info-tag" :class="selectedDocument?.status">{{ selectedDocument?.status === 'parsed' ? 'Parsed' : selectedDocument?.status === 'parsing' ? 'Parsing' : selectedDocument?.status === 'failed' ? 'Failed' : 'Pending' }}</span>
|
||||||
<span class="info-tag">{{ selectedDocument?.chunk_count || 0 }} chunks</span>
|
<span class="info-tag">{{ selectedDocument?.chunk_count || 0 }} chunks</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import './database/database.css'
|
import './database/database.css'
|
||||||
|
|
||||||
interface MemoryItem {
|
interface MemoryItem {
|
||||||
@@ -22,7 +23,7 @@ const memories = ref<MemoryItem[]>([
|
|||||||
subject: '外部信息获取规范',
|
subject: '外部信息获取规范',
|
||||||
attribute: '工具调用原则',
|
attribute: '工具调用原则',
|
||||||
score: 0.95,
|
score: 0.95,
|
||||||
createdAt: '3/10 15:35',
|
createdAt: '2026-03-10T15:35',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,7 +33,7 @@ const memories = ref<MemoryItem[]>([
|
|||||||
subject: '工具调用',
|
subject: '工具调用',
|
||||||
attribute: '错误教训',
|
attribute: '错误教训',
|
||||||
score: 0.95,
|
score: 0.95,
|
||||||
createdAt: '3/10 12:34',
|
createdAt: '2026-03-10T12:34',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -42,7 +43,7 @@ const memories = ref<MemoryItem[]>([
|
|||||||
subject: '任务执行效率优化',
|
subject: '任务执行效率优化',
|
||||||
attribute: '迭代策略原则',
|
attribute: '迭代策略原则',
|
||||||
score: 0.92,
|
score: 0.92,
|
||||||
createdAt: '3/10 15:35',
|
createdAt: '2026-03-10T15:35',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -52,7 +53,7 @@ const memories = ref<MemoryItem[]>([
|
|||||||
subject: '执行路径监控',
|
subject: '执行路径监控',
|
||||||
attribute: '质量控制机制',
|
attribute: '质量控制机制',
|
||||||
score: 0.88,
|
score: 0.88,
|
||||||
createdAt: '3/10 15:35',
|
createdAt: '2026-03-10T15:35',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,7 +63,7 @@ const memories = ref<MemoryItem[]>([
|
|||||||
subject: '迭代效率',
|
subject: '迭代效率',
|
||||||
attribute: '错误教训',
|
attribute: '错误教训',
|
||||||
score: 0.85,
|
score: 0.85,
|
||||||
createdAt: '3/10 12:34',
|
createdAt: '2026-03-10T12:34',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -72,7 +73,7 @@ const memories = ref<MemoryItem[]>([
|
|||||||
subject: '代码生成策略',
|
subject: '代码生成策略',
|
||||||
attribute: '错误教训',
|
attribute: '错误教训',
|
||||||
score: 0.80,
|
score: 0.80,
|
||||||
createdAt: '3/10 12:34',
|
createdAt: '2026-03-10T12:34',
|
||||||
selected: false
|
selected: false
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -252,7 +253,7 @@ const getScoreColor = (score: number) => {
|
|||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<span :class="['font-medium', getScoreColor(memory.score)]">{{ memory.score }}</span>
|
<span :class="['font-medium', getScoreColor(memory.score)]">{{ memory.score }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-sm text-gray-400">{{ memory.createdAt }}</td>
|
<td class="px-5 py-4 text-sm text-gray-400">{{ formatDate(memory.createdAt, 'YYYY/MM/DD HH:mm') }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import * as monaco from 'monaco-editor'
|
import * as monaco from 'monaco-editor'
|
||||||
|
|
||||||
interface Script {
|
interface Script {
|
||||||
@@ -204,7 +205,7 @@ const saveNewScript = () => {
|
|||||||
description: newScriptForm.value.description,
|
description: newScriptForm.value.description,
|
||||||
code: newScriptForm.value.code,
|
code: newScriptForm.value.code,
|
||||||
status: 'stopped',
|
status: 'stopped',
|
||||||
createdAt: new Date().toISOString().split('T')[0],
|
createdAt: formatDate(new Date(), 'YYYY/MM/DD HH:mm'),
|
||||||
})
|
})
|
||||||
isCreatingCode.value = false
|
isCreatingCode.value = false
|
||||||
}
|
}
|
||||||
@@ -269,7 +270,7 @@ const saveNewScript = () => {
|
|||||||
<span class="capitalize text-sm">{{ script.status }}</span>
|
<span class="capitalize text-sm">{{ script.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ script.createdAt }}</td>
|
<td class="px-5 py-4 text-gray-400 text-sm">{{ formatDate(script.createdAt, 'YYYY/MM/DD HH:mm') }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useSkills } from './skill/skill'
|
import { useSkills } from './skill/skill'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import { Edit, Trash2, Wand2, Plus, Search, X, FolderInput, Play, Pause } from 'lucide-vue-next'
|
import { Edit, Trash2, Wand2, Plus, Search, X, FolderInput, Play, Pause } from 'lucide-vue-next'
|
||||||
import '@/views/database/database.css'
|
import '@/views/database/database.css'
|
||||||
|
|
||||||
@@ -146,20 +147,20 @@ onMounted(() => {
|
|||||||
<span class="text-gray-400 text-sm">{{ skill.skill_type === 'system' ? 'System' : 'User' }}</span>
|
<span class="text-gray-400 text-sm">{{ skill.skill_type === 'system' ? 'System' : 'User' }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-center">
|
<td class="px-5 py-4 text-center">
|
||||||
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" :class="skill.status === 'active' ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'">
|
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" :class="skill.status === 1 ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'">
|
||||||
<span class="w-1.5 h-1.5 rounded-full" :class="skill.status === 'active' ? 'bg-green-500' : 'bg-gray-400'"></span>
|
<span class="w-1.5 h-1.5 rounded-full" :class="skill.status === 1 ? 'bg-green-500' : 'bg-gray-400'"></span>
|
||||||
<span class="capitalize">{{ skill.status }}</span>
|
<span class="capitalize">{{ skill.status === 1 ? 'active' : 'inactive' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.created_at ? new Date(skill.created_at).toLocaleDateString() : '-' }}</td>
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.created_at ? formatDate(skill.created_at, 'YYYY/MM/DD HH:mm') : '-' }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<button
|
<button
|
||||||
@click="toggleStatus(skill)"
|
@click="toggleStatus(skill)"
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
:title="skill.status === 'active' ? 'Pause' : 'Start'"
|
:title="skill.status === 1 ? 'Pause' : 'Start'"
|
||||||
>
|
>
|
||||||
<Pause v-if="skill.status === 'active'" class="w-4 h-4 text-gray-400 hover:text-yellow-400" />
|
<Pause v-if="skill.status === 1" class="w-4 h-4 text-gray-400 hover:text-yellow-400" />
|
||||||
<Play v-else class="w-4 h-4 text-gray-400 hover:text-green-400" />
|
<Play v-else class="w-4 h-4 text-gray-400 hover:text-green-400" />
|
||||||
</button>
|
</button>
|
||||||
<button @click="openEdit(skill)" class="btn-icon" title="Edit">
|
<button @click="openEdit(skill)" class="btn-icon" title="Edit">
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
X,
|
X,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { useTools } from './tools/useTools'
|
import { useTools } from './tools/useTools'
|
||||||
|
import { formatDate } from '@/utils/format'
|
||||||
import '@/views/database/database.css'
|
import '@/views/database/database.css'
|
||||||
|
|
||||||
// 使用工具 composable
|
// 使用工具 composable
|
||||||
@@ -330,7 +331,7 @@ const submitMcp = async () => {
|
|||||||
<span class="capitalize">{{ tool.status }}</span>
|
<span class="capitalize">{{ tool.status }}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ tool.created_at || '-' }}</td>
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ tool.created_at ? formatDate(tool.created_at, 'YYYY/MM/DD HH:mm') : '-' }}</td>
|
||||||
<td class="px-5 py-4">
|
<td class="px-5 py-4">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<!-- 激活/停用按钮 - 所有工具都有 -->
|
<!-- 激活/停用按钮 - 所有工具都有 -->
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface Skill {
|
|||||||
skill_type: string
|
skill_type: string
|
||||||
skill_desc: string
|
skill_desc: string
|
||||||
path: string
|
path: string
|
||||||
status: string
|
status: number
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ Example 1:
|
|||||||
|
|
||||||
// 切换状态
|
// 切换状态
|
||||||
const toggleStatus = async (skill: Skill) => {
|
const toggleStatus = async (skill: Skill) => {
|
||||||
const newStatus = skill.status === 'active' ? 'inactive' : 'active'
|
const newStatus = skill.status === 1 ? 0 : 1
|
||||||
try {
|
try {
|
||||||
await updateSkill(skill.id, { status: newStatus })
|
await updateSkill(skill.id, { status: newStatus })
|
||||||
skill.status = newStatus
|
skill.status = newStatus
|
||||||
|
|||||||
Reference in New Issue
Block a user