mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-12 23:36:55 +08:00
feat(nofxi): Phase 3 complete - Exchange factory, Web UI, Strategy runner, Docker
Exchange Factory: - CreateTrader() supports Binance/OKX/Bybit/Bitget/KuCoin/Gate - Auto-registers traders from config on startup - Direct import of nofx/trader packages (merged into main module) Web UI: - Dark theme chat interface at :8900 - Quick action sidebar (analyze, watch, positions, balance) - Real-time health check indicator - Mobile responsive Strategy Runner: - /strategy start BTC 1h - AI auto-analyzes on interval - /strategy stop <id> - stop strategy - /strategy list - view active strategies - Notifications pushed to Telegram on signals - Configurable intervals: 15m/30m/1h/4h Docker: - Multi-stage Dockerfile (alpine, ~20MB) - docker-compose.yml with volume persistence Bug fixes: - Fixed panic on empty exchanges config - Fixed thinking mode response parsing (qwen3 content:null) - Added request timeout (55s) in Telegram handler - Better error logging
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
@@ -65,7 +66,7 @@ func (t *TelegramBot) Start(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramBot) handleUpdate(ctx context.Context, update tgbotapi.Update) {
|
||||
func (t *TelegramBot) handleUpdate(parentCtx context.Context, update tgbotapi.Update) {
|
||||
msg := update.Message
|
||||
userID := msg.From.ID
|
||||
|
||||
@@ -91,13 +92,21 @@ func (t *TelegramBot) handleUpdate(ctx context.Context, update tgbotapi.Update)
|
||||
typing := tgbotapi.NewChatAction(msg.Chat.ID, tgbotapi.ChatTyping)
|
||||
t.bot.Send(typing)
|
||||
|
||||
// Process message
|
||||
// Process message with timeout
|
||||
ctx, cancel := context.WithTimeout(parentCtx, 55*time.Second)
|
||||
defer cancel()
|
||||
|
||||
response, err := t.handler(ctx, userID, text)
|
||||
if err != nil {
|
||||
t.logger.Error("handle message", "error", err)
|
||||
response = fmt.Sprintf("⚠️ Error: %v", err)
|
||||
if ctx.Err() != nil {
|
||||
response = "⏱️ AI 响应超时,请稍后再试。"
|
||||
} else {
|
||||
response = fmt.Sprintf("⚠️ Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
t.logger.Info("sending response", "user_id", userID, "len", len(response))
|
||||
t.sendMarkdown(msg.Chat.ID, response)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,23 +6,28 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WebServer provides a REST API for NOFXi.
|
||||
// WebServer provides a REST API and Web UI for NOFXi.
|
||||
type WebServer struct {
|
||||
handler MessageHandler
|
||||
port int
|
||||
webDir string // Path to web/ directory for static files
|
||||
logger *slog.Logger
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// NewWebServer creates a new web API server.
|
||||
func NewWebServer(port int, handler MessageHandler, logger *slog.Logger) *WebServer {
|
||||
// webDir is the path to the web/ directory containing index.html.
|
||||
func NewWebServer(port int, handler MessageHandler, webDir string, logger *slog.Logger) *WebServer {
|
||||
return &WebServer{
|
||||
handler: handler,
|
||||
port: port,
|
||||
webDir: webDir,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -135,6 +140,14 @@ func (w *WebServer) Start(ctx context.Context) error {
|
||||
})
|
||||
})
|
||||
|
||||
// Serve web UI static files
|
||||
if w.webDir != "" {
|
||||
if _, err := os.Stat(filepath.Join(w.webDir, "index.html")); err == nil {
|
||||
mux.Handle("/", http.FileServer(http.Dir(w.webDir)))
|
||||
w.logger.Info("serving web UI", "dir", w.webDir)
|
||||
}
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", w.port)
|
||||
w.server = &http.Server{
|
||||
Addr: addr,
|
||||
|
||||
Reference in New Issue
Block a user