diff --git a/app/app/Providers/AutopodborServiceProvider.php b/app/app/Providers/AutopodborServiceProvider.php index 6c3f89df..fa6041ab 100644 --- a/app/app/Providers/AutopodborServiceProvider.php +++ b/app/app/Providers/AutopodborServiceProvider.php @@ -2,24 +2,38 @@ namespace App\Providers; +use App\Services\Autopodbor\Agent\Aggregator\AggregatorClassifier; +use App\Services\Autopodbor\Agent\Aggregator\AggregatorFilter; use App\Services\Autopodbor\Agent\CompetitorAgent; use App\Services\Autopodbor\Agent\FakeCompetitorAgent; use App\Services\Autopodbor\Agent\Fetch\CompositeFetcher; use App\Services\Autopodbor\Agent\Fetch\CurlPlaywrightFetcher; +use App\Services\Autopodbor\Agent\Fetch\LivePageFetcher; use App\Services\Autopodbor\Agent\Fetch\XfetchClient; use App\Services\Autopodbor\Agent\Fetch\XfetchDirectoryFetcher; +use App\Services\Autopodbor\Agent\FindCompetitorsAssembler; +use App\Services\Autopodbor\Agent\LiveFindCompetitors; use App\Services\Autopodbor\Agent\RealCompetitorAgent; +use App\Services\Autopodbor\Agent\Resolve\TwoGisResolver; +use App\Services\Autopodbor\Agent\Resolve\YandexResolver; +use App\Services\Autopodbor\Agent\Search\SearchResultsParser; +use App\Services\Autopodbor\Agent\Similarity\Embedder; +use App\Services\Autopodbor\Agent\Similarity\EmbeddingRelevance; +use App\Services\Autopodbor\AutopodborDedup; +use App\Services\Autopodbor\AutopodborNormalizer; use Illuminate\Support\ServiceProvider; class AutopodborServiceProvider extends ServiceProvider { public function register(): void { - // Шаг 2 (изучение конкурента) — настоящий движок: сайт конкурента берём обычным - // curl + локальный Playwright (бесплатно), справочники 2ГИС/Яндекс — через антибот - // xfetch.ru. Поиск/резолв (шаг 1) ещё не реальны — RealCompetitorAgent делегирует - // их заглушке FakeCompetitorAgent (fallback). Когда шаг 1 будет готов — заменить - // fallback на настоящую реализацию find/resolve. + // Шаг 2 (изучение конкурента) — настоящий движок: сайт конкурента берём обычным curl + + // локальный Playwright, справочники 2ГИС/Яндекс — через антибот xfetch.ru. + // + // Шаг 1 (поиск конкурентов): при включённом флаге autopodbor.real_find подключается ЖИВОЙ + // движок (ниша → поиск 2ГИС/Яндекс → резолв → сборка). Без ИИ-ключа отсев агрегаторов и + // похожесть-% отключены (null-классификатор + нулевой эмбеддер) — выдаётся сырой список. + // Флаг ВЫКЛ → findCompetitors отдаёт демо-заглушку (как раньше). resolveByName — заглушка. $this->app->bind(CompetitorAgent::class, function ($app): CompetitorAgent { $xfetch = new XfetchClient( apiKey: config('services.xfetch.key'), @@ -31,7 +45,44 @@ class AutopodborServiceProvider extends ServiceProvider directoryFetcher: new XfetchDirectoryFetcher($xfetch), ); - return new RealCompetitorAgent($fetcher, new FakeCompetitorAgent); + $liveFind = config('autopodbor.real_find') + ? $this->buildLiveFind($xfetch) + : null; + + return new RealCompetitorAgent($fetcher, new FakeCompetitorAgent, liveFind: $liveFind); }); } + + /** Живой движок поиска шага 1 (канал А). Без ИИ-ключа — без отсева агрегаторов и без %. */ + private function buildLiveFind(XfetchClient $xfetch): LiveFindCompetitors + { + $nullClassifier = new class implements AggregatorClassifier + { + public function isAggregator(string $name, ?string $siteUrl, ?string $description): ?bool + { + return null; // нет ИИ — никого не выкидываем + } + }; + $zeroEmbedder = new class implements Embedder + { + public function embed(array $texts): array + { + return array_map(static fn (): array => [0.0], $texts); + } + }; + + $assembler = new FindCompetitorsAssembler( + new AggregatorFilter($nullClassifier), + new AutopodborDedup(new AutopodborNormalizer), + new EmbeddingRelevance($zeroEmbedder), + ); + + return new LiveFindCompetitors( + new LivePageFetcher($xfetch), + new SearchResultsParser, + new TwoGisResolver, + new YandexResolver, + $assembler, + ); + } } diff --git a/app/app/Services/Autopodbor/Agent/Fetch/LivePageFetcher.php b/app/app/Services/Autopodbor/Agent/Fetch/LivePageFetcher.php new file mode 100644 index 00000000..be508438 --- /dev/null +++ b/app/app/Services/Autopodbor/Agent/Fetch/LivePageFetcher.php @@ -0,0 +1,59 @@ +xfetch->html($url); + } + + if (str_contains($url, 'yandex.')) { + $html = $this->xfetch->html($url); + + return $html !== '' ? $html : $this->renderLocally($url); + } + + return ''; + } + + /** Локальный Playwright-рендер (бесплатный запас для Яндекса). */ + private function renderLocally(string $url): string + { + try { + $process = new Process([$this->nodeBin, base_path($this->renderScript), $url]); + $process->setTimeout($this->renderTimeoutSec); + $process->run(); + + if (! $process->isSuccessful()) { + return ''; + } + + $decoded = json_decode($process->getOutput(), true); + + return is_array($decoded) && isset($decoded['html']) ? (string) $decoded['html'] : ''; + } catch (ProcessTimedOutException|\Throwable) { + return ''; + } + } +} diff --git a/app/app/Services/Autopodbor/Agent/RealCompetitorAgent.php b/app/app/Services/Autopodbor/Agent/RealCompetitorAgent.php index fcfffd7d..9b3875c4 100644 --- a/app/app/Services/Autopodbor/Agent/RealCompetitorAgent.php +++ b/app/app/Services/Autopodbor/Agent/RealCompetitorAgent.php @@ -21,15 +21,17 @@ final class RealCompetitorAgent implements CompetitorAgent { public function __construct( private Fetcher $fetcher, - private CompetitorAgent $fallback, // для find/resolve, пока они не реальны + private CompetitorAgent $fallback, // для resolve, и для find пока не подключён живой private CandidateBuilder $builder = new CandidateBuilder, private SourceAggregator $aggregator = new SourceAggregator, private AutopodborNormalizer $norm = new AutopodborNormalizer, + private ?LiveFindCompetitors $liveFind = null, // живой поиск шага 1 (если подключён за флагом) ) {} public function findCompetitors(FindCompetitorsRequest $r): FindCompetitorsResult { - return $this->fallback->findCompetitors($r); + // Подключён живой движок поиска — используем его; иначе заглушка (демо-данные). + return $this->liveFind?->find($r) ?? $this->fallback->findCompetitors($r); } public function resolveByName(ResolveByNameRequest $r): ResolveByNameResult diff --git a/app/config/autopodbor.php b/app/config/autopodbor.php new file mode 100644 index 00000000..e5443d32 --- /dev/null +++ b/app/config/autopodbor.php @@ -0,0 +1,15 @@ + env('AUTOPODBOR_REAL_FIND', false), +];