feat: add support for semantic search using operand
This commit is contained in:
		
							parent
							
								
									14b89105dc
								
							
						
					
					
						commit
						5ef9aad501
					
				
					 8 changed files with 69 additions and 17 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -5,3 +5,4 @@ resources
 | 
				
			||||||
content/.obsidian
 | 
					content/.obsidian
 | 
				
			||||||
assets/indices/linkIndex.json
 | 
					assets/indices/linkIndex.json
 | 
				
			||||||
assets/indices/contentIndex.json
 | 
					assets/indices/contentIndex.json
 | 
				
			||||||
 | 
					linkmap
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,6 +56,6 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const allIds = new Set([...getByField("title"), ...getByField("content")])
 | 
					    const allIds = new Set([...getByField("title"), ...getByField("content")])
 | 
				
			||||||
    const finalResults = [...allIds].map(formatForDisplay)
 | 
					    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
 | 
					// Common utilities for search
 | 
				
			||||||
const resultToHTML = ({ url, title, content, term }) => {
 | 
					const resultToHTML = ({ url, title, content }) => {
 | 
				
			||||||
  const text = removeMarkdown(content)
 | 
					  const cleaned = removeMarkdown(content)
 | 
				
			||||||
  const resultTitle = highlight(title, term)
 | 
					 | 
				
			||||||
  const resultText = highlight(text, term)
 | 
					 | 
				
			||||||
  return `<button class="result-card" id="${url}">
 | 
					  return `<button class="result-card" id="${url}">
 | 
				
			||||||
      <h3>${resultTitle}</h3>
 | 
					      <h3>${title}</h3>
 | 
				
			||||||
      <p>${resultText}</p>
 | 
					      <p>${cleaned}</p>
 | 
				
			||||||
  </button>`
 | 
					  </button>`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -183,7 +181,7 @@ const registerHandlers = (onInputFn) => {
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const displayResults = (finalResults) => {
 | 
					const displayResults = (finalResults, extractHighlight = false) => {
 | 
				
			||||||
  const results = document.getElementById("results-container")
 | 
					  const results = document.getElementById("results-container")
 | 
				
			||||||
  if (finalResults.length === 0) {
 | 
					  if (finalResults.length === 0) {
 | 
				
			||||||
    results.innerHTML = `<button class="result-card">
 | 
					    results.innerHTML = `<button class="result-card">
 | 
				
			||||||
| 
						 | 
					@ -192,11 +190,17 @@ const displayResults = (finalResults) => {
 | 
				
			||||||
                </button>`
 | 
					                </button>`
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    results.innerHTML = finalResults
 | 
					    results.innerHTML = finalResults
 | 
				
			||||||
      .map((result) =>
 | 
					      .map((result) => {
 | 
				
			||||||
        resultToHTML({
 | 
					          if (extractHighlight) {
 | 
				
			||||||
          ...result,
 | 
					            return resultToHTML({
 | 
				
			||||||
          term,
 | 
					              url: result.url,
 | 
				
			||||||
        }),
 | 
					              title: highlight(result.title, term),
 | 
				
			||||||
 | 
					              content: highlight(result.content, term)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return resultToHTML(result)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .join("\n")
 | 
					      .join("\n")
 | 
				
			||||||
    const anchors = [...document.getElementsByClassName("result-card")]
 | 
					    const anchors = [...document.getElementsByClassName("result-card")]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,9 +54,13 @@ enableRecentNotes: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# whether to display and 'edit' button next to the last edited field
 | 
					# whether to display and 'edit' button next to the last edited field
 | 
				
			||||||
# that links to github
 | 
					# that links to github
 | 
				
			||||||
enableGitHubEdit: false
 | 
					enableGitHubEdit: true
 | 
				
			||||||
GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content
 | 
					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
 | 
					# page description used for SEO
 | 
				
			||||||
description:
 | 
					description:
 | 
				
			||||||
  Host your second brain and digital garden for free. Quartz features extremely fast full-text search,
 | 
					  Host your second brain and digital garden for free. Quartz features extremely fast full-text search,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,8 +10,10 @@ enableSPA: true
 | 
				
			||||||
enableFooter: true
 | 
					enableFooter: true
 | 
				
			||||||
enableContextualBacklinks: true
 | 
					enableContextualBacklinks: true
 | 
				
			||||||
enableRecentNotes: false
 | 
					enableRecentNotes: false
 | 
				
			||||||
enableGitHubEdit: false
 | 
					enableGitHubEdit: true
 | 
				
			||||||
GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content
 | 
					GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content
 | 
				
			||||||
 | 
					enableSemanticSearch: true
 | 
				
			||||||
 | 
					operandApiKey: "1e47d93b-1468-45b7-98d5-7f733d5e45e2"
 | 
				
			||||||
description:
 | 
					description:
 | 
				
			||||||
  Host your second brain and digital garden for free. Quartz features extremely fast full-text search,
 | 
					  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.
 | 
					  Wikilink support, backlinks, local graph, tags, and link previews.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,3 @@
 | 
				
			||||||
{{if $.Site.Data.config.enableGitHubEdit}}
 | 
					{{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}}
 | 
					{{end}}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,13 @@
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </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"
 | 
					<script src="https://cdn.jsdelivr.net/npm/flexsearch@0.7.21/dist/flexsearch.bundle.js"
 | 
				
			||||||
  integrity="sha256-i3A0NZGkhsKjVMzFxv3ksk0DZh3aXqu0l49Bbh0MdjE=" crossorigin="anonymous" defer></script>
 | 
					  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>
 | 
					<script defer src="{{ $js.Permalink }}"></script>
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue