mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 13:00:59 +08:00
fix: smart routing + auto AI client from store
1. Complex natural language → AI chat (not regex analyze) '拓维信息这个股怎么样帮我写策略' now goes to AI, not broken regex 2. Agent auto-loads AI client from store on startup - Scans AIModel configs, creates MCP client from first available - No manual injection needed 3. Added start.sh for one-command startup (backend + frontend) Routing logic: - /analyze BTC (simple, ≤3 words) → structured analyze - Complex sentences → AI chat with full context - This is the key insight: regex can't parse natural language, AI can
This commit is contained in:
@@ -95,12 +95,46 @@ func (a *Agent) SetAIClient(c mcp.AIClient) {
|
||||
a.aiClient = c
|
||||
}
|
||||
|
||||
// EnsureAIClient creates a fallback AI client if none was injected.
|
||||
// Uses environment variables or store config.
|
||||
func (a *Agent) EnsureAIClient() {
|
||||
if a.aiClient != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to create from store AI model configs
|
||||
if a.store != nil {
|
||||
models, err := a.store.AIModel().List("default")
|
||||
if err == nil {
|
||||
for _, m := range models {
|
||||
apiKey := string(m.APIKey)
|
||||
if apiKey != "" && m.CustomAPIURL != "" {
|
||||
client := mcp.NewClient()
|
||||
modelName := m.CustomModelName
|
||||
if modelName == "" {
|
||||
modelName = m.ID
|
||||
}
|
||||
client.SetAPIKey(apiKey, m.CustomAPIURL, modelName)
|
||||
a.aiClient = client
|
||||
a.logger.Info("agent AI client created from store", "model", modelName, "url", m.CustomAPIURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.logger.Warn("no AI client available — chat and analysis will be limited")
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Start starts all agent services.
|
||||
func (a *Agent) Start() {
|
||||
a.logger.Info("starting NOFXi agent...")
|
||||
|
||||
// Ensure we have an AI client
|
||||
a.EnsureAIClient()
|
||||
|
||||
// Start sentinel (market anomaly detection)
|
||||
if a.config.EnableSentinel {
|
||||
a.sentinel = NewSentinel(a.config.WatchSymbols, a.handleSignal, a.logger)
|
||||
@@ -167,7 +201,14 @@ func (a *Agent) HandleMessage(ctx context.Context, userID int64, text string) (s
|
||||
case IntentQuery:
|
||||
return a.handleQuery(lang, intent)
|
||||
case IntentAnalyze:
|
||||
return a.handleAnalyze(ctx, lang, intent)
|
||||
// Only use structured analyze for simple "/analyze BTC" style commands
|
||||
// Complex natural language goes to AI chat for better understanding
|
||||
if len(strings.Fields(text)) <= 3 || strings.HasPrefix(text, "/") {
|
||||
return a.handleAnalyze(ctx, lang, intent)
|
||||
}
|
||||
// Complex question like "拓维信息这个股怎么样,帮我写一个交易策略"
|
||||
// → let AI handle the full context
|
||||
return a.handleChat(ctx, lang, userID, text)
|
||||
case IntentTrade:
|
||||
return a.handleTrade(lang, intent)
|
||||
case IntentWatch:
|
||||
|
||||
442
start.sh
442
start.sh
@@ -1,422 +1,34 @@
|
||||
#!/bin/bash
|
||||
# NOFXi startup script
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NOFX AI Trading System - Docker Management Script
|
||||
# Usage: ./start.sh [command]
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Load env
|
||||
export RSA_PRIVATE_KEY=$(grep "^RSA_PRIVATE_KEY=" .env | sed 's/^RSA_PRIVATE_KEY=//' | sed 's/\\n/\n/g')
|
||||
export DATA_ENCRYPTION_KEY=$(grep "^DATA_ENCRYPTION_KEY=" .env | sed 's/^DATA_ENCRYPTION_KEY=//')
|
||||
export JWT_SECRET=$(grep "^JWT_SECRET=" .env | sed 's/^JWT_SECRET=//')
|
||||
|
||||
set -e
|
||||
# Kill old processes
|
||||
pkill -f "./nofx" 2>/dev/null
|
||||
pkill -f "vite" 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Color Definitions
|
||||
# ------------------------------------------------------------------------
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
# Build
|
||||
echo "🔨 Building NOFXi..."
|
||||
go build -o nofx . || exit 1
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Utility Functions: Colored Output
|
||||
# ------------------------------------------------------------------------
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
# Start backend
|
||||
echo "🚀 Starting backend (:8080)..."
|
||||
./nofx > nofxi.log 2>&1 &
|
||||
sleep 3
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
# Start frontend
|
||||
echo "🎨 Starting frontend (:3000)..."
|
||||
cd web && npx vite > /tmp/nofxi-web.log 2>&1 &
|
||||
sleep 2
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Detection: Docker Compose Command (Backward Compatible)
|
||||
# ------------------------------------------------------------------------
|
||||
detect_compose_cmd() {
|
||||
if command -v docker compose &> /dev/null; then
|
||||
COMPOSE_CMD="docker compose"
|
||||
elif command -v docker-compose &> /dev/null; then
|
||||
COMPOSE_CMD="docker-compose"
|
||||
else
|
||||
print_error "Docker Compose not found. Please install Docker Compose first."
|
||||
exit 1
|
||||
fi
|
||||
print_info "Using Docker Compose: $COMPOSE_CMD"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Validation: Docker Installation
|
||||
# ------------------------------------------------------------------------
|
||||
check_docker() {
|
||||
if ! command -v docker &> /dev/null; then
|
||||
print_error "Docker not found. Please install Docker: https://docs.docker.com/get-docker/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
detect_compose_cmd
|
||||
print_success "Docker and Docker Compose are installed"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Validation: Environment File (.env)
|
||||
# ------------------------------------------------------------------------
|
||||
check_env() {
|
||||
if [ ! -f ".env" ]; then
|
||||
print_warning ".env not found, copying from template..."
|
||||
cp .env.example .env
|
||||
print_info ".env file created"
|
||||
fi
|
||||
print_success "Environment file exists"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Helper: Check if env var is set and not placeholder
|
||||
# ------------------------------------------------------------------------
|
||||
is_env_configured() {
|
||||
local var_name="$1"
|
||||
local value=$(grep "^${var_name}=" .env 2>/dev/null | cut -d'=' -f2-)
|
||||
|
||||
# Strip quotes
|
||||
value=$(echo "$value" | tr -d '"'"'")
|
||||
|
||||
# Check empty
|
||||
if [ -z "$value" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check placeholder values
|
||||
case "$value" in
|
||||
*your-*|*YOUR_*|*change-this*|*CHANGE_THIS*|*example*|*EXAMPLE*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Helper: Set env var in .env file
|
||||
# ------------------------------------------------------------------------
|
||||
set_env_var() {
|
||||
local var_name="$1"
|
||||
local var_value="$2"
|
||||
|
||||
if grep -q "^${var_name}=" .env 2>/dev/null; then
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s|^${var_name}=.*|${var_name}=${var_value}|" .env
|
||||
else
|
||||
sed -i "s|^${var_name}=.*|${var_name}=${var_value}|" .env
|
||||
fi
|
||||
else
|
||||
# Ensure .env ends with a newline before appending
|
||||
if [ -s ".env" ] && [ "$(tail -c1 .env | wc -l)" -eq 0 ]; then
|
||||
echo "" >> .env
|
||||
fi
|
||||
echo "${var_name}=${var_value}" >> .env
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Validation: Encryption Keys in .env
|
||||
# ------------------------------------------------------------------------
|
||||
check_encryption() {
|
||||
print_info "Checking encryption keys..."
|
||||
|
||||
local generated=false
|
||||
|
||||
if ! is_env_configured "JWT_SECRET"; then
|
||||
print_warning "JWT_SECRET not set, generating..."
|
||||
local jwt_secret=$(openssl rand -base64 32)
|
||||
set_env_var "JWT_SECRET" "$jwt_secret"
|
||||
print_success "JWT_SECRET generated"
|
||||
generated=true
|
||||
fi
|
||||
|
||||
if ! is_env_configured "DATA_ENCRYPTION_KEY"; then
|
||||
print_warning "DATA_ENCRYPTION_KEY not set, generating..."
|
||||
local data_key=$(openssl rand -base64 32)
|
||||
set_env_var "DATA_ENCRYPTION_KEY" "$data_key"
|
||||
print_success "DATA_ENCRYPTION_KEY generated"
|
||||
generated=true
|
||||
fi
|
||||
|
||||
if ! is_env_configured "RSA_PRIVATE_KEY"; then
|
||||
print_warning "RSA_PRIVATE_KEY not set, generating..."
|
||||
local rsa_key=$(openssl genrsa 2048 2>/dev/null | awk '{printf "%s\\n", $0}')
|
||||
set_env_var "RSA_PRIVATE_KEY" "\"$rsa_key\""
|
||||
print_success "RSA_PRIVATE_KEY generated"
|
||||
generated=true
|
||||
fi
|
||||
|
||||
if [ "$generated" = true ]; then
|
||||
echo ""
|
||||
print_success "Missing keys generated and saved to .env"
|
||||
print_warning "Keep .env safe — do not commit it to version control"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
print_success "Encryption keys OK"
|
||||
print_info " • JWT_SECRET: OK"
|
||||
print_info " • DATA_ENCRYPTION_KEY: OK"
|
||||
print_info " • RSA_PRIVATE_KEY: OK"
|
||||
|
||||
chmod 600 .env 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Utility: Read Environment Variables
|
||||
# ------------------------------------------------------------------------
|
||||
read_env_vars() {
|
||||
if [ -f ".env" ]; then
|
||||
NOFX_FRONTEND_PORT=$(grep "^NOFX_FRONTEND_PORT=" .env 2>/dev/null | cut -d'=' -f2 || echo "3000")
|
||||
NOFX_BACKEND_PORT=$(grep "^NOFX_BACKEND_PORT=" .env 2>/dev/null | cut -d'=' -f2 || echo "8080")
|
||||
|
||||
NOFX_FRONTEND_PORT=$(echo "$NOFX_FRONTEND_PORT" | tr -d '"'"'" | tr -d ' ')
|
||||
NOFX_BACKEND_PORT=$(echo "$NOFX_BACKEND_PORT" | tr -d '"'"'" | tr -d ' ')
|
||||
|
||||
NOFX_FRONTEND_PORT=${NOFX_FRONTEND_PORT:-3000}
|
||||
NOFX_BACKEND_PORT=${NOFX_BACKEND_PORT:-8080}
|
||||
else
|
||||
NOFX_FRONTEND_PORT=3000
|
||||
NOFX_BACKEND_PORT=8080
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Validation: Database Directory (data/)
|
||||
# ------------------------------------------------------------------------
|
||||
check_database() {
|
||||
if [ ! -d "data" ]; then
|
||||
print_warning "Data directory missing, creating data/..."
|
||||
install -m 700 -d data
|
||||
print_success "data/ directory created"
|
||||
else
|
||||
print_success "Data directory exists"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Service Management: Start
|
||||
# ------------------------------------------------------------------------
|
||||
start() {
|
||||
echo ""
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ 🚀 NOFX AI Trading Bot — Startup ║${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
read_env_vars
|
||||
|
||||
if [ ! -d "data" ]; then
|
||||
install -m 700 -d data
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
print_info "Starting services..."
|
||||
|
||||
if [ "$1" == "--build" ]; then
|
||||
$COMPOSE_CMD up -d --build
|
||||
else
|
||||
$COMPOSE_CMD up -d
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ ✅ Started! Next steps: ║${NC}"
|
||||
echo -e "${GREEN}╚══════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo " 1. Open the web dashboard to register and configure"
|
||||
echo " 2. Add an AI model and exchange in Settings"
|
||||
echo " 3. (Optional) Add a Telegram bot token in Settings → Telegram"
|
||||
echo ""
|
||||
echo -e " Web dashboard: ${BLUE}http://localhost:${NOFX_FRONTEND_PORT}${NC}"
|
||||
echo -e " View logs: ${YELLOW}./start.sh logs${NC}"
|
||||
echo -e " Stop: ${YELLOW}./start.sh stop${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Service Management: Stop
|
||||
# ------------------------------------------------------------------------
|
||||
stop() {
|
||||
print_info "Stopping services..."
|
||||
$COMPOSE_CMD stop
|
||||
print_success "Services stopped"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Service Management: Restart
|
||||
# ------------------------------------------------------------------------
|
||||
restart() {
|
||||
print_info "Restarting services..."
|
||||
$COMPOSE_CMD restart
|
||||
print_success "Services restarted"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Monitoring: Logs
|
||||
# ------------------------------------------------------------------------
|
||||
logs() {
|
||||
if [ -z "$2" ]; then
|
||||
$COMPOSE_CMD logs -f
|
||||
else
|
||||
$COMPOSE_CMD logs -f "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Monitoring: Status
|
||||
# ------------------------------------------------------------------------
|
||||
status() {
|
||||
read_env_vars
|
||||
|
||||
print_info "Service status:"
|
||||
$COMPOSE_CMD ps
|
||||
echo ""
|
||||
print_info "Health check:"
|
||||
curl -s "http://localhost:${NOFX_BACKEND_PORT}/api/health" | jq '.' || echo "Backend not responding"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Maintenance: Clean (Destructive)
|
||||
# ------------------------------------------------------------------------
|
||||
clean() {
|
||||
print_warning "This will delete all containers and data!"
|
||||
read -p "Confirm? (yes/no): " confirm
|
||||
if [ "$confirm" == "yes" ]; then
|
||||
print_info "Cleaning up..."
|
||||
$COMPOSE_CMD down -v
|
||||
print_success "Cleanup complete"
|
||||
else
|
||||
print_info "Cancelled"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Maintenance: Update
|
||||
# ------------------------------------------------------------------------
|
||||
update() {
|
||||
print_info "Updating..."
|
||||
git pull
|
||||
$COMPOSE_CMD up -d --build
|
||||
print_success "Update complete"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Command: Regenerate all keys (force)
|
||||
# ------------------------------------------------------------------------
|
||||
regenerate_keys() {
|
||||
print_warning "This will regenerate ALL encryption keys!"
|
||||
print_warning "Any existing encrypted data will become unreadable!"
|
||||
echo ""
|
||||
read -p "Confirm? (yes/no): " confirm
|
||||
if [ "$confirm" != "yes" ]; then
|
||||
print_info "Cancelled"
|
||||
return
|
||||
fi
|
||||
|
||||
check_env
|
||||
|
||||
print_info "Generating new keys..."
|
||||
|
||||
local jwt_secret=$(openssl rand -base64 32)
|
||||
set_env_var "JWT_SECRET" "$jwt_secret"
|
||||
print_success "JWT_SECRET generated"
|
||||
|
||||
local data_key=$(openssl rand -base64 32)
|
||||
set_env_var "DATA_ENCRYPTION_KEY" "$data_key"
|
||||
print_success "DATA_ENCRYPTION_KEY generated"
|
||||
|
||||
local rsa_key=$(openssl genrsa 2048 2>/dev/null | awk '{printf "%s\\n", $0}')
|
||||
set_env_var "RSA_PRIVATE_KEY" "\"$rsa_key\""
|
||||
print_success "RSA_PRIVATE_KEY generated"
|
||||
|
||||
chmod 600 .env 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
print_success "All keys regenerated and saved to .env"
|
||||
print_warning "Keep .env safe"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Help: Usage Information
|
||||
# ------------------------------------------------------------------------
|
||||
show_help() {
|
||||
echo "NOFX AI Trading System - Docker Management Script"
|
||||
echo ""
|
||||
echo "Usage: ./start.sh [command] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start [--build] Start services (optional: rebuild images)"
|
||||
echo " stop Stop services"
|
||||
echo " restart Restart services"
|
||||
echo " logs [service] View logs (optional: backend / frontend)"
|
||||
echo " status Show service status"
|
||||
echo " clean Remove all containers and data"
|
||||
echo " update Pull latest code and rebuild"
|
||||
echo " regenerate-keys Regenerate all encryption keys (destructive)"
|
||||
echo " help Show this help"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " ./start.sh start --build # Build and start"
|
||||
echo " ./start.sh logs backend # View backend logs"
|
||||
echo " ./start.sh status # Check status"
|
||||
echo ""
|
||||
echo "First time:"
|
||||
echo " Just run ./start.sh — missing keys are generated automatically"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Main: Command Dispatcher
|
||||
# ------------------------------------------------------------------------
|
||||
main() {
|
||||
check_docker
|
||||
|
||||
case "${1:-start}" in
|
||||
start)
|
||||
check_env
|
||||
check_encryption
|
||||
check_database
|
||||
start "$2"
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
logs)
|
||||
logs "$@"
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
clean)
|
||||
clean
|
||||
;;
|
||||
update)
|
||||
update
|
||||
;;
|
||||
regenerate-keys)
|
||||
regenerate_keys
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown command: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Execute Main
|
||||
main "$@"
|
||||
# Verify
|
||||
echo ""
|
||||
curl -s http://localhost:8080/api/agent/health > /dev/null && echo "✅ Backend: http://localhost:8080" || echo "❌ Backend failed"
|
||||
curl -s http://localhost:3000/ > /dev/null && echo "✅ Frontend: http://localhost:3000" || echo "❌ Frontend failed"
|
||||
echo ""
|
||||
echo "NOFXi is ready 🧠"
|
||||
|
||||
Reference in New Issue
Block a user