mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 04:50:57 +08:00
Key changes:
- Agent API routes (/api/agent/*) registered directly on NOFX's :8080 server
- No more separate :8900 port — everything on :8080
- AgentChatPage.tsx added to React frontend
- Agent tab (🤖 Agent) added to navigation bar, set as DEFAULT page
- Branding: NOFX → NOFXi everywhere (title, header, branding.ts, i18n)
- Default landing page is now Agent chat, not competition
This is THE change: there is no more 'NOFX' vs 'NOFXi'.
Users open localhost:8080, they see NOFXi.
Agent chat is the first thing they see.
All other pages (Traders, Dashboard, Strategy, Data) are still there.
One binary. One port. One product. NOFXi.
103 lines
2.8 KiB
Go
103 lines
2.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// WebHandler provides HTTP endpoints for the NOFXi agent.
|
|
type WebHandler struct {
|
|
agent *Agent
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewWebHandler(agent *Agent, logger *slog.Logger) *WebHandler {
|
|
return &WebHandler{agent: agent, logger: logger}
|
|
}
|
|
|
|
// HandleHealth handles GET /api/agent/health.
|
|
func (w *WebHandler) HandleHealth(rw http.ResponseWriter, r *http.Request) {
|
|
writeJSON(rw, 200, map[string]string{"status": "ok", "agent": "NOFXi", "time": time.Now().Format(time.RFC3339)})
|
|
}
|
|
|
|
// HandleChat handles POST /api/agent/chat.
|
|
func (w *WebHandler) HandleChat(rw http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(rw, "method not allowed", 405)
|
|
return
|
|
}
|
|
var req struct {
|
|
Message string `json:"message"`
|
|
UserID int64 `json:"user_id"`
|
|
Lang string `json:"lang"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(rw, 400, map[string]string{"error": "invalid request"})
|
|
return
|
|
}
|
|
if req.Message == "" {
|
|
writeJSON(rw, 400, map[string]string{"error": "message required"})
|
|
return
|
|
}
|
|
if req.UserID == 0 {
|
|
req.UserID = 1
|
|
}
|
|
msg := req.Message
|
|
if req.Lang != "" {
|
|
msg = "[lang:" + req.Lang + "] " + msg
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 55*time.Second)
|
|
defer cancel()
|
|
|
|
resp, err := w.agent.HandleMessage(ctx, req.UserID, msg)
|
|
if err != nil {
|
|
writeJSON(rw, 500, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(rw, 200, map[string]string{"response": resp})
|
|
}
|
|
|
|
// HandleKlines proxies kline data from Binance.
|
|
func (w *WebHandler) HandleKlines(rw http.ResponseWriter, r *http.Request) {
|
|
symbol := r.URL.Query().Get("symbol")
|
|
if symbol == "" { symbol = "BTCUSDT" }
|
|
interval := r.URL.Query().Get("interval")
|
|
if interval == "" { interval = "1h" }
|
|
|
|
proxyBinance(rw, fmt.Sprintf("https://fapi.binance.com/fapi/v1/klines?symbol=%s&interval=%s&limit=300", symbol, interval))
|
|
}
|
|
|
|
// HandleTicker proxies ticker data from Binance.
|
|
func (w *WebHandler) HandleTicker(rw http.ResponseWriter, r *http.Request) {
|
|
symbol := r.URL.Query().Get("symbol")
|
|
if symbol == "" { symbol = "BTCUSDT" }
|
|
|
|
proxyBinance(rw, fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", symbol))
|
|
}
|
|
|
|
func proxyBinance(rw http.ResponseWriter, url string) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
writeJSON(rw, 502, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
rw.Header().Set("Access-Control-Allow-Origin", "*")
|
|
io.Copy(rw, resp.Body)
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(v)
|
|
}
|