package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/explorer/virtual-banker/backend/api" "github.com/explorer/virtual-banker/backend/asr" "github.com/explorer/virtual-banker/backend/llm" "github.com/explorer/virtual-banker/backend/orchestrator" "github.com/explorer/virtual-banker/backend/rag" "github.com/explorer/virtual-banker/backend/realtime" "github.com/explorer/virtual-banker/backend/session" "github.com/explorer/virtual-banker/backend/tools" "github.com/explorer/virtual-banker/backend/tools/banking" "github.com/explorer/virtual-banker/backend/tts" "github.com/jackc/pgx/v5/pgxpool" "github.com/redis/go-redis/v9" ) func main() { // Load configuration from environment dbURL := getEnv("DATABASE_URL", "postgres://explorer:changeme@localhost:5432/explorer?sslmode=disable") redisURL := getEnv("REDIS_URL", "redis://localhost:6379") port := getEnv("PORT", "8081") // Initialize database connection db, err := pgxpool.New(context.Background(), dbURL) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer db.Close() // Initialize Redis connection opt, err := redis.ParseURL(redisURL) if err != nil { log.Fatalf("Failed to parse Redis URL: %v", err) } redisClient := redis.NewClient(opt) defer redisClient.Close() // Test connections if err := db.Ping(context.Background()); err != nil { log.Fatalf("Database ping failed: %v", err) } if err := redisClient.Ping(context.Background()).Err(); err != nil { log.Fatalf("Redis ping failed: %v", err) } // Initialize services sessionManager := session.NewManager(db, redisClient) // Initialize ASR/TTS asrService := asr.NewMockASRService() ttsService := newTTSService() // Initialize LLM (using mock for now) llmGateway := llm.NewMockLLMGateway() // Initialize RAG ragService := rag.NewRAGService(db) // Initialize tools toolRegistry := tools.NewRegistry() toolRegistry.Register(banking.NewAccountStatusTool()) toolRegistry.Register(banking.NewCreateTicketTool()) toolRegistry.Register(banking.NewScheduleAppointmentTool()) toolRegistry.Register(banking.NewSubmitPaymentTool()) auditLogger := &tools.MockAuditLogger{} toolExecutor := tools.NewExecutor(toolRegistry, auditLogger) // Initialize orchestrator convOrchestrator := orchestrator.NewOrchestrator( asrService, ttsService, llmGateway, ragService, toolExecutor, ) // Initialize realtime gateway realtimeGateway := realtime.NewGateway() // Initialize API server apiServer := api.NewServer(sessionManager, realtimeGateway) // Store orchestrator reference (would be used by handlers) _ = convOrchestrator // Create HTTP server srv := &http.Server{ Addr: ":" + port, Handler: apiServer, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // Start server in goroutine go func() { log.Printf("Virtual Banker API server starting on port %s", port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed to start: %v", err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") // Graceful shutdown ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } log.Println("Server exited") } // newTTSService returns a TTS service from env: use real API when TTS_API_KEY (or // ELEVENLABS_API_KEY) and TTS_VOICE_ID are set. Optional: TTS_BASE_URL (Phoenix), // TTS_AUTH_HEADER_NAME / TTS_AUTH_HEADER_VALUE (e.g. Authorization: Bearer), // USE_PHOENIX_TTS=true to require TTS_BASE_URL. func newTTSService() tts.Service { apiKey := getEnv("TTS_API_KEY", os.Getenv("ELEVENLABS_API_KEY")) voiceID := getEnv("TTS_VOICE_ID", os.Getenv("ELEVENLABS_VOICE_ID")) baseURL := getEnv("TTS_BASE_URL", "") authName := getEnv("TTS_AUTH_HEADER_NAME", "") authValue := getEnv("TTS_AUTH_HEADER_VALUE", "") usePhoenix := getEnv("USE_PHOENIX_TTS", "") == "true" || getEnv("USE_PHOENIX_TTS", "") == "1" if usePhoenix && baseURL == "" { baseURL = getEnv("PHOENIX_TTS_BASE_URL", "https://phoenix.example.com/tts/v1") } hasAuth := apiKey != "" || authValue != "" if hasAuth && voiceID != "" { opts := tts.TTSOptions{BaseURL: baseURL, AuthHeaderName: authName, AuthHeaderValue: authValue} return tts.NewElevenLabsTTSServiceWithOptionsFull(apiKey, voiceID, opts) } return tts.NewMockTTSService() } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue }