feat: add support for semantic search using operand
This commit is contained in:
		
							parent
							
								
									14b89105dc
								
							
						
					
					
						commit
						5ef9aad501
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -5,3 +5,4 @@ resources | ||||
| content/.obsidian | ||||
| assets/indices/linkIndex.json | ||||
| assets/indices/contentIndex.json | ||||
| linkmap | ||||
|  | ||||
| @ -56,6 +56,6 @@ | ||||
|     } | ||||
|     const allIds = new Set([...getByField("title"), ...getByField("content")]) | ||||
|     const finalResults = [...allIds].map(formatForDisplay) | ||||
|     displayResults(finalResults) | ||||
|     displayResults(finalResults, true) | ||||
|   }) | ||||
| })() | ||||
							
								
								
									
										35
									
								
								assets/js/semantic-search.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								assets/js/semantic-search.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| const apiKey = "{{$.Site.Data.config.operandApiKey}}" | ||||
| 
 | ||||
| async function searchContents(query) { | ||||
|   const response = await fetch('https://prod.operand.ai/v3/search/objects', { | ||||
|     method: 'POST', | ||||
|     headers: { | ||||
|       'Content-Type': 'application/json', | ||||
|       Authorization: apiKey, | ||||
|     }, | ||||
|     body: JSON.stringify({ | ||||
|       query, | ||||
|       max: 10 | ||||
|     }), | ||||
|   }); | ||||
|   return (await response.json()); | ||||
| } | ||||
| 
 | ||||
| function debounce(func, timeout = 300) { | ||||
|   let timer; | ||||
|   return (...args) => { | ||||
|     clearTimeout(timer) | ||||
|     timer = setTimeout(() => { func.apply(this, args); }, timeout) | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| registerHandlers(debounce((e) => { | ||||
|   term = e.target.value | ||||
|   searchContents(term) | ||||
|     .then((res) => res.results.map(entry => ({ | ||||
|       url: entry.object.metadata.url, | ||||
|       content: entry.snippet, | ||||
|       title: entry.object.title | ||||
|     }))) | ||||
|     .then(results => displayResults(results)) | ||||
| })) | ||||
| @ -108,13 +108,11 @@ const highlight = (content, term) => { | ||||
| } | ||||
| 
 | ||||
| // Common utilities for search
 | ||||
| const resultToHTML = ({ url, title, content, term }) => { | ||||
|   const text = removeMarkdown(content) | ||||
|   const resultTitle = highlight(title, term) | ||||
|   const resultText = highlight(text, term) | ||||
| const resultToHTML = ({ url, title, content }) => { | ||||
|   const cleaned = removeMarkdown(content) | ||||
|   return `<button class="result-card" id="${url}">
 | ||||
|       <h3>${resultTitle}</h3> | ||||
|       <p>${resultText}</p> | ||||
|       <h3>${title}</h3> | ||||
|       <p>${cleaned}</p> | ||||
|   </button>` | ||||
| } | ||||
| 
 | ||||
| @ -183,7 +181,7 @@ const registerHandlers = (onInputFn) => { | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| const displayResults = (finalResults) => { | ||||
| const displayResults = (finalResults, extractHighlight = false) => { | ||||
|   const results = document.getElementById("results-container") | ||||
|   if (finalResults.length === 0) { | ||||
|     results.innerHTML = `<button class="result-card">
 | ||||
| @ -192,11 +190,17 @@ const displayResults = (finalResults) => { | ||||
|                 </button>` | ||||
|   } else { | ||||
|     results.innerHTML = finalResults | ||||
|       .map((result) => | ||||
|         resultToHTML({ | ||||
|           ...result, | ||||
|           term, | ||||
|         }), | ||||
|       .map((result) => { | ||||
|           if (extractHighlight) { | ||||
|             return resultToHTML({ | ||||
|               url: result.url, | ||||
|               title: highlight(result.title, term), | ||||
|               content: highlight(result.content, term) | ||||
|             }) | ||||
|           } else { | ||||
|             return resultToHTML(result) | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|       .join("\n") | ||||
|     const anchors = [...document.getElementsByClassName("result-card")] | ||||
|  | ||||
| @ -54,9 +54,13 @@ enableRecentNotes: false | ||||
| 
 | ||||
| # whether to display and 'edit' button next to the last edited field | ||||
| # that links to github | ||||
| enableGitHubEdit: false | ||||
| enableGitHubEdit: true | ||||
| GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content | ||||
| 
 | ||||
| # whether to use Operand to power semantic search | ||||
| enableSemanticSearch: true | ||||
| operandApiKey: "1e47d93b-1468-45b7-98d5-7f733d5e45e2" | ||||
| 
 | ||||
| # page description used for SEO | ||||
| description: | ||||
|   Host your second brain and digital garden for free. Quartz features extremely fast full-text search, | ||||
|  | ||||
| @ -10,8 +10,10 @@ enableSPA: true | ||||
| enableFooter: true | ||||
| enableContextualBacklinks: true | ||||
| enableRecentNotes: false | ||||
| enableGitHubEdit: false | ||||
| enableGitHubEdit: true | ||||
| GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content | ||||
| enableSemanticSearch: true | ||||
| operandApiKey: "1e47d93b-1468-45b7-98d5-7f733d5e45e2" | ||||
| description: | ||||
|   Host your second brain and digital garden for free. Quartz features extremely fast full-text search, | ||||
|   Wikilink support, backlinks, local graph, tags, and link previews. | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| {{if $.Site.Data.config.enableGitHubEdit}} | ||||
| <a href="{{$.Site.Data.config.GitHubLink}}/{{.Path}}" rel="noopener">Edit Source</a> | ||||
| <a href="{{$.Site.Data.config.GitHubLink}}/{{.File.Path}}" rel="noopener">Edit Source</a> | ||||
| {{end}} | ||||
|  | ||||
| @ -6,7 +6,13 @@ | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| {{if $.Site.Data.config.enableSemanticSearch}} | ||||
| {{ $js := resources.Get "js/semantic-search.js" | resources.ExecuteAsTemplate "js/semantic-search.js" . | resources.Fingerprint "md5" | resources.Minify }} | ||||
| <script defer src="{{ $js.Permalink }}"></script> | ||||
| {{else}} | ||||
| <script src="https://cdn.jsdelivr.net/npm/flexsearch@0.7.21/dist/flexsearch.bundle.js" | ||||
|   integrity="sha256-i3A0NZGkhsKjVMzFxv3ksk0DZh3aXqu0l49Bbh0MdjE=" crossorigin="anonymous" defer></script> | ||||
| {{ $js := resources.Get "js/search.js" | resources.Fingerprint "md5" | resources.Minify }} | ||||
| {{ $js := resources.Get "js/full-text-search.js" | resources.Fingerprint "md5" | resources.Minify }} | ||||
| <script defer src="{{ $js.Permalink }}"></script> | ||||
| {{end}} | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user