Files
sproutclaw-web/backend/internal/handlers/settings.go
2026-06-13 20:16:09 +08:00

216 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}