package main import ( "context" "flag" "fmt" "io" "log" "os" "path/filepath" "time" "github.com/gin-gonic/gin" "github.com/paramah/ai_devs4/s01e03/internal/api" "github.com/paramah/ai_devs4/s01e03/internal/config" "github.com/paramah/ai_devs4/s01e03/internal/domain" "github.com/paramah/ai_devs4/s01e03/internal/infrastructure/llm" "github.com/paramah/ai_devs4/s01e03/internal/infrastructure/packages" "github.com/paramah/ai_devs4/s01e03/internal/infrastructure/session" "github.com/paramah/ai_devs4/s01e03/internal/infrastructure/verify" "github.com/paramah/ai_devs4/s01e03/internal/infrastructure/weather" "github.com/paramah/ai_devs4/s01e03/internal/usecase" ) func main() { configPath := flag.String("config", "config.json", "Path to configuration file") flag.Parse() // Setup logging to file if err := setupLogging(); err != nil { log.Fatalf("Failed to setup logging: %v", err) } // Load configuration cfg, err := config.Load(*configPath) if err != nil { log.Fatalf("Failed to load configuration: %v", err) } if err := cfg.Validate(); err != nil { log.Fatalf("Invalid configuration: %v", err) } // Log configuration log.Printf("Log level: %s", cfg.LogLevel) verbose := cfg.IsVerbose() // Generate session ID sessionID, err := verify.GenerateSessionID(16) if err != nil { log.Fatalf("Failed to generate session ID: %v", err) } log.Printf("Generated session ID: %s", sessionID) // Register with the hub verifyClient := verify.NewClient(cfg.Verify.URL, cfg.Verify.APIKey, verbose) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() log.Printf("Registering with hub at %s", cfg.Verify.URL) log.Printf("Tunnel URL: %s/shadow", cfg.Verify.TunnelURL) if err := verifyClient.Register(ctx, cfg.Verify.TunnelURL+"/shadow", sessionID); err != nil { log.Fatalf("Failed to register with hub: %v", err) } log.Printf("Successfully registered with hub") // Initialize infrastructure var llmProvider domain.LLMProvider switch cfg.LLM.Provider { case "openrouter": llmProvider = llm.NewOpenRouterProvider(cfg.LLM.APIKey, cfg.LLM.Model, verbose) log.Printf("Using OpenRouter with model: %s", cfg.LLM.Model) case "lmstudio": llmProvider = llm.NewLMStudioProvider(cfg.LLM.BaseURL, cfg.LLM.Model, verbose) log.Printf("Using LM Studio at %s with model: %s", cfg.LLM.BaseURL, cfg.LLM.Model) default: log.Fatalf("Unknown LLM provider: %s", cfg.LLM.Provider) } sessionStorage := session.NewFileStorage(cfg.CacheDir) packageClient := packages.NewAPIClient(cfg.PackageAPI.BaseURL, cfg.PackageAPI.APIKey, verbose) weatherClient := weather.NewAPIClient(cfg.WeatherAPI.BaseURL, cfg.WeatherAPI.APIKey) // Initialize use case conversationUC := usecase.NewConversationUseCase(llmProvider, sessionStorage, packageClient, weatherClient) // Initialize API handler handler := api.NewHandler(conversationUC) // Setup Gin router router := gin.Default() // Add verbose logging middleware router.Use(api.VerboseLoggingMiddleware(verbose)) // Register routes router.POST("/shadow", handler.Shadow) // Start server log.Printf("Starting server on port %s", cfg.Server.Port) log.Printf("Using LLM model: %s", cfg.LLM.Model) log.Printf("Cache directory: %s", cfg.CacheDir) if err := router.Run(":" + cfg.Server.Port); err != nil { log.Fatalf("Failed to start server: %v", err) } } // setupLogging configures logging to both console and file func setupLogging() error { // Create logs directory if it doesn't exist logsDir := "logs" if err := os.MkdirAll(logsDir, 0755); err != nil { return fmt.Errorf("creating logs directory: %w", err) } // Create log file with timestamp timestamp := time.Now().Format("2006-01-02_15-04-05") logFile := filepath.Join(logsDir, fmt.Sprintf("app_%s.log", timestamp)) file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return fmt.Errorf("opening log file: %w", err) } // Setup multi-writer to log to both console and file multiWriter := io.MultiWriter(os.Stdout, file) log.SetOutput(multiWriter) log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile) log.Printf("Logging to file: %s", logFile) return nil }