diff --git a/agent/agent.go b/agent/agent.go index 3217e5ae..1e92b292 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -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: diff --git a/start.sh b/start.sh index c0c4232e..e2498099 100755 --- a/start.sh +++ b/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 🧠"