diff --git a/server/cmd/api/main.go b/server/cmd/api/main.go index 051b80a..cd06f6e 100644 --- a/server/cmd/api/main.go +++ b/server/cmd/api/main.go @@ -147,7 +147,28 @@ func main() { } // 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 强制创建) db.Exec(` @@ -297,7 +318,7 @@ func main() { toolService := service.NewToolService(toolRepo) mcpService := service.NewMCPService(mcpRepo) skillService := service.NewSkillService(skillRepo) - agentService := service.NewAgentService(cfg.PythonServiceURL, modelRepo) + agentService := service.NewAgentService(cfg.PythonServiceURL, modelRepo, agentRepo) memoryService := service.NewMemoryService(agentRepo) // 4.2 初始化默认工具 @@ -502,6 +523,9 @@ func main() { { agentGroup.GET("/list", agentHandler.ListAgents) 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/stream", agentHandler.ChatStream) agentGroup.POST("/team/chat", agentHandler.TeamChat) diff --git a/server/internal/handler/agent_handler.go b/server/internal/handler/agent_handler.go index 6133bbb..7ed9003 100644 --- a/server/internal/handler/agent_handler.go +++ b/server/internal/handler/agent_handler.go @@ -243,3 +243,103 @@ func (h *AgentHandler) ListAgents(c *gin.Context) { 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"}) +} diff --git a/server/internal/handler/skill_handler.go b/server/internal/handler/skill_handler.go index b98810b..62d54f9 100644 --- a/server/internal/handler/skill_handler.go +++ b/server/internal/handler/skill_handler.go @@ -245,7 +245,7 @@ func (h *SkillHandler) Create(c *gin.Context) { SkillType: skillType, SkillDesc: skillDesc, Path: filepath.Join(skillPath, "SKILL.md"), - Status: "active", + Status: 1, } // 记录创建者 @@ -386,7 +386,7 @@ func (h *SkillHandler) Update(c *gin.Context) { skillName := req.SkillName skillDesc := req.SkillDesc skillType := req.SkillType - status := req.Status + var status int if skillName == "" { skillName = existingSkill.SkillName @@ -397,8 +397,18 @@ func (h *SkillHandler) Update(c *gin.Context) { if skillType == "" { skillType = existingSkill.SkillType } - if status == "" { + // 处理 status:如果是空字符串则使用现有值,否则转换为整数 + if req.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 + } } // 获取项目根目录 diff --git a/server/internal/model/agent.go b/server/internal/model/agent.go index 389b473..0e585e3 100644 --- a/server/internal/model/agent.go +++ b/server/internal/model/agent.go @@ -15,24 +15,18 @@ const ( // Agent 智能体 type Agent struct { - ID string `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"size:100;not null"` - Description string `json:"description" gorm:"type:text"` - OwnerID string `json:"owner_id" gorm:"size:50;not null;index"` + ID string `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"size:100;not null"` + Description string `json:"description" gorm:"type:text"` + OwnerID string `json:"owner_id" gorm:"size:50;not null;index"` - // Agent能力配置 - Capabilities []string `json:"capabilities" gorm:"type:text"` // JSON数组,可用工具列表 - MemoryLimit int64 `json:"memory_limit" gorm:"default:134217728"` // 128MB - Timeout int `json:"timeout" gorm:"default:60"` // 60秒 + // 技能列表(JSON数组) + Skills []string `json:"skills" gorm:"type:text;serializer:json"` - // 安全配置 - 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) - ModelProvider string `json:"model_provider" gorm:"size:50"` // 模型提供商: openai/anthropic - ModelName string `json:"model_name" gorm:"size:100"` // 模型名称 + ModelProvider string `json:"model_provider" gorm:"size:50"` // 模型提供商: openai/anthropic + ModelName string `json:"model_name" gorm:"size:100"` // 模型名称 // 协作模式 IsSupervisor bool `json:"is_supervisor" gorm:"default:false"` // 是否为主智能体 diff --git a/server/internal/model/skill.go b/server/internal/model/skill.go index 5b58a96..4abaace 100644 --- a/server/internal/model/skill.go +++ b/server/internal/model/skill.go @@ -14,8 +14,8 @@ type Skill struct { SkillType string `json:"skill_type" gorm:"size:20;not null"` // system / user SkillDesc string `json:"skill_desc" gorm:"type:text"` Path string `json:"path" gorm:"size:500"` // skill 文件路径 - Status string `json:"status" gorm:"size:20;default:'active'"` - CreatedBy string `json:"created_by" gorm:"size:100"` // 创建者用户名 + Status int `json:"status" gorm:"default:1"` // 1: active, 0: inactive + CreatedBy string `json:"created_by" gorm:"size:100"` // 创建者用户名 CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } diff --git a/server/internal/repository/model_repo.go b/server/internal/repository/model_repo.go index 0840bc6..eddf44f 100644 --- a/server/internal/repository/model_repo.go +++ b/server/internal/repository/model_repo.go @@ -32,6 +32,16 @@ func (r *ModelRepository) FindByID(id string) (*model.ModelInfo, error) { 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 创建模型 func (r *ModelRepository) Create(info *model.ModelInfo) error { return r.db.Create(info).Error diff --git a/server/internal/repository/skill_repo.go b/server/internal/repository/skill_repo.go index fafb6c9..cb0586f 100644 --- a/server/internal/repository/skill_repo.go +++ b/server/internal/repository/skill_repo.go @@ -65,7 +65,7 @@ func (r *SkillRepository) Update(skill *model.Skill) error { if skill.SkillType != "" { updates["skill_type"] = skill.SkillType } - if skill.Status != "" { + if skill.Status != 0 { updates["status"] = skill.Status } if skill.Path != "" { diff --git a/server/internal/service/agent_service.go b/server/internal/service/agent_service.go index 45100ef..068298c 100644 --- a/server/internal/service/agent_service.go +++ b/server/internal/service/agent_service.go @@ -9,9 +9,11 @@ import ( "net/http" "time" + "x-agents/server/internal/model" "x-agents/server/internal/repository" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) // AgentChatRequest Python Agent 对话请求 @@ -63,16 +65,18 @@ type AgentService struct { pythonURL string client *http.Client modelRepo *repository.ModelRepository + agentRepo *repository.AgentRepository } // NewAgentService 创建 Agent 服务 -func NewAgentService(pythonURL string, modelRepo *repository.ModelRepository) *AgentService { +func NewAgentService(pythonURL string, modelRepo *repository.ModelRepository, agentRepo *repository.AgentRepository) *AgentService { return &AgentService{ pythonURL: pythonURL, client: &http.Client{ Timeout: 120 * time.Second, // Agent 可能需要较长时间 }, modelRepo: modelRepo, + agentRepo: agentRepo, } } @@ -282,16 +286,16 @@ func (s *AgentService) ChatStream(c interface{}, agentID int, message, sessionID // CreateAgentRequest 创建智能体请求 type CreateAgentRequest struct { - Name string `json:"name"` - Description string `json:"description"` - Avatar string `json:"avatar"` - SkillsMode string `json:"skills_mode"` - Skills []string `json:"skills"` - Knowledge string `json:"knowledge"` - Prompt string `json:"prompt"` - ModelProvider string `json:"model_provider"` - ModelName string `json:"model_name"` - UserID int `json:"user_id"` + Name string `json:"name"` + Description string `json:"description"` + Avatar string `json:"avatar"` + SkillsMode string `json:"skillsMode"` + Skills []string `json:"skills"` + Knowledge string `json:"knowledge"` + Prompt string `json:"prompt"` + ModelProvider string `json:"model_provider"` + ModelName string `json:"model_name"` + UserID int `json:"user_id"` } // CreateAgentResponse 创建智能体响应 @@ -303,56 +307,54 @@ type CreateAgentResponse struct { // CreateAgent 创建智能体 func (s *AgentService) CreateAgent(req CreateAgentRequest, userID int) (*CreateAgentResponse, error) { - url := fmt.Sprintf("%s/agent/create", s.pythonURL) - - // 构建请求体 - 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, + if s.agentRepo == nil { + log.Printf("[AgentService] CreateAgent: agentRepo is nil!") + return nil, fmt.Errorf("agent repository not initialized") } - jsonData, err := json.Marshal(pythonReq) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %w", err) + log.Printf("[AgentService] CreateAgent: %s (userID: %d), skillsMode: %s, skills: %v", req.Name, userID, req.SkillsMode, req.Skills) + + // 处理 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)) - if err != nil { - return nil, fmt.Errorf("failed to create request: %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) + // 创建 Agent 模型并保存到数据库 + agent := &model.Agent{ + ID: uuid.New().String(), + Name: req.Name, + Description: req.Description, + OwnerID: fmt.Sprintf("%d", userID), + Skills: skills, + RoleDescription: req.Prompt, + ModelProvider: req.ModelProvider, + ModelName: req.ModelName, + IsActive: true, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("python agent error: %s", string(body)) + log.Printf("[AgentService] CreateAgent: calling Create, agent = %+v", agent) + 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 - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("failed to unmarshal response: %w", err) - } + log.Printf("[AgentService] Agent created in database: %s (ID: %s)", agent.Name, agent.ID) - 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 获取智能体列表响应 @@ -360,37 +362,120 @@ type ListAgentsResponse struct { Agents []interface{} `json:"agents"` } -// ListAgents 获取智能体列表 +// ListAgents 获取智能体列表(从数据库获取) 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 { - return nil, fmt.Errorf("failed to create request: %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) + log.Printf("[AgentService] ListAgents: FindAll error: %v", err) + return nil, fmt.Errorf("failed to list agents: %w", err) } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("python agent error: %s", string(body)) + log.Printf("[AgentService] ListAgents: found %d agents", len(agents)) + + // 转换为 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 - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("failed to unmarshal response: %w", err) - } - - log.Printf("[AgentService] Listed agents: %d", len(result.Agents)) - - return &result, nil + return &ListAgentsResponse{ + Agents: agentsList, + }, nil +} + +// UpdateAgentStatus 更新智能体状态 +func (s *AgentService) UpdateAgentStatus(agentID string, isActive bool) error { + 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 } diff --git a/server/internal/service/chat_service.go b/server/internal/service/chat_service.go index 3cc3559..10703ce 100644 --- a/server/internal/service/chat_service.go +++ b/server/internal/service/chat_service.go @@ -166,14 +166,11 @@ func (s *ChatService) ListAgents(userID string) ([]model.Agent, error) { // CreateAgent 创建新的 Agent func (s *ChatService) CreateAgent(userID string, name, description string) (*model.Agent, error) { agent := &model.Agent{ - ID: uuid.New().String(), - Name: name, - Description: description, - OwnerID: userID, - SecurityLevel: model.SecurityLevelSafe, - IsActive: true, - Timeout: 60, - MemoryLimit: 134217728, // 128MB + ID: uuid.New().String(), + Name: name, + Description: description, + OwnerID: userID, + IsActive: true, } if err := s.agentRepo.Create(agent); err != nil { diff --git a/server/internal/service/model_service.go b/server/internal/service/model_service.go index 428b305..37a146d 100644 --- a/server/internal/service/model_service.go +++ b/server/internal/service/model_service.go @@ -35,6 +35,12 @@ func (s *ModelService) GetByID(id string) (*model.ModelInfo, error) { // Create 创建模型 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 status := req.Status if status == "" { diff --git a/server/internal/service/skill_service.go b/server/internal/service/skill_service.go index b5a2f9f..b294364 100644 --- a/server/internal/service/skill_service.go +++ b/server/internal/service/skill_service.go @@ -143,7 +143,7 @@ func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([ SkillType: skillType, SkillDesc: skillInfo.SkillDesc, Path: skillPath, - Status: "active", + Status: 1, } skills = append(skills, skill)