Um adaptador para usar a incrível biblioteca Instantsearch.js com um Typesense Search Server, para construir interfaces de pesquisa ricas.
Aqui está um exemplo de UI que você pode construir com este adaptador: songs-search.typesense.org
Nota: Se sua interface de pesquisa for construída em um componente de preenchimento automático personalizado ou for baseada em @algolia/autocomplete-js, então você não precisa deste adaptador para usá-lo com Typesense, pois a biblioteca typesense-js já suporta busca no lado do cliente dados de qualquer fonte de dados assíncrona. Leia mais aqui.
O pessoal da Algolia criou e abriu o código-fonte do Instantsearch.js, que é uma coleção de componentes prontos para uso que você pode usar para criar experiências de pesquisa interativas rapidamente.
Com o adaptador neste repositório, você poderá usar Instantsearch (e seus primos React, Vue e Angular) com dados indexados em um servidor de pesquisa Typesense.
Se você nunca usou o Instantsearch antes, recomendamos consultar o guia de primeiros passos aqui. Depois de ler o guia, siga as instruções abaixo para conectar o adaptador Typesense ao Instantsearch.
Aqui está um guia sobre como construir uma interface de pesquisa rápida com Typesense e InstantSearch.js: https://typesense.org/docs/0.20.0/guide/search-ui-components.html
Aqui está um aplicativo inicial de demonstração que mostra como usar o adaptador: https://github.com/typesense/typesense-instantsearch-demo
$ npm install --save typesense-instantsearch-adapter @babel/runtime
ou
$ yarn add typesense-instantsearch-adapter @babel/runtime
ou você também pode incluir diretamente o adaptador por meio de uma tag de script em seu HTML:
< script src =" https://cdn.jsdelivr.net/npm/typesense-instantsearch-adapter@2/dist/typesense-instantsearch-adapter.min.js " > </ script >
<!-- You might want to pin the version of the adapter used if you don't want to always receive the latest minor version -->
Como este é um adaptador, ele não instalará a biblioteca Instantsearch automaticamente para você. Você precisa instalar um dos seguintes itens diretamente em seu aplicativo:
Você encontrará informações sobre como começar a usar cada uma das bibliotecas acima em seus respectivos repositórios.
Também recomendamos verificar create-instantsearch-app para criar sua interface de pesquisa a partir de um modelo inicial.
import instantsearch from "instantsearch.js" ;
import { searchBox , hits } from "instantsearch.js/es/widgets" ;
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter" ;
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "abcd" , // Be sure to use an API key that only allows search operations
nodes : [
{
host : "localhost" ,
path : "" , // Optional. Example: If you have your typesense mounted in localhost:8108/typesense, path should be equal to '/typesense'
port : "8108" ,
protocol : "http" ,
} ,
] ,
cacheSearchResultsForSeconds : 2 * 60 , // Cache search results from server. Defaults to 2 minutes. Set to 0 to disable caching.
} ,
// The following parameters are directly passed to Typesense's search API endpoint.
// So you can pass any parameters supported by the search endpoint below.
// query_by is required.
additionalSearchParameters : {
query_by : "name,description,categories" ,
} ,
} ) ;
const searchClient = typesenseInstantsearchAdapter . searchClient ;
const search = instantsearch ( {
searchClient ,
indexName : "products" ,
} ) ;
search . addWidgets ( [
searchBox ( {
container : "#searchbox" ,
} ) ,
hits ( {
container : "#hits" ,
templates : {
item : `
<div class="hit-name">
{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}
</div>
` ,
} ,
} ) ,
] ) ;
search . start ( ) ;
É possível incluir aqui qualquer um dos widgets do Instantsearch que são suportados pelo adaptador.
Você também encontrará um exemplo prático em test/support/testground. Para executá-lo, execute npm run testground
na pasta raiz do projeto.
import React from "react" ;
import ReactDOM from "react-dom" ;
import { SearchBox } from "react-instantsearch-dom" ;
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter" ;
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "abcd" , // Be sure to use an API key that only allows search operations
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "" , // Optional. Example: If you have your typesense mounted in localhost:8108/typesense, path should be equal to '/typesense'
protocol : "http" ,
} ,
] ,
cacheSearchResultsForSeconds : 2 * 60 , // Cache search results from server. Defaults to 2 minutes. Set to 0 to disable caching.
} ,
// The following parameters are directly passed to Typesense's search API endpoint.
// So you can pass any parameters supported by the search endpoint below.
// query_by is required.
additionalSearchParameters : {
query_by : "name,description,categories" ,
} ,
} ) ;
const searchClient = typesenseInstantsearchAdapter . searchClient ;
const App = ( ) => (
< InstantSearch indexName = "products" searchClient = { searchClient } >
< SearchBox / >
< Hits / >
< / InstantSearch >
) ;
Você pode então adicionar aqui qualquer um dos widgets Instantsearch-React que são suportados pelo adaptador.
As instruções acima também se aplicam ao React Native.
Aplicativo.vue:
< template >
< ais-instant-search :search-client = " searchClient " index-name = " products " >
< ais-search-box />
< ais-hits >
< div slot = " item " slot-scope = " { item } " >
< h2 >{{ item.name }}</ h2 >
</ div >
</ ais-hits >
</ ais-instant-search >
</ template >
< script >
import TypesenseInstantSearchAdapter from " typesense-instantsearch-adapter " ;
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ({
server : {
apiKey : " abcd " , // Be sure to use an API key that only allows search operations
nodes : [
{
host : " localhost " ,
path : " " , // Optional. Example: If you have your typesense mounted in localhost:8108/typesense, path should be equal to '/typesense'
port : " 8108 " ,
protocol : " http " ,
},
],
cacheSearchResultsForSeconds : 2 * 60 , // Cache search results from server. Defaults to 2 minutes. Set to 0 to disable caching.
},
// The following parameters are directly passed to Typesense's search API endpoint.
// So you can pass any parameters supported by the search endpoint below.
// query_by is required.
additionalSearchParameters : {
query_by : " name,description,categories " ,
},
});
const searchClient = typesenseInstantsearchAdapter . searchClient ;
export default {
data () {
return {
searchClient,
};
},
};
</ script >
Você pode então adicionar aqui qualquer um dos widgets do Instantsearch que são suportados pelo adaptador.
// app.component.ts
import { Component } from "@angular/core" ;
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter" ;
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "abcd" , // Be sure to use an API key that only allows search operations
nodes : [
{
host : "localhost" ,
path : "" , // Optional. Example: If you have your typesense mounted in localhost:8108/typesense, path should be equal to '/typesense'
port : "8108" ,
protocol : "http" ,
} ,
] ,
cacheSearchResultsForSeconds : 2 * 60 , // Cache search results from server. Defaults to 2 minutes. Set to 0 to disable caching.
} ,
// The following parameters are directly passed to Typesense's search API endpoint.
// So you can pass any parameters supported by the search endpoint below.
// query_by is required.
additionalSearchParameters : {
query_by : "name,description,categories" ,
} ,
} ) ;
const searchClient = typesenseInstantsearchAdapter . searchClient ;
@ Component ( {
selector : "app-root" ,
templateUrl : "./app.component.html" ,
styleUrls : [ "./app.component.css" ] ,
} )
export class AppComponent {
config = {
indexName : "products" ,
searchClient ,
} ;
}
Você pode então adicionar aqui qualquer um dos widgets do Instantsearch que são suportados pelo adaptador.
hierarchicalMenu
Para este widget, você deseja criar campos independentes no esquema da coleção com esta convenção de nomenclatura específica:
field.lvl0
field.lvl1
field.lvl2
para uma hierarquia aninhada de field.lvl0 > field.lvl1 > field.lvl2
Cada um desses campos também pode conter uma matriz de valores. Isto é útil para lidar com múltiplas hierarquias.
sortBy
Ao instanciar este widget, você deseja definir o valor do nome do índice para este formato específico:
search . addWidgets ( [
sortBy ( {
container : "#sort-by" ,
items : [
{ label : "Default" , value : "products" } ,
{ label : "Price (asc)" , value : "products/sort/price:asc" } ,
{ label : "Price (desc)" , value : "products/sort/price:desc" } ,
] ,
} ) ,
] ) ;
O padrão generalizado para o atributo value é: <index_name>[/sort/<sort_by>]
. O adaptador usará o valor em <sort_by>
como o valor do parâmetro de procura sort_by
.
configure
Se você precisar especificar um parâmetro de pesquisa filter_by
para Typesense, você deseja usar o widget configure
InstantSearch, junto com facetFilters
, numericFilters
ou filters
.
O formato para facetFilters
e numericFilters
é o mesmo do Algolia descrito aqui. Mas filters
precisam estar no formato filter_by
do Typesense conforme descrito nesta tabela aqui.
Definir filter_by
dentro da configuração additionalQueryParameters
só funciona quando os widgets são carregados inicialmente, porque o InstantSearch substitui internamente o campo filter_by
posteriormente. Leia mais aqui.
index
Para pesquisa federada/multi-índice, você precisará usar o widget index
. Para poder especificar diferentes parâmetros de pesquisa para cada índice/coleção, você pode especificá-los usando a configuração collectionSpecificSearchParameters
:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "abcd" , // Be sure to use an API key that only allows search operations
nodes : [ { host : "localhost" , path : "/" , port : "8108" , protocol : "http" } ] ,
} ,
// Search parameters that are common to all collections/indices go here:
additionalSearchParameters : {
numTypos : 3 ,
} ,
// Search parameters that need to be *overridden* on a per-collection-basis go here:
collectionSpecificSearchParameters : {
products : {
query_by : "name,description,categories" ,
} ,
brands : {
query_by : "name" ,
} ,
} ,
} ) ;
const searchClient = typesenseInstantsearchAdapter . searchClient ;
Essencialmente, quaisquer parâmetros definidos em collectionSpecificSearchParameters
serão mesclados com os valores em additionalSearchParameters
ao consultar o Typesense, substituindo efetivamente os valores em additionalSearchParameters
por coleção.
geoSearch
Algolia usa _geoloc
por padrão para o nome do campo que armazena os valores de lat-long para um registro. No Typesense, você pode nomear qualquer campo de localização geográfica. Se você usar um nome diferente de _geoloc
, será necessário especificá-lo ao inicializar o adaptador como abaixo, para que o InstantSearch possa acessá-lo:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
geoLocationField : "lat_lng_field" , // <<======
additionalSearchParameters ,
} ) ;
dynamicWidgets
Disponível a partir do Typesense Server
v0.25.0.rc12
Este widget dynamicWidgets
funciona imediatamente sem alterações adicionais, mas se você deseja controlar a ordem em que essas facetas são exibidas na interface do usuário, o Instantsearch espera que um parâmetro chamado renderingContent
seja definido.
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
renderingContent : {
// <<===== Add this, only if you want to control the order of the widgets displayed by dynamicWidgets
facetOrdering : {
facets : {
order : [ "size" , "brand" ] , // <<===== Change this as needed
} ,
} ,
} ,
additionalSearchParameters ,
} ) ;
Leia mais sobre todas as opções disponíveis para renderingContent
na documentação da Algolia aqui.
Disponível a partir do typesense-instantsearch-adapter
2.7.0-2
Se algum campo de string em seus documentos tiver dois pontos :
em seus valores (por exemplo, digamos que haja um campo chamado { brand: "a:b" }
, então você precisará adicionar um parâmetro como abaixo ao instanciar o adaptador:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
facetableFieldsWithSpecialCharacters : [ "brand" ] , // <======= Add string fields that have colons in their values here, to aid in parsing
additionalSearchParameters ,
} ) ;
Se algum nome de campo numérico em seus documentos tiver caracteres especiais como >
, <
, =
(por exemplo, digamos que haja um campo chamado { price>discount: 3.0 }
), então você precisará adicionar um parâmetro como abaixo ao instanciar o adaptador:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
facetableFieldsWithSpecialCharacters : [ "price>discount" ] , // // <======= Add numeric fields that have >, < or = in their names, to aid in parsing
additionalSearchParameters ,
} ) ;
facet_by
Disponível a partir do typesense-instantsearch-adapter
2.8.0-1
e Typesense Serverv0.26.0.rc25
O parâmetro facet_by
é gerenciado internamente pelo InstantSearch quando você usa os vários widgets de filtro.
Mas se você precisar passar opções personalizadas para o parâmetro facet_by
(por exemplo: opções de classificação do lado do servidor), poderá usar o parâmetro facetByOptions
conforme mostrado abaixo:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
facetByOptions : {
brand : "(sort_by: _alpha:asc)" ,
category : "(sort_by: _alpha:desc)" ,
} , // <======= Add any facet_by parameter as a key value pair. Don't forget the surrounding parantheses in the value.
collectionSpecificFacetByOptions : {
collection1 : {
brand : "(sort_by: _alpha:desc)" ,
} ,
} , // <======= Use this parameter if multiple collections share the same field names, and you want to use different options for each field. This will override facetByOptions for that particular collection.
additionalSearchParameters ,
} ) ;
Observe que para classificar em refinementLists, além de classificar no lado do servidor Typesense, você também precisa passar o parâmetro sortBy
para o widget refinementList para também classificar os resultados adequadamente no lado do cliente.
filter_by
Disponível a partir do typesense-instantsearch-adapter
2.8.0-5
O parâmetro filter_by
é gerenciado internamente pelo InstantSearch quando você usa os vários widgets de filtro.
Por padrão, o adaptador usa filtragem exata ( filter_by: field:=value
) ao enviar as consultas ao Typesense. Se você precisar configurar o adaptador para usar :
(filtragem em nível de palavra não exata - filter_by: field:value
), você deseja instanciar o adaptador usando a configuração filterByOptions
:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
filterByOptions : {
brand : { exactMatch : false } , // <========== Add this to do non-exact word-level filtering
category : { exactMatch : false } ,
} ,
collectionSpecificFilterByOptions : {
collection1 : {
brand : { exactMatch : false } ,
} ,
} , // <======= Use this parameter if multiple collections share the same field names, and you want to use different options for each field. This will override filterByOptions for that particular collection.
additionalSearchParameters ,
} ) ;
Disponível a partir do typesense-instantsearch-adapter
2.9.0-0
Esta é uma maneira de desabilitar substituições/regras de curadoria quando os usuários selecionam uma ordem de classificação específica:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
sortByOptions : {
"field1:desc,field2:desc" : { enable_overrides : false } , // <========== Add this to disable sorting when this particular Typesense `sort_by` string is generated by the sortBy widget
} ,
collectionSpecificSortByOptions : {
collection2 : {
"field1:desc,field2:desc" : { enable_overrides : false } ,
} ,
} , // <======= Use this parameter if multiple collections share the same field names, and you want to use different options for each field. This will override sortByOptions for that particular collection.
additionalSearchParameters ,
} ) ;
Se você tiver widgets sortBy configurados com um valor indexName de products/sort/price:asc
por exemplo, a chave dentro de sortByOptions
deve ser price:asc
.
Disponível a partir do typesense-instantsearch-adapter
2.7.1-4
Por padrão, quando group_by
é usado como parâmetro de procura, o adaptador nivela os resultados em todos os grupos em uma única lista de ocorrências sequenciais.
Se quiser preservar os grupos, defina flattenGroupedHits: false
ao instanciar o adaptador.
Isso colocará o primeiro hit em um grupo como o hit principal e, em seguida, adicionará todos os hits do grupo dentro de uma chave _grouped_hits
dentro de cada hit.
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
flattenGroupedHits : false , // <=======
additionalSearchParameters ,
} ) ;
Disponível a partir do typesense-instantsearch-adapter
2.7.0-3
A ideia geral é primeiro conectar-se ao ciclo de vida da consulta do Instantsearch, interceptar a consulta digitada e enviá-la para uma API de incorporação, buscar os embeddings e, em seguida, enviar os vetores ao Typesense para fazer uma pesquisa de vetor vizinho mais próximo.
Aqui está uma demonstração que você pode executar localmente para ver isso em ação: https://github.com/typesense/showcase-hn-comments-semantic-search.
Veja como fazer isso em Instantsearch.js:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "xyz" ,
nodes : [
{
host : "localhost" ,
port : "8108" ,
path : "/" ,
protocol : "http" ,
} ,
] ,
} ,
additionalSearchParameters ,
} ) ;
// from https://github.com/typesense/showcase-hn-comments-semantic-search/blob/8a33006cae58b425c53f56a64e1273e808cd9375/src/js/index.js#L101
const searchClient = typesenseInstantsearchAdapter . searchClient ;
search = instantsearch ( {
searchClient ,
indexName : INDEX_NAME ,
routing : true ,
async searchFunction ( helper ) {
// This fetches 200 (nearest neighbor) results for semantic / hybrid search
let query = helper . getQuery ( ) . query ;
const page = helper . getPage ( ) ; // Retrieve the current page
if ( query !== "" && [ "semantic" , "hybrid" ] . includes ( $ ( "#search-type-select" ) . val ( ) ) ) {
console . log ( helper . getQuery ( ) . query ) ;
helper
. setQueryParameter (
"typesenseVectorQuery" , // <=== Special parameter that only works in [email protected] and above
`embedding:([], k:200)` ,
)
. setPage ( page )
. search ( ) ;
console . log ( helper . getQuery ( ) . query ) ;
} else {
helper . setQueryParameter ( "typesenseVectorQuery" , null ) . setPage ( page ) . search ( ) ;
}
} ,
} ) ;
Existem dois modos de cache:
Cache do lado do servidor:
Para habilitar o cache do lado do servidor, adicione um parâmetro chamado useServerSideSearchCache: true
no bloco de configuração server
do typesense-instantsearch-adapter como este:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "..." ,
nearestNode : { ... } ,
nodes : [ ... ] ,
useServerSideSearchCache : true // <<< Add this to send use_cache as a query parameter instead of post body parameter
} ,
additionalSearchParameters : { ... }
} ) ;
Isso fará com que o adaptador adicione ?use_cache=true
como um parâmetro de consulta de URL a todas as solicitações de pesquisa iniciadas pelo adaptador, o que fará com que o Typesense Server habilite o cache do lado do servidor para essas solicitações.
Cache do lado do cliente:
O adaptador também possui cache do lado do cliente habilitado por padrão, para evitar chamadas de rede desnecessárias para o servidor. O TTL para este cache do lado do cliente pode ser configurado assim:
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter ( {
server : {
apiKey : "..." ,
nearestNode : { ... } ,
nodes : [ ... ] ,
cacheSearchResultsForSeconds : 2 * 60 // <<< Add this to configure the TTL for client-side cache in the browser
} ,
additionalSearchParameters : { ... }
} ) ;
Servidor Typesense | Adaptador TypeSense-InstantSearch | pesquisa instantânea.js | reação-pesquisa instantânea | vue-instantsearch | pesquisa instantânea angular |
---|---|---|---|---|---|
>= v0.25.0 | >= v2.7.1 | >= 4,51 | >= 6,39 | >= 4,8 | >= 4,4 |
>= v0.25.0.rc14 | >= v2.7.0-1 | >= 4,51 | >= 6,39 | >= 4,8 | >= 4,4 |
>= v0.25.0.rc12 | >= v2.6.0 | >= 4,51 | >= 6,39 | >= 4,8 | >= 4,4 |
>=v0.24 | >= v2.5.0 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
>=v0.21 | >= v2.0.0 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
>=v0.19 | >= v1.0.0 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
>=v0.15 | >= v0.3.0 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
>=v0.14 | >= v0.2.0 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
>=v0.13 | >=v0.1.0 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
>=v0.12 | >=v0.0.4 | >= 4.2.0 | >= 6.0.0 | >= 2.2.1 | >= 3.0.0 |
Se uma versão específica das bibliotecas acima não funcionar com o adaptador, abra um problema no Github com detalhes.
Este adaptador funciona com todos os widgets desta lista
$ npm install
$ npm run typesenseServer
$ FORCE_REINDEX=true npm run indexTestData
$ npm link typesense-instantsearch-adapter
$ npm run testground
$ npm test
Para lançar uma nova versão, usamos o pacote np:
$ npm install --global np
$ np
# Follow instructions that np shows you
Se você tiver alguma dúvida ou tiver algum problema, crie um problema no Github e faremos o possível para ajudar.
© 2020-presente Typesense, Inc.