package handlers import ( "fmt" "net/http" "os" "runtime" "strings" "github.com/gin-gonic/gin" "sproutclaw-web/internal/config" "sproutclaw-web/internal/db" "sproutclaw-web/internal/models" "sproutclaw-web/internal/rpc" "sproutclaw-web/internal/services" ) // RegisterSettings registers all settings-related routes. func RegisterSettings(r *gin.RouterGroup, cfg *config.Config, database *db.DB, piClient *rpc.Client) { r.GET("/settings", handleGetSettings(cfg)) r.POST("/settings/skills/toggle", handleToggleSkill(cfg, piClient)) r.POST("/settings/extensions/toggle", handleToggleExtension(cfg, piClient)) r.POST("/settings/mcp/server/toggle", handleToggleMCPServer(cfg, piClient)) r.POST("/settings/mcp/tool/toggle", handleToggleMCPTool(cfg, piClient)) r.POST("/settings/reload", handleReload(piClient)) r.POST("/settings/models-config", handleSetModelsConfig(cfg, piClient)) r.POST("/settings/system-prompt", handleSetSystemPrompt(cfg, piClient)) r.POST("/settings/avatars", handleSetAvatars(database)) r.GET("/environment", handleEnvironment(cfg)) } func handleGetSettings(cfg *config.Config) gin.HandlerFunc { return func(c *gin.Context) { skills := services.ListSkills(cfg.AgentDir) extensions := services.ListExtensions(cfg.AgentDir) mcpServers := services.ReadMCPServers(cfg.McpConfigFile, cfg.McpCacheFile) systemPrompt, _ := services.ReadSystemPrompt(cfg.SystemPromptFile) modelsConfig, _ := services.ReadModelsConfig(cfg.ModelsConfigFile) c.JSON(http.StatusOK, models.SettingsResponse{ Skills: skills, Extensions: extensions, MCPServers: mcpServers, SystemPrompt: systemPrompt, ModelsConfig: modelsConfig, }) } } func handleToggleSkill(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { var req models.ToggleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if err := services.ToggleSkill(cfg.AgentDir, req.ID, req.Enabled); err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()}) return } reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleToggleExtension(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { var req models.ToggleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if err := services.ToggleExtension(cfg.AgentDir, req.ID, req.Enabled); err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()}) return } reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleToggleMCPServer(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { var req models.MCPServerToggleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if err := services.ToggleMCPServer(cfg.McpConfigFile, req.Server, req.Enabled); err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()}) return } reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleToggleMCPTool(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { var req models.MCPToolToggleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if err := services.ToggleMCPTool(cfg.McpConfigFile, req.Server, req.Tool, req.Enabled); err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()}) return } reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleReload(piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleSetModelsConfig(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { var req models.ModelsConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if err := services.WriteModelsConfig(cfg.ModelsConfigFile, req.Content); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleSetSystemPrompt(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc { return func(c *gin.Context) { var req models.SystemPromptRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if err := services.WriteSystemPrompt(cfg.SystemPromptFile, req.Content); err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()}) return } reloadAgent(piClient) c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleSetAvatars(database *db.DB) gin.HandlerFunc { return func(c *gin.Context) { var req models.AvatarsSetRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()}) return } if req.UserAvatar != "" { _ = database.SetConfig("userAvatar", req.UserAvatar) } if req.AssistantAvatar != "" { _ = database.SetConfig("assistantAvatar", req.AssistantAvatar) } c.JSON(http.StatusOK, models.OKResponse{OK: true}) } } func handleEnvironment(cfg *config.Config) gin.HandlerFunc { return func(c *gin.Context) { hostname, _ := os.Hostname() lanAddrs := getLANAddresses() c.JSON(http.StatusOK, gin.H{ "platform": runtime.GOOS, "arch": runtime.GOARCH, "hostname": hostname, "version": runtime.Version(), "port": cfg.Port, "lan": lanAddrs, }) } } func reloadAgent(piClient *rpc.Client) { _, _ = piClient.SendCmd(models.RPCCommand{Type: "reload"}) } func getLANAddresses() []string { // simplified – returns first non-loopback IPv4 var addrs []string hostname, err := os.Hostname() if err != nil { return addrs } _ = hostname // Use a simple approach: read network interfaces via fmt // For a full implementation, net.InterfaceAddrs() would be used ifaces := getNetworkAddresses() for _, a := range ifaces { if !strings.HasPrefix(a, "127.") && !strings.HasPrefix(a, "::") { addrs = append(addrs, a) } } return addrs } func getNetworkAddresses() []string { var result []string hostname, _ := os.Hostname() if hostname != "" { result = append(result, fmt.Sprintf("%s (hostname)", hostname)) } return result }