Hi All- I’m exploring Typesense for the first time...
# community-help
d
Hi All- I’m exploring Typesense for the first time and walking through the demo here: https://typesense.org/docs/guide/search-ui-components.html#building-search-uis. When I try to perform a search on the page I get the following error:
Copy code
[Error] Error: 404 - Could not find a facet field named `*` in the schema. — TypesenseInstantsearchAdapter.js:81
	_callee$ (app.a6a4d504.js:7599)
	tryCatch (app.a6a4d504.js:192)
	invoke (app.a6a4d504.js:423)
	asyncGeneratorStep (app.a6a4d504.js:890)
	_next (app.a6a4d504.js:912)
	promiseReactionJob
[Error] Unhandled Promise Rejection: Error: 404 - Could not find a facet field named `*` in the schema.
	(anonymous function) (instantsearch.js@4.39.1:2:32449)
	(anonymous function) (instantsearch.js@4.39.1:2:105993)
	(anonymous function) (instantsearch.js@4.39.1:2:32639)
	(anonymous function) (instantsearch.js@4.39.1:2:49241)
	(anonymous function)
	promiseReactionJob
Is this something I am missing in
app.js
?
j
Hmmm, I wonder if this is an issue with a recent version of create-instantsearch-app. Do you see a widget called
dynamicWidgets
in app.js? If so, could you remove it and try again?
d
Different. Now I get
Could not find a facet named 'abstract' in the schema
. One of my fields in my schema is called “abstract”.
j
Could you share your full schema?
Also, is the "abstract" field set to "facet: true" in the schema?
d
Sure, below. It is set to “false”. I’m not sure what that means.
Copy code
{
  "name": "ascl_entries",
  "fields": [
    {
      "name": "ascl_id",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "title",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "credit",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "abstract",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "topic_id",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "bibcode",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "views",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "preferred_citation",
      "type": "string",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "site_list",
      "type": "string[]",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "used_in",
      "type": "string[]",
      "facet": false,
      "optional": false,
      "index": true
    },
    {
      "name": "described_in",
      "type": "string[]",
      "facet": false,
      "optional": false,
      "index": true
    }
  ],
  "default_sorting_field": ""
}
j
You want to set
"facet": true
for the abstract field if you want to use it with instant search
d
This is how it was created in Python:
Copy code
schema = {
	"name" : "ascl_entries",
	"fields" : [
		{ "name":"ascl_id", "type":"string" },
		{ "name":"title", "type":"string"},
		{ "name":"credit", "type":"string"},
		{ "name":"abstract", "type":"string"},
		{ "name":"topic_id", "type":"string"},
		{ "name":"bibcode", "type":"string"},
		{ "name":"views", "type":"string"},
		{ "name":"preferred_citation", "type":"string"},
		{ "name":"site_list", "type":"string[]"},
		{ "name":"used_in", "type":"string[]"},
		{ "name":"described_in", "type":"string[]"}
		#{ "name":"keywords", "type":"string[]"},
	]#,
#  "default_sorting_field": "title" # optional
}
Great, I will try that.
j
On a side note, these look like academic papers? If so, any reason you're trying to filter/facet by a long description field like abstract?
Filtering is only useful if most records share common values for the field you're filtering on
d
Adding
"facet":True
to the schema worked. I don’t have the text of the full papers, just the abstracts. I’m not sure what you mean though; the intent was to be able to filter on arbitrary words, e.g. “hydrogen”.
j
Abstracts are essentially paragraph summaries of the paper right?
d
Yes.
j
If you want to find keywords within the abstract, you want to use that field in query_by
Wait, actually I (might have) assumed (incorrectly) that you're trying to use the refinementList widget with the instantsearch. If my assumption is wrong, ignore me!
But I'm curious how your instantsearch widget configuration code looks, that would require the abstract field to be set as a facet...
d
If you are referring to the
keywords
field in my schema, the data set I am using as a sample doesn’t have any of those populated. I’m not familiar with the refinementList? This is literally my first time using Typesense; just trying to get a minimum working example to see how I might integrate it into a web app. This is the whole of the
app.js
, pretty short. I minimally modified it from the template.
Copy code
const { algoliasearch, instantsearch } = window;

import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";

const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: "zPOYYT6TdNOIK9h87bzv5GPtZXIMaxJC", // Be sure to use the search-only-api-key
    nodes: [
      {
        host: "localhost",
        port: "8108",
        protocol: "http"
      }
    ]
  },
  // The following parameters are directly passed to Typesense's search API endpoint.
  //  So you can pass any parameters supported by the search endpoint below.
  //  queryBy is required.
  additionalSearchParameters: {
    query_by: "title,abstract"
  }
});
const searchClient = typesenseInstantsearchAdapter.searchClient;

const search = instantsearch({
  searchClient,
  indexName: "ascl_entries"
});

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: '#searchbox',
  }),
  instantsearch.widgets.hits({
    container: '#hits',
       templates: {
          item: `
            <div>
              <img src="" align="left" alt="" />
              <div class="hit-name">
                {{#helpers.highlight}}{ "attribute": "title" }{{/helpers.highlight}}
              </div>
              <div class="hit-abstract">
                {{#helpers.highlight}}{ "attribute": "abstract" }{{/helpers.highlight}}
              </div>
              <div class="hit-ascl_id">ASCL ID: </div>
            </div>
          `,
        },
  }),
  instantsearch.widgets.configure({
    facets: ['abstract'],
    maxValuesPerFacet: 20,
  }),
