c806eaf7f9
- Updated the API to include disabled agent IDs for better management of user-specific agent visibility. - Introduced a new function to set the disabled state for shared agents, allowing users to manage their shared agent preferences. - Added a new Skills API to retrieve available skills, enhancing the overall agent capabilities. - Improved the AgentSelector and related components to filter out disabled agents, ensuring a cleaner user experience. - Updated localization strings to reflect new features and improve clarity in agent and skill management. This update enhances the user experience by providing better control over agent visibility and introducing new skill management functionalities.
379 lines
12 KiB
Go
379 lines
12 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/Tencent/WeKnora/internal/application/service"
|
|
"github.com/Tencent/WeKnora/internal/errors"
|
|
"github.com/Tencent/WeKnora/internal/logger"
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
|
secutils "github.com/Tencent/WeKnora/internal/utils"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CustomAgentHandler defines the HTTP handler for custom agent operations
|
|
type CustomAgentHandler struct {
|
|
service interfaces.CustomAgentService
|
|
disabledRepo interfaces.TenantDisabledSharedAgentRepository
|
|
}
|
|
|
|
// NewCustomAgentHandler creates a new custom agent handler instance
|
|
func NewCustomAgentHandler(service interfaces.CustomAgentService, disabledRepo interfaces.TenantDisabledSharedAgentRepository) *CustomAgentHandler {
|
|
return &CustomAgentHandler{
|
|
service: service,
|
|
disabledRepo: disabledRepo,
|
|
}
|
|
}
|
|
|
|
// CreateAgentRequest defines the request body for creating an agent
|
|
type CreateAgentRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
Avatar string `json:"avatar"`
|
|
Config types.CustomAgentConfig `json:"config"`
|
|
}
|
|
|
|
// UpdateAgentRequest defines the request body for updating an agent
|
|
type UpdateAgentRequest struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Avatar string `json:"avatar"`
|
|
Config types.CustomAgentConfig `json:"config"`
|
|
}
|
|
|
|
// CreateAgent godoc
|
|
// @Summary 创建智能体
|
|
// @Description 创建新的自定义智能体
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body CreateAgentRequest true "智能体信息"
|
|
// @Success 201 {object} map[string]interface{} "创建的智能体"
|
|
// @Failure 400 {object} errors.AppError "请求参数错误"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents [post]
|
|
func (h *CustomAgentHandler) CreateAgent(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start creating custom agent")
|
|
|
|
// Parse request body
|
|
var req CreateAgentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Error(ctx, "Failed to parse request parameters", err)
|
|
c.Error(errors.NewBadRequestError("Invalid request parameters").WithDetails(err.Error()))
|
|
return
|
|
}
|
|
|
|
// Build agent object
|
|
agent := &types.CustomAgent{
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
Avatar: req.Avatar,
|
|
Config: req.Config,
|
|
}
|
|
|
|
logger.Infof(ctx, "Creating custom agent, name: %s, agent_mode: %s",
|
|
secutils.SanitizeForLog(req.Name), req.Config.AgentMode)
|
|
|
|
// Create agent using the service
|
|
createdAgent, err := h.service.CreateAgent(ctx, agent)
|
|
if err != nil {
|
|
logger.ErrorWithFields(ctx, err, nil)
|
|
if err == service.ErrAgentNameRequired {
|
|
c.Error(errors.NewBadRequestError(err.Error()))
|
|
return
|
|
}
|
|
c.Error(errors.NewInternalServerError(err.Error()))
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Custom agent created successfully, ID: %s, name: %s",
|
|
secutils.SanitizeForLog(createdAgent.ID), secutils.SanitizeForLog(createdAgent.Name))
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"success": true,
|
|
"data": createdAgent,
|
|
})
|
|
}
|
|
|
|
// GetAgent godoc
|
|
// @Summary 获取智能体详情
|
|
// @Description 根据ID获取智能体详情
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "智能体ID"
|
|
// @Success 200 {object} map[string]interface{} "智能体详情"
|
|
// @Failure 400 {object} errors.AppError "请求参数错误"
|
|
// @Failure 404 {object} errors.AppError "智能体不存在"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents/{id} [get]
|
|
func (h *CustomAgentHandler) GetAgent(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
// Get agent ID from URL parameter
|
|
id := secutils.SanitizeForLog(c.Param("id"))
|
|
if id == "" {
|
|
logger.Error(ctx, "Agent ID is empty")
|
|
c.Error(errors.NewBadRequestError("Agent ID cannot be empty"))
|
|
return
|
|
}
|
|
|
|
agent, err := h.service.GetAgentByID(ctx, id)
|
|
if err != nil {
|
|
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
|
"agent_id": id,
|
|
})
|
|
if err == service.ErrAgentNotFound {
|
|
c.Error(errors.NewNotFoundError("Agent not found"))
|
|
return
|
|
}
|
|
c.Error(errors.NewInternalServerError(err.Error()))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"data": agent,
|
|
})
|
|
}
|
|
|
|
// ListAgents godoc
|
|
// @Summary 获取智能体列表
|
|
// @Description 获取当前租户的所有智能体(包括内置智能体)
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} map[string]interface{} "智能体列表"
|
|
// @Failure 500 {object} errors.AppError "服务器错误"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents [get]
|
|
func (h *CustomAgentHandler) ListAgents(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
// Get all agents for this tenant
|
|
agents, err := h.service.ListAgents(ctx)
|
|
if err != nil {
|
|
logger.ErrorWithFields(ctx, err, nil)
|
|
c.Error(errors.NewInternalServerError(err.Error()))
|
|
return
|
|
}
|
|
|
|
// Per-tenant "disabled by me" for own agents (only affects this tenant's conversation dropdown)
|
|
tenantID, _ := c.Get(types.TenantIDContextKey.String())
|
|
disabledOwnIDs, _ := h.disabledRepo.ListDisabledOwnAgentIDs(ctx, tenantID.(uint64))
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"data": agents,
|
|
"disabled_own_agent_ids": disabledOwnIDs,
|
|
})
|
|
}
|
|
|
|
// UpdateAgent godoc
|
|
// @Summary 更新智能体
|
|
// @Description 更新智能体的名称、描述和配置
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "智能体ID"
|
|
// @Param request body UpdateAgentRequest true "更新请求"
|
|
// @Success 200 {object} map[string]interface{} "更新后的智能体"
|
|
// @Failure 400 {object} errors.AppError "请求参数错误"
|
|
// @Failure 403 {object} errors.AppError "无法修改内置智能体"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents/{id} [put]
|
|
func (h *CustomAgentHandler) UpdateAgent(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start updating custom agent")
|
|
|
|
// Get agent ID from URL parameter
|
|
id := secutils.SanitizeForLog(c.Param("id"))
|
|
if id == "" {
|
|
logger.Error(ctx, "Agent ID is empty")
|
|
c.Error(errors.NewBadRequestError("Agent ID cannot be empty"))
|
|
return
|
|
}
|
|
|
|
// Parse request body
|
|
var req UpdateAgentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Error(ctx, "Failed to parse request parameters", err)
|
|
c.Error(errors.NewBadRequestError("Invalid request parameters").WithDetails(err.Error()))
|
|
return
|
|
}
|
|
|
|
// Build agent object
|
|
agent := &types.CustomAgent{
|
|
ID: id,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
Avatar: req.Avatar,
|
|
Config: req.Config,
|
|
}
|
|
|
|
logger.Infof(ctx, "Updating custom agent, ID: %s, name: %s",
|
|
secutils.SanitizeForLog(id), secutils.SanitizeForLog(req.Name))
|
|
|
|
// Update the agent
|
|
updatedAgent, err := h.service.UpdateAgent(ctx, agent)
|
|
if err != nil {
|
|
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
|
"agent_id": id,
|
|
})
|
|
switch err {
|
|
case service.ErrAgentNotFound:
|
|
c.Error(errors.NewNotFoundError("Agent not found"))
|
|
case service.ErrCannotModifyBuiltin:
|
|
c.Error(errors.NewForbiddenError("Cannot modify built-in agent"))
|
|
case service.ErrAgentNameRequired:
|
|
c.Error(errors.NewBadRequestError(err.Error()))
|
|
default:
|
|
c.Error(errors.NewInternalServerError(err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Custom agent updated successfully, ID: %s", secutils.SanitizeForLog(id))
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"data": updatedAgent,
|
|
})
|
|
}
|
|
|
|
// DeleteAgent godoc
|
|
// @Summary 删除智能体
|
|
// @Description 删除指定的智能体
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "智能体ID"
|
|
// @Success 200 {object} map[string]interface{} "删除成功"
|
|
// @Failure 400 {object} errors.AppError "请求参数错误"
|
|
// @Failure 403 {object} errors.AppError "无法删除内置智能体"
|
|
// @Failure 404 {object} errors.AppError "智能体不存在"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents/{id} [delete]
|
|
func (h *CustomAgentHandler) DeleteAgent(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start deleting custom agent")
|
|
|
|
// Get agent ID from URL parameter
|
|
id := secutils.SanitizeForLog(c.Param("id"))
|
|
if id == "" {
|
|
logger.Error(ctx, "Agent ID is empty")
|
|
c.Error(errors.NewBadRequestError("Agent ID cannot be empty"))
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Deleting custom agent, ID: %s", secutils.SanitizeForLog(id))
|
|
|
|
// Delete the agent
|
|
err := h.service.DeleteAgent(ctx, id)
|
|
if err != nil {
|
|
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
|
"agent_id": id,
|
|
})
|
|
switch err {
|
|
case service.ErrAgentNotFound:
|
|
c.Error(errors.NewNotFoundError("Agent not found"))
|
|
case service.ErrCannotDeleteBuiltin:
|
|
c.Error(errors.NewForbiddenError("Cannot delete built-in agent"))
|
|
default:
|
|
c.Error(errors.NewInternalServerError(err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Custom agent deleted successfully, ID: %s", secutils.SanitizeForLog(id))
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Agent deleted successfully",
|
|
})
|
|
}
|
|
|
|
// CopyAgent godoc
|
|
// @Summary 复制智能体
|
|
// @Description 复制指定的智能体
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "智能体ID"
|
|
// @Success 201 {object} map[string]interface{} "复制成功"
|
|
// @Failure 400 {object} errors.AppError "请求参数错误"
|
|
// @Failure 404 {object} errors.AppError "智能体不存在"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents/{id}/copy [post]
|
|
func (h *CustomAgentHandler) CopyAgent(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start copying custom agent")
|
|
|
|
// Get agent ID from URL parameter
|
|
id := secutils.SanitizeForLog(c.Param("id"))
|
|
if id == "" {
|
|
logger.Error(ctx, "Agent ID is empty")
|
|
c.Error(errors.NewBadRequestError("Agent ID cannot be empty"))
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Copying custom agent, ID: %s", secutils.SanitizeForLog(id))
|
|
|
|
// Copy the agent
|
|
copiedAgent, err := h.service.CopyAgent(ctx, id)
|
|
if err != nil {
|
|
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
|
"agent_id": id,
|
|
})
|
|
switch err {
|
|
case service.ErrAgentNotFound:
|
|
c.Error(errors.NewNotFoundError("Agent not found"))
|
|
default:
|
|
c.Error(errors.NewInternalServerError(err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Custom agent copied successfully, source ID: %s, new ID: %s",
|
|
secutils.SanitizeForLog(id), secutils.SanitizeForLog(copiedAgent.ID))
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"success": true,
|
|
"data": copiedAgent,
|
|
})
|
|
}
|
|
|
|
// GetPlaceholders godoc
|
|
// @Summary 获取占位符定义
|
|
// @Description 获取所有可用的提示词占位符定义,按字段类型分组
|
|
// @Tags 智能体
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} map[string]interface{} "占位符定义"
|
|
// @Security Bearer
|
|
// @Security ApiKeyAuth
|
|
// @Router /agents/placeholders [get]
|
|
func (h *CustomAgentHandler) GetPlaceholders(c *gin.Context) {
|
|
// Return all placeholder definitions grouped by field type
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"data": gin.H{
|
|
"all": types.AllPlaceholders(),
|
|
"system_prompt": types.PlaceholdersByField(types.PromptFieldSystemPrompt),
|
|
"agent_system_prompt": types.PlaceholdersByField(types.PromptFieldAgentSystemPrompt),
|
|
"context_template": types.PlaceholdersByField(types.PromptFieldContextTemplate),
|
|
"rewrite_system_prompt": types.PlaceholdersByField(types.PromptFieldRewriteSystemPrompt),
|
|
"rewrite_prompt": types.PlaceholdersByField(types.PromptFieldRewritePrompt),
|
|
"fallback_prompt": types.PlaceholdersByField(types.PromptFieldFallbackPrompt),
|
|
},
|
|
})
|
|
}
|