DevExtreme v26.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

JavaScript/jQuery Data Grid - Semantic Search

This demo incorporates server-side semantic search into the DevExtreme JavaScript DataGrid. Semantic search finds results based on meaning rather than exact wording, understanding the context and intent behind a question or phrase. This allows your DevExtreme-powered app to deliver more relevant answers by connecting related concepts, even if exact words differ.

To review benefits of this feature, search for dictionary entries and their descriptions and use synonyms or generic descriptions instead of exact search strings (such as "clothing" instead of a specific product name). You can fine-tune search results: use the Similarity Factor editor to change the search precision.

Backend API
$(() => { const url = 'https://js.devexpress.com/Demos/NetCore/api/DataGridSemanticSearch'; let searchValue = ''; let similarityFactor = 0.31; const dataGrid = $('#gridContainer').dxDataGrid({ dataSource: DevExpress.data.AspNet.createStore({ key: 'ID', loadUrl: `${url}/Get`, loadParams: { searchValue: () => searchValue, similarityFactor: () => similarityFactor, }, onBeforeSend(method, ajaxOptions) { ajaxOptions.xhrFields = { withCredentials: true }; }, }), showBorders: true, remoteOperations: true, height: 600, columns: [{ dataField: 'ID', width: 50, }, { dataField: 'Name', }, { dataField: 'Description', }], scrolling: { mode: 'virtual', }, searchPanel: { visible: true, }, toolbar: { items: [ { location: 'before', template() { const container = $('<div>') .css('display', 'flex') .css('align-items', 'center'); const label = $('<span>') .text('Similarity Factor:') .css('margin-right', '8px'); const editor = $('<div>') .dxNumberBox({ value: similarityFactor, min: 0, max: 1, format: '0.00', step: 0.05, showSpinButtons: true, inputAttr: { 'aria-label': 'Similarity Factor' }, onValueChanged(e) { similarityFactor = e.value; if (searchValue !== '') { dataGrid.getDataSource().reload(); } }, }); return container.append(label, editor); }, }, { name: 'searchPanel', }, ], }, onEditorPreparing(e) { if (e.parentType === 'searchPanel') { let searchTimeout; e.editorOptions.onValueChanged = (args) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { searchValue = args.value; e.component.getDataSource().reload(); }, e.updateValueTimeout); }; e.editorOptions.placeholder = 'Try: clothing'; } }, }).dxDataGrid('instance'); });
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script>window.jQuery || document.write(decodeURIComponent('%3Cscript src="js/jquery.min.js"%3E%3C/script%3E'))</script> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/26.1.3/css/dx.light.css" /> <script src="js/dx.all.js?v=26.1.3"></script> <script src="https://unpkg.com/devextreme-aspnet-data@5.0.0/js/dx.aspnet.data.js"></script> <script src="index.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" /> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="gridContainer"></div> </div> </body> </html>
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Numerics.Tensors; using System.Threading.Tasks; using Azure.AI.OpenAI; using Microsoft.Extensions.AI; namespace DevExtreme.MVC.Demos.DataProviders { public class SmartFilterProvider { readonly IEmbeddingGenerator<string, Embedding<float>> Embedder; readonly static ConcurrentDictionary<string, Embedding<float>> cache = new ConcurrentDictionary<string, Embedding<float>>(StringComparer.OrdinalIgnoreCase); public SmartFilterProvider(AzureOpenAIClient openAIClient) { Embedder = openAIClient.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator(); } public async Task FillCacheAsync(IEnumerable<string> words) { try { var nonCachedWords = words.Where(x => !cache.ContainsKey(x)).ToArray(); if(!nonCachedWords.Any()) return; var embeddings = await Embedder.GenerateAsync(nonCachedWords); foreach(var (word, embedding) in nonCachedWords.Zip(embeddings, (word, embedding) => (word, embedding))) { cache[word] = embedding; } } catch { } } public float GetSimilarity(string text, string searchText) { var eFilter = cache[text]; var eText = cache[searchText]; var cosineSimilarity = TensorPrimitives.CosineSimilarity(eText.Vector.Span, eFilter.Vector.Span); return cosineSimilarity; } } }
using DevExtreme.AspNet.Data; using DevExtreme.AspNet.Mvc; using DevExtreme.MVC.Demos.DataProviders; using DevExtreme.MVC.Demos.Models.DataGrid; using DevExtreme.MVC.Demos.Models.SampleData; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web.Http; namespace DevExtreme.MVC.Demos.Controllers.ApiControllers { public class DataGridSemanticSearchController : ApiController { SmartFilterProvider smartFilter; public DataGridSemanticSearchController(SmartFilterProvider smartFilterProvider) { smartFilter = smartFilterProvider; } [HttpGet] public async Task<object> Get(string searchValue, float similarityFactor, DataSourceLoadOptions loadOptions) { if(string.IsNullOrEmpty(searchValue)) { return DataSourceLoader.Load(SampleData.DictionaryItems, loadOptions); } await smartFilter.FillCacheAsync(new[] { searchValue }); var result = Search(searchValue, similarityFactor); return DataSourceLoader.Load(result, loadOptions); } IEnumerable<DictionaryItem> Search(string searchValue, float similarityFactor = 0.31f) { return SampleData.DictionaryItems .Select(item => new { Entry = item, Similarity = smartFilter.GetSimilarity(DictionaryItem.GetItemDescription(item), searchValue) }) .OrderByDescending(x => x.Similarity) .Where(x => x.Similarity > similarityFactor) .Select(x => x.Entry); } } }
using DevExtreme.MVC.Demos.DataProviders; using DevExtreme.MVC.Demos.Models.DataGrid; using DevExtreme.MVC.Demos.Models.SampleData; using Microsoft.Extensions.Hosting; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace DevExtreme.MVC.Demos.Services { public class EmbeddingsInitializerService : IHostedService { readonly SmartFilterProvider smartFilter; public EmbeddingsInitializerService(SmartFilterProvider smartFilterProvider) { smartFilter = smartFilterProvider; } public Task StartAsync(CancellationToken cancellationToken) { return smartFilter.FillCacheAsync(SampleData.DictionaryItems.Select(DictionaryItem.GetItemDescription)); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } }
namespace DevExtreme.MVC.Demos.Models.DataGrid { public class DictionaryItem { public DictionaryItem(int id, string name, string description) { ID = id; Name = name; Description = description; } public int ID { get; set; } public string Name { get; set; } public string Description { get; set; } public static string GetItemDescription(DictionaryItem item) { return $"{item.Name} - {item.Description}"; } } }

AI services used for this demo have been rate and data limited. As such, you may experience performance-related delays when exploring the capabilities of the JavaScript DataGrid AI Assistant.

When connected to your own AI model/service without rate and data limits, semantic search will perform seamlessly, without artificial delays. Note that DevExtreme does not offer an AI REST API and does not ship any built-in LLMs/SLMs.

This demo configures semantic filtering on the server (using AzureOpenAI embeddings). You can incorporate server-side semantic search using built-in JavaScript DataGrid visual elements. In this demo, each request returns filtered data using two parameters:

  • A search value (entered in the built-in search panel)
  • A similarity factor (can be modified via a custom toolbar item)

After you enter a query in the search panel, the DevExtreme JavaScript DataGrid passes the new search value to the backend and loads the filtered data set.