diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.css b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.css new file mode 100644 index 000000000000..4a7b8ff8058c --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.css @@ -0,0 +1,4 @@ +:host .align-bottom.dx-toolbar-item { + vertical-align: bottom; +} + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.html b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.html new file mode 100644 index 000000000000..4ec9bfddc3e3 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.html @@ -0,0 +1,31 @@ + + + + + +
+ +
+
+ +
+ + + +
diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.ts b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.ts new file mode 100644 index 000000000000..8c83cad4c5e2 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/app/app.component.ts @@ -0,0 +1,78 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core'; +import { DxDataGridModule, type DxDataGridTypes } from 'devextreme-angular/ui/data-grid'; +import { DxNumberBoxModule, type DxNumberBoxTypes } from 'devextreme-angular/ui/number-box'; +import DataSource from 'devextreme/data/data_source'; +import * as AspNetData from 'devextreme-aspnet-data-nojquery'; + +if (!/localhost/.test(document.location.host)) { + enableProdMode(); +} + +let modulePrefix = ''; +// @ts-ignore +if (window && window.config?.packageConfigPaths) { + modulePrefix = '/app'; +} + +const url = 'https://js.devexpress.com/Demos/NetCore/api/DataGridSemanticSearch'; + +@Component({ + selector: 'demo-app', + templateUrl: `.${modulePrefix}/app.component.html`, + styleUrls: [`.${modulePrefix}/app.component.css`], + imports: [ + DxDataGridModule, + DxNumberBoxModule, + ], +}) +export class AppComponent { + searchValue = ''; + + similarityFactor = 0.31; + + dataSource: DataSource; + + constructor() { + this.dataSource = new DataSource({ + store: AspNetData.createStore({ + key: 'ID', + loadUrl: `${url}/Get`, + loadParams: { + searchValue: () => this.searchValue, + similarityFactor: () => this.similarityFactor, + }, + onBeforeSend: (method, ajaxOptions) => { + ajaxOptions.xhrFields = { withCredentials: true }; + }, + }) + }); + } + + onSimilarityFactorChanged(e: DxNumberBoxTypes.ValueChangedEvent): void { + this.similarityFactor = e.value; + if (this.searchValue !== '') { + this.dataSource.reload(); + } + } + + onEditorPreparing(e: DxDataGridTypes.EditorPreparingEvent): void { + if (e.parentType === 'searchPanel') { + let searchTimeout: ReturnType | undefined; + e.editorOptions.onValueChanged = (args: { value: string }) => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + this.searchValue = args.value; + this.dataSource.reload(); + }, e.updateValueTimeout); + }; + e.editorOptions.placeholder = 'Try: clothing'; + } + } +} + +bootstrapApplication(AppComponent, { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), + ], +}); diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Angular/index.html b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/index.html new file mode 100644 index 000000000000..1ab1fb54a1df --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Angular/index.html @@ -0,0 +1,26 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + + + +
+ Loading... +
+ + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/React/App.tsx b/apps/demos/Demos/DataGrid/SemanticSearch/React/App.tsx new file mode 100644 index 000000000000..88816005df69 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/React/App.tsx @@ -0,0 +1,80 @@ +import React, { useCallback, useRef, useMemo } from 'react'; +import DataGrid, { Column, Scrolling, SearchPanel, Toolbar, Item, type DataGridTypes } from 'devextreme-react/data-grid'; +import NumberBox, { type NumberBoxTypes } from 'devextreme-react/number-box'; +import DataSource from 'devextreme/data/data_source'; +import { createStore } from 'devextreme-aspnet-data-nojquery'; + +const url = 'https://js.devexpress.com/Demos/NetCore/api/DataGridSemanticSearch'; + +const App = () => { + const searchValueRef = useRef(''); + const similarityFactorRef = useRef(0.31); + + const dataSource = useMemo(() => new DataSource({ + store: createStore({ + key: 'ID', + loadUrl: `${url}/Get`, + loadParams: { + searchValue: () => searchValueRef.current, + similarityFactor: () => similarityFactorRef.current, + }, + onBeforeSend(method, ajaxOptions) { + ajaxOptions.xhrFields = { withCredentials: true }; + }, + }) + }), []); + + const onSimilarityFactorChanged = useCallback(({ value }: NumberBoxTypes.ValueChangedEvent) => { + similarityFactorRef.current = value; + if (searchValueRef.current !== '') { + dataSource.reload(); + } + }, []); + + const onEditorPreparing = useCallback((e: DataGridTypes.EditorPreparingEvent) => { + if (e.parentType === 'searchPanel') { + let searchTimeout: ReturnType | undefined; + e.editorOptions.onValueChanged = (args: { value: string }) => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + searchValueRef.current = args.value; + e.component.getDataSource().reload(); + }, e.updateValueTimeout); + }; + e.editorOptions.placeholder = 'Try: clothing'; + } + }, []); + + return ( + + + + + + + + + + + + + + ); +}; + +export default App; diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/React/index.html b/apps/demos/Demos/DataGrid/SemanticSearch/React/index.html new file mode 100644 index 000000000000..ee451f8288ff --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/React/index.html @@ -0,0 +1,24 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/React/index.tsx b/apps/demos/Demos/DataGrid/SemanticSearch/React/index.tsx new file mode 100644 index 000000000000..8acbec4b6179 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/React/index.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './App.tsx'; + +ReactDOM.render( + , + document.getElementById('app'), +); diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/React/styles.css b/apps/demos/Demos/DataGrid/SemanticSearch/React/styles.css new file mode 100644 index 000000000000..49409f518a7b --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/React/styles.css @@ -0,0 +1,3 @@ +.align-bottom.dx-toolbar-item { + vertical-align: bottom; +} diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/App.js b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/App.js new file mode 100644 index 000000000000..3784c7084244 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/App.js @@ -0,0 +1,99 @@ +import React, { useCallback, useRef, useMemo } from 'react'; +import DataGrid, { + Column, + Scrolling, + SearchPanel, + Toolbar, + Item, +} from 'devextreme-react/data-grid'; +import NumberBox from 'devextreme-react/number-box'; +import DataSource from 'devextreme/data/data_source'; +import { createStore } from 'devextreme-aspnet-data-nojquery'; + +const url = 'https://js.devexpress.com/Demos/NetCore/api/DataGridSemanticSearch'; +const App = () => { + const searchValueRef = useRef(''); + const similarityFactorRef = useRef(0.31); + const dataSource = useMemo( + () => + new DataSource({ + store: createStore({ + key: 'ID', + loadUrl: `${url}/Get`, + loadParams: { + searchValue: () => searchValueRef.current, + similarityFactor: () => similarityFactorRef.current, + }, + onBeforeSend(method, ajaxOptions) { + ajaxOptions.xhrFields = { withCredentials: true }; + }, + }), + }), + [], + ); + const onSimilarityFactorChanged = useCallback(({ value }) => { + similarityFactorRef.current = value; + if (searchValueRef.current !== '') { + dataSource.reload(); + } + }, []); + const onEditorPreparing = useCallback((e) => { + if (e.parentType === 'searchPanel') { + let searchTimeout; + e.editorOptions.onValueChanged = (args) => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + searchValueRef.current = args.value; + e.component.getDataSource().reload(); + }, e.updateValueTimeout); + }; + e.editorOptions.placeholder = 'Try: clothing'; + } + }, []); + return ( + + + + + + + + + + + + + + ); +}; +export default App; diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/index.html b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/index.html new file mode 100644 index 000000000000..db31b0fd60c6 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/index.html @@ -0,0 +1,44 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/index.js b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/index.js new file mode 100644 index 000000000000..b853e0be8242 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/index.js @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App.js'; + +ReactDOM.render(, document.getElementById('app')); diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/styles.css b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/styles.css new file mode 100644 index 000000000000..49409f518a7b --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/ReactJs/styles.css @@ -0,0 +1,3 @@ +.align-bottom.dx-toolbar-item { + vertical-align: bottom; +} diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Vue/App.vue b/apps/demos/Demos/DataGrid/SemanticSearch/Vue/App.vue new file mode 100644 index 000000000000..64fd7e83915b --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Vue/App.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Vue/index.html b/apps/demos/Demos/DataGrid/SemanticSearch/Vue/index.html new file mode 100644 index 000000000000..b21c5e373a7a --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Vue/index.html @@ -0,0 +1,29 @@ + + + + DevExtreme Demo + + + + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/Vue/index.ts b/apps/demos/Demos/DataGrid/SemanticSearch/Vue/index.ts new file mode 100644 index 000000000000..684d04215d72 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/Vue/index.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue'; +import App from './App.vue'; + +createApp(App).mount('#app'); diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/index.html b/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/index.html new file mode 100644 index 000000000000..5bfc9fb29a7c --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/index.html @@ -0,0 +1,20 @@ + + + + DevExtreme Demo + + + + + + + + + + + +
+
+
+ + diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/index.js b/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/index.js new file mode 100644 index 000000000000..2dd62a5bcb18 --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/index.js @@ -0,0 +1,74 @@ +$(() => { + 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: ['ID', 'Name', 'Description'], + scrolling: { + mode: 'virtual', + }, + searchPanel: { + visible: true, + }, + toolbar: { + items: [ + { + location: 'after', + cssClass: 'align-bottom', + template: function (data, container) { + $('
') + .appendTo(container) + .dxNumberBox({ + label: 'Similarity Factor', + labelMode: 'floating', + value: similarityFactor, + min: 0, + max: 1, + format: '0.00', + step: 0.05, + onValueChanged(e) { + similarityFactor = e.value; + if (searchValue !== '') { + dataGrid.getDataSource().reload(); + } + }, + }); + } + }, + { + name: 'searchPanel', + cssClass: 'align-bottom', + }, + ], + }, + 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'); +}); diff --git a/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/styles.css b/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/styles.css new file mode 100644 index 000000000000..49409f518a7b --- /dev/null +++ b/apps/demos/Demos/DataGrid/SemanticSearch/jQuery/styles.css @@ -0,0 +1,3 @@ +.align-bottom.dx-toolbar-item { + vertical-align: bottom; +} diff --git a/apps/demos/menuMeta.json b/apps/demos/menuMeta.json index bb9175e74ef8..ccc66e33ea50 100644 --- a/apps/demos/menuMeta.json +++ b/apps/demos/menuMeta.json @@ -38,6 +38,29 @@ "Modules": "openai" } ] + }, + { + "Name": "Semantic Search", + "Equivalents": "", + "Demos": [ + { + "Title": "Semantic Search", + "Name": "SemanticSearch", + "Widget": "DataGrid", + "Badge": "AI", + "DemoType": "Web", + "Modules": "devextreme-aspnet-data-nojquery", + "MvcAdditionalFiles": [ + "/DataProviders/SmartFilterProvider.cs", + "/Controllers/ApiControllers/DataGridSemanticSearchController.cs", + "/Services/EmbeddingsInitializerService.cs", + "/Models/DataGrid/DictionaryItem.cs", + "/Models/SampleData/DictionaryItems.cs" + ], + "TopLevel": true, + "Equivalents": "AI-Powered Search, Smart Search, Context-Aware Search, Natural Language Search, AI Search, Semantic Retrieval, Similarity Search" + } + ] } ] }, @@ -315,6 +338,22 @@ "/Models/SampleData/DataGridEmployees.cs" ], "DemoType": "Web" + }, + { + "Title": "Semantic Search", + "Name": "SemanticSearch", + "Widget": "DataGrid", + "Badge": "AI", + "DemoType": "Web", + "Modules": "devextreme-aspnet-data-nojquery", + "MvcAdditionalFiles": [ + "/DataProviders/SmartFilterProvider.cs", + "/Controllers/ApiControllers/DataGridSemanticSearchController.cs", + "/Services/EmbeddingsInitializerService.cs", + "/Models/DataGrid/DictionaryItem.cs", + "/Models/SampleData/DictionaryItems.cs" + ], + "Equivalents": "AI-Powered Search, Smart Search, Context-Aware Search, Natural Language Search, AI Search, Semantic Retrieval, Similarity Search" } ] },