//   instantsearch.widgets.dynamicWidgets({
//     container: '#dynamic-widgets',
//     fallbackWidget({ container, attribute }) {
//       return instantsearch.widgets.refinementList({
//         container,
//         attribute,
//       });
//     },
//     widgets: [],
//   }),
  instantsearch.widgets.pagination({
    container: '#pagination',
  }),
]);

search.start();
j
Ah, I see what's triggering that. You can also comment out this part:
Copy code
instantsearch.widgets.configure({
    facets: ['abstract'],
    maxValuesPerFacet: 20,
  }),
And then you should be able to remove facet: true from abstract
For context: here's what faceting is usually used for: https://typesense.org/docs/0.22.2/api/documents.html#facet-results
Setting
facet: true
on a field with almost all unique values across records ends up using a lot of memory...
d
Ah, ok. Yes, commented that out and it worked. Should I be thinking “facet” == “keyword”?
j
Yup, that's a good field to set as a facet
d
Oh, I just mean conceptually.
j
Ah, Keyword is a little broad to use in this context... Here's one example: if you have a list of products, you'd typically set
color
,
brand
as faceted field, so you can display a filter to users that lets them drill-down products based on color or brand.
d
Thanks, that makes sense. If you don’t mind me asking, I’m hoping to create an interface that allows users to search for small blog entries, think Stack Overflow sized or a little bigger. I want to be able to specify both keyword searches (so, faceted) and free form text. If one selects “planets” as a keyword, I’d get a unique list of all keywords for the entries that match “planets”. Is something like this possible? Would that UI need to be built from scratch?
I think the more general question is that I have the working example, but I’m not sure how to take it and customize it for my purposes.
j
I’d get a unique list of all keywords for the entries that match “planets”.
Did you mean unique list of records, or keywords?
Or do you have a UI mockup you're trying to build?
d
Unique list of keywords. Selecting one would display a list such that if a second were selected, at least one entry would be found. I have a concept in my head; still need to sketch it out.
j
Sounds like a hierarchy of keywords then?
In any case, yeah this should be doable with Instantsearch, but a hierarchy of checkboxes might need a custom widget - they only have a hierarchy of single-select items out of the box
d
Yes, functionally.
j
For eg: see the browse by categories widget on the left here: https://ecommerce-store.typesense.org/
d
I don’t visualize it as hierarchical. For example, let’s say the corpus is code snippets. Selecting “python” would include “file I/O”, “regex”, etc. but maybe not “Objective-C”.
j
Ah, you would have to build that mapping and put it inside each record
d
Given above, I would have “python” and “file I/O” as predefined keywords in the record. Is that what you mean?
In the interface you linked, I suppose I’m looking for an AND search rather than OR.
j
So you'd have create an array field called say "keywords" and add "python" to the list, for any of records that have "“file I/O”, “regex”, etc
In the interface you linked, I suppose I’m looking for an AND search rather than OR.
This is also possible. The brand filter uses this refinementList widget which does an OR by default. You can change it to AND like this: https://www.algolia.com/doc/api-reference/widgets/refinement-list/js/#widget-param-operator
d
Right, that would work. Fully curated content. Re: link, ah, perfect. I am very experienced on the backend side of things but relatively new to JS frontends+packaging. What would you recommend I learn next to build this? Also, the example above is a full app; is this something I can embed inside an existing app (e.g. Flask, not React)?
Though this is a complex question - I don’t want to take too much time from your evening! I saw that you have office hours. I can book something there if that’s better.
j
Yup, you can use regular <script> tags to load instantsearch like this: https://www.algolia.com/doc/guides/building-search-ui/installation/js/#directly-in-your-page You can also load the instantsearch adapter to get those widgets to work with Typesense via a script tag like this: https://github.com/typesense/typesense-instantsearch-adapter#installation And after that, you can just use regular JS inside script tags in your html views to load the widgets
d
Ah, I think that’s exactly what I was looking for. Are these widgets pure JS or part of a framework? I suppose I can start by looking at the source of one.
j
The widgets come as part of instantsearch.js and are standalone, so you don't have to install any other frameworks
d
Thanks, that’s really helpful. I will dive into the documentation and see how far I get. Thank you so much for your time and help, I really appreciate it!
j
Happy to help!
CC: @Helin Cao ^
h
Happy to help another Python developer to build frontend! @Demitri Muna, this is a demo app made purely by Python (~100 lines of code): https://demo.pyweb.io/typesense/app/ let me know if you’re interested in its implementation details.
d
Hi @Helin Cao - sorry, I only just saw your reply! Thanks so much for the pointer. I’d definitely be interested in hearing more about the implementation.
h
@Demitri Muna sorry for not checking messages for a long time…. Will send it over.
d
@Helin Cao Thanks! I appreciate it.
h
@Demitri Muna here is the source code: https://github.com/pywebio/demos/blob/main/typesense_search_app.py You can either try it on localhost (after
pip install pywebio
), or run it on build.pyweb.io. Note that the live demo does not work anymore as my Typesense cloud usage has exceeded the free tier limit. I should probably ask for a complete free account for this demo purpose (cc @Jason Bosco 🙂 )
j
@Helin Cao Feel free to use the endpoints and API key used in this demo https://recipe-search.typesense.org/. You should be able to get it from the network tab
🙌 1
h
Thanks to Jason’s support, the live demo is back up online with full search and create feature now: https://demo.pyweb.io/live/typesense_search/app/ . 🎉