mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
feat: integrate NOFXi agent into dev
This commit is contained in:
106
api/agent_preferences.go
Normal file
106
api/agent_preferences.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"nofx/agent"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type agentPreferencePayload struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (s *Server) handleGetAgentPreferences(c *gin.Context) {
|
||||
uid := agent.SessionUserIDFromKey(c.GetString("user_id"))
|
||||
raw, err := s.store.GetSystemConfig(agent.PreferencesConfigKey(uid))
|
||||
if err != nil || strings.TrimSpace(raw) == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"preferences": []agent.PersistentPreference{}})
|
||||
return
|
||||
}
|
||||
|
||||
var prefs []agent.PersistentPreference
|
||||
if err := json.Unmarshal([]byte(raw), &prefs); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"preferences": []agent.PersistentPreference{}})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"preferences": prefs})
|
||||
}
|
||||
|
||||
func (s *Server) handleCreateAgentPreference(c *gin.Context) {
|
||||
uid := agent.SessionUserIDFromKey(c.GetString("user_id"))
|
||||
|
||||
var req agentPreferencePayload
|
||||
if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.Text) == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "text required"})
|
||||
return
|
||||
}
|
||||
|
||||
created, err := agent.NewPersistentPreference(req.Text)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
prefs := s.loadAgentPreferences(uid)
|
||||
prefs = append([]agent.PersistentPreference{created}, prefs...)
|
||||
if len(prefs) > 20 {
|
||||
prefs = prefs[:20]
|
||||
}
|
||||
|
||||
if err := s.saveAgentPreferences(uid, prefs); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save preference"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"preferences": prefs})
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteAgentPreference(c *gin.Context) {
|
||||
uid := agent.SessionUserIDFromKey(c.GetString("user_id"))
|
||||
id := strings.TrimSpace(c.Param("id"))
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
|
||||
return
|
||||
}
|
||||
|
||||
prefs := s.loadAgentPreferences(uid)
|
||||
filtered := prefs[:0]
|
||||
for _, pref := range prefs {
|
||||
if pref.ID != id {
|
||||
filtered = append(filtered, pref)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.saveAgentPreferences(uid, filtered); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete preference"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"preferences": filtered})
|
||||
}
|
||||
|
||||
func (s *Server) loadAgentPreferences(userID int64) []agent.PersistentPreference {
|
||||
raw, err := s.store.GetSystemConfig(agent.PreferencesConfigKey(userID))
|
||||
if err != nil || strings.TrimSpace(raw) == "" {
|
||||
return []agent.PersistentPreference{}
|
||||
}
|
||||
|
||||
var prefs []agent.PersistentPreference
|
||||
if err := json.Unmarshal([]byte(raw), &prefs); err != nil {
|
||||
return []agent.PersistentPreference{}
|
||||
}
|
||||
return prefs
|
||||
}
|
||||
|
||||
func (s *Server) saveAgentPreferences(userID int64, prefs []agent.PersistentPreference) error {
|
||||
data, err := json.Marshal(prefs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.store.SetSystemConfig(agent.PreferencesConfigKey(userID), string(data))
|
||||
}
|
||||
26
api/agent_routes.go
Normal file
26
api/agent_routes.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"nofx/agent"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterAgentHandler registers NOFXi agent API routes on the main router.
|
||||
// Chat endpoint requires authentication; market data endpoints are public.
|
||||
func (s *Server) RegisterAgentHandler(h *agent.WebHandler) {
|
||||
// Chat requires auth — can trigger trades and access account data
|
||||
s.router.POST("/api/agent/chat", s.authMiddleware(), func(c *gin.Context) {
|
||||
req := c.Request.WithContext(agent.WithStoreUserID(c.Request.Context(), c.GetString("user_id")))
|
||||
h.HandleChat(c.Writer, req)
|
||||
})
|
||||
s.router.POST("/api/agent/chat/stream", s.authMiddleware(), func(c *gin.Context) {
|
||||
req := c.Request.WithContext(agent.WithStoreUserID(c.Request.Context(), c.GetString("user_id")))
|
||||
h.HandleChatStream(c.Writer, req)
|
||||
})
|
||||
// Public endpoints — read-only market data
|
||||
s.router.GET("/api/agent/health", gin.WrapF(h.HandleHealth))
|
||||
s.router.GET("/api/agent/klines", gin.WrapF(h.HandleKlines))
|
||||
s.router.GET("/api/agent/ticker", gin.WrapF(h.HandleTicker))
|
||||
s.router.GET("/api/agent/tickers", gin.WrapF(h.HandleTickers))
|
||||
}
|
||||
@@ -30,6 +30,7 @@ type SafeModelConfig struct {
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Enabled bool `json:"enabled"`
|
||||
HasAPIKey bool `json:"has_api_key"`
|
||||
CustomAPIURL string `json:"customApiUrl"` // Custom API URL (usually not sensitive)
|
||||
CustomModelName string `json:"customModelName"` // Custom model name (not sensitive)
|
||||
WalletAddress string `json:"walletAddress,omitempty"`
|
||||
@@ -60,14 +61,14 @@ func (s *Server) handleGetModelConfigs(c *gin.Context) {
|
||||
if len(models) == 0 {
|
||||
logger.Infof("⚠️ No AI models in database, returning defaults")
|
||||
defaultModels := []SafeModelConfig{
|
||||
{ID: "deepseek", Name: "DeepSeek AI", Provider: "deepseek", Enabled: false},
|
||||
{ID: "qwen", Name: "Qwen AI", Provider: "qwen", Enabled: false},
|
||||
{ID: "openai", Name: "OpenAI", Provider: "openai", Enabled: false},
|
||||
{ID: "claude", Name: "Claude AI", Provider: "claude", Enabled: false},
|
||||
{ID: "gemini", Name: "Gemini AI", Provider: "gemini", Enabled: false},
|
||||
{ID: "grok", Name: "Grok AI", Provider: "grok", Enabled: false},
|
||||
{ID: "kimi", Name: "Kimi AI", Provider: "kimi", Enabled: false},
|
||||
{ID: "minimax", Name: "MiniMax AI", Provider: "minimax", Enabled: false},
|
||||
{ID: "deepseek", Name: "DeepSeek AI", Provider: "deepseek", Enabled: false, HasAPIKey: false},
|
||||
{ID: "qwen", Name: "Qwen AI", Provider: "qwen", Enabled: false, HasAPIKey: false},
|
||||
{ID: "openai", Name: "OpenAI", Provider: "openai", Enabled: false, HasAPIKey: false},
|
||||
{ID: "claude", Name: "Claude AI", Provider: "claude", Enabled: false, HasAPIKey: false},
|
||||
{ID: "gemini", Name: "Gemini AI", Provider: "gemini", Enabled: false, HasAPIKey: false},
|
||||
{ID: "grok", Name: "Grok AI", Provider: "grok", Enabled: false, HasAPIKey: false},
|
||||
{ID: "kimi", Name: "Kimi AI", Provider: "kimi", Enabled: false, HasAPIKey: false},
|
||||
{ID: "minimax", Name: "MiniMax AI", Provider: "minimax", Enabled: false, HasAPIKey: false},
|
||||
}
|
||||
c.JSON(http.StatusOK, defaultModels)
|
||||
return
|
||||
@@ -83,6 +84,7 @@ func (s *Server) handleGetModelConfigs(c *gin.Context) {
|
||||
Name: model.Name,
|
||||
Provider: model.Provider,
|
||||
Enabled: model.Enabled,
|
||||
HasAPIKey: model.APIKey != "",
|
||||
CustomAPIURL: model.CustomAPIURL,
|
||||
CustomModelName: model.CustomModelName,
|
||||
}
|
||||
@@ -171,7 +173,8 @@ func (s *Server) handleUpdateModelConfigs(c *gin.Context) {
|
||||
if modelData.CustomAPIURL != "" {
|
||||
cleanURL := strings.TrimSuffix(modelData.CustomAPIURL, "#")
|
||||
if err := security.ValidateURL(cleanURL); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid custom_api_url for model %s: %s", modelID, err.Error())})
|
||||
logger.Warnf("Invalid custom_api_url for model %s: %v", modelID, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid custom_api_url for model %s: URL must be a valid HTTPS endpoint", modelID)})
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -214,11 +217,13 @@ func (s *Server) handleGetSupportedModels(c *gin.Context) {
|
||||
{"id": "qwen", "name": "Qwen", "provider": "qwen", "defaultModel": "qwen3-max"},
|
||||
{"id": "openai", "name": "OpenAI", "provider": "openai", "defaultModel": "gpt-5.1"},
|
||||
{"id": "claude", "name": "Claude", "provider": "claude", "defaultModel": "claude-opus-4-6"},
|
||||
{"id": "gemini", "name": "Google Gemini", "provider": "gemini", "defaultModel": "gemini-3.1-pro"},
|
||||
{"id": "gemini", "name": "Google Gemini", "provider": "gemini", "defaultModel": "gemini-3-pro-preview"},
|
||||
{"id": "grok", "name": "Grok (xAI)", "provider": "grok", "defaultModel": "grok-3-latest"},
|
||||
{"id": "kimi", "name": "Kimi (Moonshot)", "provider": "kimi", "defaultModel": "moonshot-v1-auto"},
|
||||
{"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.7"},
|
||||
{"id": "claw402", "name": "Claw402 (Base USDC)", "provider": "claw402", "defaultModel": "glm-5"},
|
||||
{"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.5"},
|
||||
{"id": "blockrun-base", "name": "BlockRun (Base Wallet)", "provider": "blockrun-base", "defaultModel": "auto"},
|
||||
{"id": "blockrun-sol", "name": "BlockRun (Solana Wallet)", "provider": "blockrun-sol", "defaultModel": "auto"},
|
||||
{"id": "claw402", "name": "Claw402 (Base USDC)", "provider": "claw402", "defaultModel": "deepseek"},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, supportedModels)
|
||||
|
||||
@@ -127,6 +127,9 @@ func (s *Server) setupRoutes() {
|
||||
s.route(protected, "POST", "/logout", "Logout (blacklist token)", s.handleLogout)
|
||||
s.route(protected, "POST", "/onboarding/beginner", "Prepare beginner claw402 wallet and default model", s.handleBeginnerOnboarding)
|
||||
s.route(protected, "GET", "/onboarding/beginner/current", "Get current beginner claw402 wallet", s.handleCurrentBeginnerWallet)
|
||||
s.route(protected, "GET", "/agent/preferences", "Get persistent agent preferences", s.handleGetAgentPreferences)
|
||||
s.route(protected, "POST", "/agent/preferences", "Create persistent agent preference", s.handleCreateAgentPreference)
|
||||
s.route(protected, "DELETE", "/agent/preferences/:id", "Delete persistent agent preference", s.handleDeleteAgentPreference)
|
||||
|
||||
// User account management
|
||||
s.routeWithSchema(protected, "PUT", "/user/password", "Change current user password",
|
||||
|
||||
Reference in New Issue
Block a user