86 lines
2.4 KiB
PHP
86 lines
2.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Chatbot\Search;
|
|
|
|
use App\Repository\ContentDocumentRepository;
|
|
|
|
/**
|
|
* Full-text search over the mirrored website content.
|
|
*
|
|
* Ranked by relevance, weighted by type in the order people need them: what they book
|
|
* (products, hotels), where they go (destinations, concepts), general pages, then blog
|
|
* posts. The weights are multipliers on the relevance score, so a much better match of a
|
|
* lower type still wins.
|
|
*/
|
|
class ContentSearch
|
|
{
|
|
/**
|
|
* The tool's `type` values. `destination` covers region, country and city.
|
|
*/
|
|
public const TYPES = [
|
|
'page' => ['page'],
|
|
'product' => ['product'],
|
|
'hotel' => ['hotel'],
|
|
'destination' => ['region', 'country', 'city'],
|
|
'concept' => ['concept'],
|
|
'news' => ['news'],
|
|
];
|
|
|
|
private const TYPE_WEIGHTS = [
|
|
'product' => 1.4,
|
|
'hotel' => 1.4,
|
|
'region' => 1.2,
|
|
'country' => 1.2,
|
|
'city' => 1.2,
|
|
'concept' => 1.2,
|
|
'page' => 1.0,
|
|
'news' => 0.8,
|
|
];
|
|
|
|
public function __construct(private readonly ContentDocumentRepository $repository)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @param string|null $type a key of TYPES
|
|
*
|
|
* @return list<array{type: string, title: string, url: string, site: ?string, breadcrumb: ?string, excerpt: string}>
|
|
*/
|
|
public function search(string $query, ?string $type, ?string $site, int $limit): array
|
|
{
|
|
$booleanQuery = ContentSearchQuery::booleanQuery($query);
|
|
|
|
if (null === $booleanQuery) {
|
|
return [];
|
|
}
|
|
|
|
$rows = $this->repository->searchFulltext($booleanQuery, null === $type ? [] : self::TYPES[$type], $site, self::TYPE_WEIGHTS, $limit);
|
|
$terms = ContentSearchQuery::terms($query);
|
|
|
|
return array_map(static fn (array $row): array => [
|
|
'type' => self::publicType($row['type']),
|
|
'title' => $row['title'],
|
|
'url' => $row['url'],
|
|
'site' => $row['site'],
|
|
'breadcrumb' => $row['breadcrumb'],
|
|
'excerpt' => Excerpt::around($row['body_md'], $terms),
|
|
], $rows);
|
|
}
|
|
|
|
/**
|
|
* The tool's name for a stored type.
|
|
*/
|
|
public static function publicType(string $type): string
|
|
{
|
|
foreach (self::TYPES as $public => $types) {
|
|
if (in_array($type, $types, true)) {
|
|
return $public;
|
|
}
|
|
}
|
|
|
|
return $type;
|
|
}
|
|
}
|