app->singleton(ThemeManager::class); $this->app->singleton(PluginManager::class); $this->app->singleton(AttachmentStorageService::class); $this->app->singleton(SeoPresenter::class); $this->app->bind(LlmProvider::class, function (): LlmProvider { $provider = env('AI_PROVIDER', 'stub'); try { $provider = app(\App\Settings\AiSettings::class)->provider ?: $provider; } catch (\Throwable) { // settings table may be unavailable during early boot } return match ($provider) { 'openai_compatible', 'openai' => $this->app->make(OpenAiCompatibleLlmProvider::class), default => $this->app->make(StubLlmProvider::class), }; }); } public function boot(): void { Auth::provider('larablog', function ($app, array $config) { return new LaraBlogUserProvider($app['hash'], $config['model'] ?? User::class); }); // Always register default theme views; active theme may resolve later. $this->app->make(ThemeManager::class)->registerViewNamespaces(); Blade::directive('themeslot', function (string $expression): string { return ""; }); // Always load discovered plugin migrations so `artisan migrate` works // before a plugin is enabled (enable → migrate chicken-and-egg). foreach ($this->app->make(PluginManager::class)->discover() as $manifest) { $migrations = rtrim((string) ($manifest['path'] ?? ''), '/').'/database/migrations'; if (is_dir($migrations)) { $this->loadMigrationsFrom($migrations); } } // Filament AdminPanelProvider also registers plugins before panel id(); // keep this for HTTP routes / hooks when the admin panel is unused. if (Schema::hasTable('plugins')) { $this->app->make(PluginManager::class)->registerEnabledProviders(); } if (Schema::hasTable('settings')) { RegistersSnippetSlots::boot(); } } }