From 321d5707e410230a2c7982bd55401d83a5d01a89 Mon Sep 17 00:00:00 2001 From: Guillaume Mourier Date: Sun, 22 Jun 2025 13:46:58 +0200 Subject: [PATCH 1/4] add a facet search per section --- .github/workflows/scraper.yml | 13 ++ docs-scraper.config.json | 4 + meilisearch-search-bar.css | 69 ++++++++ search.js | 305 +++++++++++++++++++++------------- 4 files changed, 279 insertions(+), 112 deletions(-) diff --git a/.github/workflows/scraper.yml b/.github/workflows/scraper.yml index 094c213681..3967678b34 100644 --- a/.github/workflows/scraper.yml +++ b/.github/workflows/scraper.yml @@ -34,3 +34,16 @@ jobs: -e MEILISEARCH_API_KEY=$API_KEY \ -v $CONFIG_FILE_PATH:/docs-scraper/config.json \ getmeili/docs-scraper:v0.12.8 pipenv run ./docs_scraper config.json + + - name: Extract section from URLs using RHAI function + env: + HOST_URL: ${{ secrets.MEILISEARCH_HOST_URL }} + API_KEY: ${{ secrets.MEILISEARCH_API_KEY }} + run: | + curl -X POST "$HOST_URL/indexes/mintlify-production/documents/edit" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $API_KEY" \ + --data-binary '{ + "filter": "url IS NOT NULL", + "function": "let url_parts = doc.url.split(\"/docs/\"); if url_parts.len() > 1 { let after_docs = url_parts[1]; let section_parts = after_docs.split(\"/\"); if section_parts.len() > 0 { doc.section = section_parts[0]; } }" + }' diff --git a/docs-scraper.config.json b/docs-scraper.config.json index 4781dd9c14..e4387c0751 100644 --- a/docs-scraper.config.json +++ b/docs-scraper.config.json @@ -42,6 +42,10 @@ "content", "hierarchy_lvl0" ], + "filterableAttributes": [ + "url", + "section" + ], "synonyms": { "large language model": [ "llm" diff --git a/meilisearch-search-bar.css b/meilisearch-search-bar.css index cd7836a74f..4fbb414954 100644 --- a/meilisearch-search-bar.css +++ b/meilisearch-search-bar.css @@ -187,4 +187,73 @@ padding: 20px; text-align: center; color: #f87171; +} + +/* Filter Container Styles */ +.meilisearch-filter-container { + padding: 16px 20px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + background-color: rgba(0, 0, 0, 0.02); +} + +.dark .meilisearch-filter-container { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + background-color: rgba(255, 255, 255, 0.02); +} + +.meilisearch-filter-title { + font-size: 14px; + font-weight: 500; + color: rgba(0, 0, 0, 0.7); + margin-bottom: 12px; +} + +.dark .meilisearch-filter-title { + color: rgba(255, 255, 255, 0.7); +} + +.meilisearch-filter-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.meilisearch-filter-tag { + background-color: rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 6px; + padding: 6px 12px; + font-size: 13px; + font-weight: 500; + color: rgba(0, 0, 0, 0.7); + cursor: pointer; + transition: all 0.2s ease; + text-transform: capitalize; +} + +.dark .meilisearch-filter-tag { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.7); +} + +.meilisearch-filter-tag:hover { + background-color: rgba(0, 0, 0, 0.1); + border-color: rgba(0, 0, 0, 0.2); +} + +.dark .meilisearch-filter-tag:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); +} + +.meilisearch-filter-tag.active { + background-color: #f472b6; + border-color: #f472b6; + color: white; +} + +.meilisearch-filter-tag.active:hover { + background-color: #cb4d86; + border-color: #cb4d86; } \ No newline at end of file diff --git a/search.js b/search.js index 097c1675e6..a9ce123813 100644 --- a/search.js +++ b/search.js @@ -204,8 +204,57 @@ function initializeMeilisearchIntegration() { resultsContainer.style.cssText = ` `; + // Create the faceted filter container + const filterContainer = document.createElement('div'); + filterContainer.id = 'meilisearch-filter-container'; + filterContainer.className = 'meilisearch-filter-container'; + filterContainer.style.display = 'none'; + + // Create filter title + const filterTitle = document.createElement('div'); + filterTitle.className = 'meilisearch-filter-title'; + filterTitle.textContent = 'Narrow down by section'; + + // Create filter tags container + const filterTagsContainer = document.createElement('div'); + filterTagsContainer.className = 'meilisearch-filter-tags'; + + // Create filter tags for each section + const sections = ['learn', 'guides', 'reference']; + const filterTags = {}; + + sections.forEach(section => { + const tag = document.createElement('button'); + tag.className = 'meilisearch-filter-tag'; + tag.textContent = section; + tag.dataset.section = section; + tag.addEventListener('click', () => { + tag.classList.toggle('active'); + + // Get current active filters + const activeFilters = Array.from(filterContainer.querySelectorAll('.meilisearch-filter-tag.active')) + .map(tag => tag.dataset.section); + + // Show filter container if there are active filters + filterContainer.style.display = activeFilters.length > 0 ? 'block' : 'none'; + + // Trigger search with current filters if there's a search query + if (searchInput.value.trim().length >= 2) { + const inputEvent = new Event('input', { bubbles: true }); + searchInput.dispatchEvent(inputEvent); + } + }); + filterTagsContainer.appendChild(tag); + filterTags[section] = tag; + }); + + // Add filter elements to container + filterContainer.appendChild(filterTitle); + filterContainer.appendChild(filterTagsContainer); + // Append everything to the modal searchModal.appendChild(searchInputContainer); + searchModal.appendChild(filterContainer); searchModal.appendChild(resultsContainer); // Add the modal to the overlay @@ -333,136 +382,168 @@ function initializeMeilisearchIntegration() { const index = client.index(MEILISEARCH_INDEX); - // Add search event listener - let debounceTimer; - searchInput.addEventListener('input', (e) => { - clearTimeout(debounceTimer); + // Function to get active filters + const getActiveFilters = () => { + const filterContainer = document.getElementById('meilisearch-filter-container'); + if (!filterContainer) return []; + const activeTags = filterContainer.querySelectorAll('.meilisearch-filter-tag.active'); + return Array.from(activeTags).map(tag => tag.dataset.section); + }; + + // Function to perform search with filters + const performSearch = (query, activeFilters = []) => { + // Show loading indicator + const indicatorEl = document.createElement('div'); + indicatorEl.className = 'meilisearch-modal__indicator'; + indicatorEl.innerHTML = 'Searching…'; + resultsContainer.appendChild(indicatorEl); - const query = e.target.value.trim(); + // Build search options + const searchOptions = { + limit: 25, + attributesToHighlight: ['hierarchy_lvl1', 'hierarchy_lvl2', 'hierarchy_lvl3', 'hierarchy_lvl4', 'hierarchy_lvl5', 'content'], + attributesToCrop: ['content'], + cropLength: 100, + hybrid: { + semanticRatio: 0.5, + embedder: "default" + } + }; - if (query.length < 2) { - resultsContainer.innerHTML = ''; - return; + // Add filter if sections are selected + if (activeFilters.length > 0) { + searchOptions.filter = activeFilters.map(section => `section = "${section}"`).join(' OR '); } - debounceTimer = setTimeout(() => { - // Show loading indicator - const indicatorEl = document.createElement('div'); - indicatorEl.className = 'meilisearch-modal__indicator'; - indicatorEl.innerHTML = 'Searching…'; - resultsContainer.appendChild(indicatorEl); + // Perform search + index.search(query, searchOptions) + .then(response => { + resultsContainer.innerHTML = ''; + + const filterContainer = document.getElementById('meilisearch-filter-container'); - // Perform search - index.search(query, { - limit: 25, - attributesToHighlight: ['hierarchy_lvl1', 'hierarchy_lvl2', 'hierarchy_lvl3', 'hierarchy_lvl4', 'hierarchy_lvl5', 'content'], - attributesToCrop: ['content'], - cropLength: 100, - hybrid: { - semanticRatio: 0.5, - embedder: "default" + if (response.hits.length === 0) { + if (filterContainer) filterContainer.style.display = 'none'; + const noResultsEl = document.createElement('div'); + noResultsEl.className = 'meilisearch-modal__indicator'; + noResultsEl.innerHTML = 'No results found'; + resultsContainer.appendChild(noResultsEl); + return; + } + + // Show filter container when there are results + if (filterContainer) filterContainer.style.display = 'block'; + + // Group results by category if available + const grouped = {}; + response.hits.forEach(hit => { + const category = hit.hierarchy_lvl0 || 'General'; + if (!grouped[category]) { + grouped[category] = []; } - }) - .then(response => { - resultsContainer.innerHTML = ''; + grouped[category].push(hit); + }); + + // Create result items + Object.keys(grouped).forEach(category => { + const results = grouped[category]; - if (response.hits.length === 0) { - const noResultsEl = document.createElement('div'); - noResultsEl.className = 'meilisearch-modal__indicator'; - noResultsEl.innerHTML = 'No results found'; - resultsContainer.appendChild(noResultsEl); - - return; + // Only add category header if there are multiple categories + if (Object.keys(grouped).length > 1) { + const categoryHeader = document.createElement('div'); + categoryHeader.className = 'meilisearch-modal__category-header'; + categoryHeader.textContent = category; + resultsContainer.appendChild(categoryHeader); } - // Group results by category if available - const grouped = {}; - response.hits.forEach(hit => { - const category = hit.hierarchy_lvl0 || 'General'; - if (!grouped[category]) { - grouped[category] = []; - } - grouped[category].push(hit); - }); - - // Create result items - Object.keys(grouped).forEach(category => { - const results = grouped[category]; - - // Only add category header if there are multiple categories - if (Object.keys(grouped).length > 1) { - const categoryHeader = document.createElement('div'); - categoryHeader.className = 'meilisearch-modal__category-header'; - categoryHeader.textContent = category; - resultsContainer.appendChild(categoryHeader); - } + results.forEach(hit => { + const resultItem = document.createElement('a'); + resultItem.href = hit.url || `/${hit.path}`; + resultItem.className = 'meilisearch-modal__result'; - results.forEach(hit => { - const resultItem = document.createElement('a'); - resultItem.href = hit.url || `/${hit.path}`; - resultItem.className = 'meilisearch-modal__result'; - - // Format content nicely - // Build title from hierarchy levels - const hierarchy_lvl1 = hit._formatted?.hierarchy_lvl1 - ? hit._formatted.hierarchy_lvl1.replace(//g, '') - : ''; - const hierarchy_lvl2 = hit._formatted?.hierarchy_lvl2 - ? hit._formatted.hierarchy_lvl2.replace(//g, '') - : ''; - const hierarchy_lvl3 = hit._formatted?.hierarchy_lvl3 - ? hit._formatted.hierarchy_lvl3.replace(//g, '') - : ''; - const hierarchy_lvl4 = hit._formatted?.hierarchy_lvl4 - ? hit._formatted.hierarchy_lvl4.replace(//g, '') - : ''; - const hierarchy_lvl5 = hit._formatted?.hierarchy_lvl5 - ? hit._formatted.hierarchy_lvl5.replace(//g, '') - : ''; - const content = hit._formatted?.content - ? hit._formatted.content.replace(//g, '') - : ''; + // Format content nicely + // Build title from hierarchy levels + const hierarchy_lvl1 = hit._formatted?.hierarchy_lvl1 + ? hit._formatted.hierarchy_lvl1.replace(//g, '') + : ''; + const hierarchy_lvl2 = hit._formatted?.hierarchy_lvl2 + ? hit._formatted.hierarchy_lvl2.replace(//g, '') + : ''; + const hierarchy_lvl3 = hit._formatted?.hierarchy_lvl3 + ? hit._formatted.hierarchy_lvl3.replace(//g, '') + : ''; + const hierarchy_lvl4 = hit._formatted?.hierarchy_lvl4 + ? hit._formatted.hierarchy_lvl4.replace(//g, '') + : ''; + const hierarchy_lvl5 = hit._formatted?.hierarchy_lvl5 + ? hit._formatted.hierarchy_lvl5.replace(//g, '') + : ''; + const content = hit._formatted?.content + ? hit._formatted.content.replace(//g, '') + : ''; - let title = ''; - if (hierarchy_lvl1) { - title = hierarchy_lvl1; - if (hierarchy_lvl2) { - title += ' > ' + hierarchy_lvl2; - if (hierarchy_lvl3) { - title += ' > ' + hierarchy_lvl3; - if (hierarchy_lvl4) { - title += ' > ' + hierarchy_lvl4; - if (hierarchy_lvl5) { - title += ' > ' + hierarchy_lvl5; - } + let title = ''; + if (hierarchy_lvl1) { + title = hierarchy_lvl1; + if (hierarchy_lvl2) { + title += ' > ' + hierarchy_lvl2; + if (hierarchy_lvl3) { + title += ' > ' + hierarchy_lvl3; + if (hierarchy_lvl4) { + title += ' > ' + hierarchy_lvl4; + if (hierarchy_lvl5) { + title += ' > ' + hierarchy_lvl5; } } } } - - resultItem.innerHTML = ` -
${title}
- ${content ? `
${content}
` : ''} - `; - - // Make clicking the result close the modal - resultItem.addEventListener('click', () => { - document.getElementById('meilisearch-modal-overlay').style.display = 'none'; - }); - - resultsContainer.appendChild(resultItem); + } + + resultItem.innerHTML = ` +
${title}
+ ${content ? `
${content}
` : ''} + `; + + // Make clicking the result close the modal + resultItem.addEventListener('click', () => { + document.getElementById('meilisearch-modal-overlay').style.display = 'none'; }); + + resultsContainer.appendChild(resultItem); }); - - }) - .catch(error => { - console.error('Meilisearch error:', error); - const errorEl = document.createElement('div'); - errorEl.className = 'meilisearch-modal__error'; - errorEl.innerHTML = 'Search error. Please try again.'; - - resultsContainer.appendChild(errorEl); }); + + }) + .catch(error => { + console.error('Meilisearch error:', error); + const errorEl = document.createElement('div'); + errorEl.className = 'meilisearch-modal__error'; + errorEl.innerHTML = 'Search error. Please try again.'; + + resultsContainer.appendChild(errorEl); + }); + }; + + // Add search event listener + let debounceTimer; + searchInput.addEventListener('input', (e) => { + clearTimeout(debounceTimer); + + const query = e.target.value.trim(); + const activeFilters = getActiveFilters(); + + if (query.length < 2) { + resultsContainer.innerHTML = ''; + const filterContainer = document.getElementById('meilisearch-filter-container'); + // Show filter container if there are active filters, even when search is empty + if (filterContainer) { + filterContainer.style.display = activeFilters.length > 0 ? 'block' : 'none'; + } + return; + } + + debounceTimer = setTimeout(() => { + performSearch(query, activeFilters); }, 300); }); } catch (error) { From 38236e011d9a43caceeb9a36bac73450f7172165 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Jun 2025 11:47:20 +0000 Subject: [PATCH 2/4] Update code samples [skip ci] --- snippets/samples/code_samples_typo_tolerance_guide_5.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/snippets/samples/code_samples_typo_tolerance_guide_5.mdx b/snippets/samples/code_samples_typo_tolerance_guide_5.mdx index 8f5efdef66..6733ece79f 100644 --- a/snippets/samples/code_samples_typo_tolerance_guide_5.mdx +++ b/snippets/samples/code_samples_typo_tolerance_guide_5.mdx @@ -15,6 +15,12 @@ client.index('movies').updateTypoTolerance({ }) ``` +```python Python +client.index('movies').update_typo_tolerance({ + 'disableOnNumbers': True +}) +``` + ```php PHP $client->index('movies')->updateTypoTolerance([ 'disableOnNumbers' => true From 264d0dc41c6e20be6f5ed7939feaa0751b79aa29 Mon Sep 17 00:00:00 2001 From: Guillaume Mourier Date: Sun, 22 Jun 2025 18:54:48 +0200 Subject: [PATCH 3/4] deboost error_codes --- docs-scraper.config.json | 4 ++- search.js | 54 ++++++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/docs-scraper.config.json b/docs-scraper.config.json index e4387c0751..3a7fe2c9a4 100644 --- a/docs-scraper.config.json +++ b/docs-scraper.config.json @@ -44,7 +44,9 @@ ], "filterableAttributes": [ "url", - "section" + "section", + "hierarchy_lvl0", + "hierarchy_lvl1" ], "synonyms": { "large language model": [ diff --git a/search.js b/search.js index a9ce123813..c06735f679 100644 --- a/search.js +++ b/search.js @@ -398,9 +398,8 @@ function initializeMeilisearchIntegration() { indicatorEl.innerHTML = 'Searching…'; resultsContainer.appendChild(indicatorEl); - // Build search options - const searchOptions = { - limit: 25, + // Build base search options + const baseSearchOptions = { attributesToHighlight: ['hierarchy_lvl1', 'hierarchy_lvl2', 'hierarchy_lvl3', 'hierarchy_lvl4', 'hierarchy_lvl5', 'content'], attributesToCrop: ['content'], cropLength: 100, @@ -410,19 +409,52 @@ function initializeMeilisearchIntegration() { } }; - // Add filter if sections are selected - if (activeFilters.length > 0) { - searchOptions.filter = activeFilters.map(section => `section = "${section}"`).join(' OR '); - } + // Create federated multi-search queries + const multiSearchQueries = [ + { + indexUid: MEILISEARCH_INDEX, + q: query, + // Lower weight for error code results + filter: activeFilters.length > 0 + ? `(${activeFilters.map(section => `section = "${section}"`).join(' OR ')}) AND (hierarchy_lvl0 = "Errors" OR hierarchy_lvl1 = "Error codes")` + : `hierarchy_lvl0 = "Errors" OR hierarchy_lvl1 = "Error codes"`, + federationOptions: { + weight: 0.7 + }, + ...baseSearchOptions + }, + { + indexUid: MEILISEARCH_INDEX, + q: query, + // Higher weight for non-error code results + filter: activeFilters.length > 0 + ? `(${activeFilters.map(section => `section = "${section}"`).join(' OR ')}) AND NOT (hierarchy_lvl0 = "Errors" OR hierarchy_lvl1 = "Error codes")` + : `NOT (hierarchy_lvl0 = "Errors" OR hierarchy_lvl1 = "Error codes")`, + federationOptions: { + weight: 1.0 + }, + ...baseSearchOptions + } + ]; - // Perform search - index.search(query, searchOptions) + // Perform federated multi-search + const multiSearchRequest = { + federation: { + limit: 25 + }, + queries: multiSearchQueries + }; + + client.multiSearch(multiSearchRequest) .then(response => { resultsContainer.innerHTML = ''; const filterContainer = document.getElementById('meilisearch-filter-container'); - if (response.hits.length === 0) { + // Handle federated multi-search response + const allHits = response.hits || []; + + if (allHits.length === 0) { if (filterContainer) filterContainer.style.display = 'none'; const noResultsEl = document.createElement('div'); noResultsEl.className = 'meilisearch-modal__indicator'; @@ -436,7 +468,7 @@ function initializeMeilisearchIntegration() { // Group results by category if available const grouped = {}; - response.hits.forEach(hit => { + allHits.forEach(hit => { const category = hit.hierarchy_lvl0 || 'General'; if (!grouped[category]) { grouped[category] = []; From 8f148436d110eb7c167d28baac98814f3e43f378 Mon Sep 17 00:00:00 2001 From: gui machiavelli Date: Tue, 24 Jun 2025 17:23:40 +0200 Subject: [PATCH 4/4] Update meilisearch-search-bar.css --- meilisearch-search-bar.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meilisearch-search-bar.css b/meilisearch-search-bar.css index 4fbb414954..d08c3ad5d4 100644 --- a/meilisearch-search-bar.css +++ b/meilisearch-search-bar.css @@ -256,4 +256,4 @@ .meilisearch-filter-tag.active:hover { background-color: #cb4d86; border-color: #cb4d86; -} \ No newline at end of file +}