216 lines
6.7 KiB
Go
216 lines
6.7 KiB
Go
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
|
||
}
|