diff --git a/bundle.js b/bundle.js index 4a40a1c..51f07ef 100644 --- a/bundle.js +++ b/bundle.js @@ -16,7 +16,7 @@ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _xenova_transformers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @xenova/transformers */ \"./node_modules/@xenova/transformers/src/transformers.js\");\n/* harmony import */ var ag_grid_community__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ag-grid-community */ \"./node_modules/ag-grid-community/dist/ag-grid-community.auto.esm.js\");\n/* harmony import */ var xlsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! xlsx */ \"./node_modules/xlsx/xlsx.mjs\");\n/* harmony import */ var ag_grid_community_styles_ag_grid_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ag-grid-community/styles/ag-grid.css */ \"./node_modules/ag-grid-community/styles/ag-grid.css\");\n/* harmony import */ var ag_grid_community_styles_ag_theme_quartz_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ag-grid-community/styles/ag-theme-quartz.css */ \"./node_modules/ag-grid-community/styles/ag-theme-quartz.css\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst loadingElement = document.getElementById(\"loading\");\r\nconst submitButton = document.getElementById(\"submit_button\");\r\nconst submit_button_text = document.getElementById(\"submit_button_text\")\r\nconst filterTextBox = document.getElementById('filter-text-box');\r\nconst eGridDiv = document.querySelector('#myGrid');\r\n\r\nlet searchResults;\r\nlet embedder;\r\nlet loadedModelName = null;\r\nlet quantizedFlag = true;\r\nlet thisCollection;\r\nlet lastCollection = \"\";\r\n\r\n// Function to update URL with form parameters\r\nfunction updateURL() {\r\n const qdrantURL = document.getElementById('QdrantURL').value;\r\n const inputText = document.getElementById('inputText').value;\r\n const qdrantLimit = document.getElementById('QdrantLimit').value;\r\n const hfModel = document.getElementById('HFModel').value;\r\n const quantizedToggle = document.getElementById('quantizedToggle').checked;\r\n\r\n const params = new URLSearchParams({\r\n qdrantURL,\r\n inputText,\r\n qdrantLimit,\r\n hfModel,\r\n quantizedToggle\r\n });\r\n\r\n window.history.replaceState({}, '', `?${params}`);\r\n}\r\n\r\n// Function to set form input fields based on URL parameters\r\nfunction setFormInputsFromURL() {\r\n const urlParams = new URLSearchParams(window.location.search);\r\n\r\n // Check if there are any parameters in the URL\r\n if (urlParams.toString() === \"\") {\r\n return; // If no parameters, do nothing\r\n }\r\n\r\n // Update form inputs with URL parameters\r\n const qdrantURLParam = urlParams.get('qdrantURL');\r\n document.getElementById('QdrantURL').value = qdrantURLParam || '';\r\n\r\n const inputTextParam = urlParams.get('inputText');\r\n document.getElementById('inputText').value = inputTextParam || '';\r\n\r\n const qdrantLimitParam = urlParams.get('qdrantLimit');\r\n document.getElementById('QdrantLimit').value = qdrantLimitParam || '';\r\n\r\n const hfModelParam = urlParams.get('hfModel');\r\n document.getElementById('HFModel').value = hfModelParam || '';\r\n\r\n const quantizedToggleParam = urlParams.get('quantizedToggle');\r\n document.getElementById('quantizedToggle').checked = quantizedToggleParam === 'true';\r\n}\r\n\r\n\r\nvar URLModeHidden = document.getElementById(\"copyURLButton\").hidden;\r\n\r\nif (URLModeHidden) {\r\n\r\n} else {\r\n // Call the function initially to set form inputs from URL parameters\r\n setFormInputsFromURL();\r\n\r\n // Add event listeners to form inputs to update URL\r\n const formInputs = document.querySelectorAll('.form-control, .form-check-input');\r\n formInputs.forEach(input => {\r\n input.addEventListener('input', updateURL);\r\n });\r\n}\r\n\r\nasync function loadModel(model, quantized = true) {\r\n if (model !== loadedModelName || quantized !== quantizedFlag) { // Check if model or quantized flag changed\r\n submitButton.setAttribute(\"disabled\", \"\");\r\n loadingElement.style.display = \"\";\r\n submit_button_text.textContent = \"Loading model...\";\r\n\r\n embedder = await (0,_xenova_transformers__WEBPACK_IMPORTED_MODULE_0__.pipeline)(\"feature-extraction\", model, { quantized: quantized });\r\n loadedModelName = model;\r\n quantizedFlag = quantized; // Update quantized flag\r\n console.log(\"Model loaded:\", loadedModelName, \" quantized: \", quantized);\r\n } else {\r\n console.log(\"Model already loaded:\", loadedModelName, \" quantized: \", quantized);\r\n }\r\n}\r\n\r\nsubmitButton.onclick = () => {\r\n const modelName = document.getElementById(\"HFModel\").value;\r\n const quantized = document.getElementById(\"quantizedToggle\").checked;\r\n loadModel(modelName, quantized).then(() => {\r\n sendRequest();\r\n });\r\n};\r\n\r\ndownload_csv.onclick = () => {\r\n exportData(searchResults, \"csv\");\r\n};\r\n\r\ndownload_xlsx.onclick = () => {\r\n exportData(searchResults, \"excel\");\r\n};\r\n\r\ndownload_json.onclick = () => {\r\n exportData(searchResults, \"json\");\r\n};\r\n\r\n////////////////////////////////////////////////////////////////////\r\n\r\nasync function searchPoints(collectionName, vectorData, filter, limit, offset, withPayload, withVector, scoreThreshold) {\r\n var reqBody = JSON.stringify({\r\n vector: vectorData,\r\n filter: filter,\r\n limit: limit,\r\n offset: offset,\r\n with_payload: withPayload,\r\n with_vector: withVector,\r\n score_threshold: scoreThreshold,\r\n });\r\n\r\n const requestOptions = {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: reqBody,\r\n };\r\n\r\n thisCollection = document.getElementById(\"QdrantURL\").value\r\n const response = await fetch(`${thisCollection}/points/search`, requestOptions);\r\n const data = await response.json();\r\n\r\n return data;\r\n}\r\n\r\n// Define global variables for the grid API and options\r\nlet gridApi;\r\nlet gridOptions;\r\n\r\n\r\nasync function sendRequest() {\r\n try {\r\n gridApi.showLoadingOverlay();\r\n }\r\n catch (error) {\r\n }\r\n loadingElement.style.display = \"\";\r\n submit_button_text.textContent = \"Loading results...\";\r\n submitButton.setAttribute(\"disabled\", \"\");\r\n\r\n let inputText = document.getElementById(\"inputText\").value.trim();\r\n if (inputText !== \"\") {\r\n let output = await embedder(inputText, { pooling: 'mean', normalize: true });\r\n const collectionName = \"test_collection\";\r\n var vectorData = Array.from(output[\"data\"]);\r\n const filter = {};\r\n const limit = parseInt(document.getElementById(\"QdrantLimit\").value);\r\n const offset = 0;\r\n const withPayload = true;\r\n const withVector = false;\r\n const scoreThreshold = null;\r\n\r\n try {\r\n\r\n searchResults = await searchPoints(collectionName, vectorData, filter, limit, offset, withPayload, withVector, scoreThreshold);\r\n //console.log(searchResults);\r\n\r\n // Extract payload keys\r\n const payloadKeys = Object.keys(searchResults.result[0].payload);\r\n\r\n function isHyperlink(value) {\r\n return /^https?:\\/\\//.test(value);\r\n }\r\n\r\n // Custom cell renderer\r\n function customRenderer(params) {\r\n const nestedKey = Object.keys(params.data.payload).find(key => typeof params.data.payload[key] === 'object');\r\n const value = params.data.payload[params.colDef.field.split('.')[1]];\r\n\r\n if (params.colDef.field.endsWith(`.${nestedKey}`)) {\r\n return typeof value === 'object' ? JSON.stringify(value) : value; // Render nested element as string\r\n } else if (isHyperlink(value)) {\r\n return `${value}`;\r\n } else {\r\n return value;\r\n }\r\n }\r\n\r\n // Update your column definition\r\n const columnDefs = [\r\n { headerName: 'id', field: 'id' },\r\n { headerName: 'score', field: 'score' },\r\n ...payloadKeys.map(key => ({\r\n headerName: key,\r\n field: `payload.${key}`,\r\n maxWidth: 300,\r\n editable: true,\r\n valueGetter: params => params.data.payload[key],\r\n tooltipValueGetter: (params) => params.value,\r\n filter: true,\r\n autoHeight: true,\r\n cellRenderer: customRenderer // Use the custom cell renderer\r\n })),\r\n ];\r\n\r\n\r\n // Check if the grid has already been initialized\r\n if (gridApi && thisCollection === lastCollection) {\r\n // If the grid is already initialized, update the row data\r\n gridApi.setRowData(searchResults.result);\r\n } else {\r\n\r\n try {\r\n //gridOptions.api.destroy()\r\n //document.getElementById(\"myGrid\").innerHTML = \"\";\r\n if (thisCollection !== lastCollection) {\r\n // update column headers if needed\r\n gridApi.updateGridOptions({ columnDefs: columnDefs })\r\n }\r\n gridApi.setRowData(searchResults.result);\r\n loadingElement.style.display = \"none\";\r\n submit_button_text.textContent = \"Submit\";\r\n submitButton.removeAttribute(\"disabled\");\r\n\r\n lastCollection = thisCollection\r\n return\r\n }\r\n catch { }\r\n\r\n // If the grid is not initialized, create the grid\r\n gridOptions = {\r\n autoSizeStrategy: {\r\n type: 'fitCellContents'\r\n },\r\n domLayout: 'autoHeight', // Add this line to enable auto height\r\n columnDefs: columnDefs,\r\n rowData: searchResults.result,\r\n tooltipShowDelay: 0,\r\n overlayLoadingTemplate:\r\n '
',\r\n overlayNoRowsTemplate:\r\n 'This is a custom \\'no rows\\' overlay',\r\n\r\n };\r\n\r\n gridApi = (0,ag_grid_community__WEBPACK_IMPORTED_MODULE_1__.createGrid)(eGridDiv, gridOptions);\r\n document.getElementById(\"exportDropdown\").removeAttribute(\"disabled\")\r\n document.getElementById(\"quickFilter\").style.display = \"\";\r\n\r\n }\r\n\r\n loadingElement.style.display = \"none\";\r\n submit_button_text.textContent = \"Submit\";\r\n submitButton.removeAttribute(\"disabled\");\r\n\r\n // on first click add the quick filter listener\r\n if (lastCollection == \"\" && !filterTextBox._listenerInitialized) {\r\n function onFilterTextBoxChanged() {\r\n gridApi.setGridOption(\r\n 'quickFilterText',\r\n document.getElementById('filter-text-box').value\r\n );\r\n }\r\n\r\n filterTextBox.addEventListener('input', () => {\r\n onFilterTextBoxChanged();\r\n });\r\n\r\n // Mark listener as initialized\r\n filterTextBox._listenerInitialized = true;\r\n console.log(\"filter init\")\r\n }\r\n\r\n\r\n lastCollection = thisCollection\r\n\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n}\r\n\r\nasync function exportData(data, format) {\r\n // Function to flatten the payload object within each object\r\n function flattenPayload(jsonData) {\r\n // Check if jsonData is an array\r\n if (!Array.isArray(jsonData)) {\r\n console.error('jsonData is not an array');\r\n return jsonData;\r\n }\r\n\r\n // Check if every element in the array is an object\r\n if (!jsonData.every(item => typeof item === 'object')) {\r\n console.error('One or more elements in jsonData are not objects');\r\n return jsonData;\r\n }\r\n\r\n // Map over the array and flatten the payload object\r\n return jsonData.map(item => {\r\n const { payload, ...rest } = item;\r\n return { ...rest, ...payload };\r\n });\r\n }\r\n\r\n // Flatten the payload object within each object\r\n let jsonData = flattenPayload(data.result);\r\n\r\n // Based on the format parameter, generate the output accordingly\r\n if (format === 'excel') {\r\n // Create a new workbook\r\n const workbook = xlsx__WEBPACK_IMPORTED_MODULE_4__.utils.book_new();\r\n\r\n // Convert JSON to worksheet\r\n const worksheet = xlsx__WEBPACK_IMPORTED_MODULE_4__.utils.json_to_sheet(jsonData);\r\n\r\n // Add the worksheet to the workbook\r\n xlsx__WEBPACK_IMPORTED_MODULE_4__.utils.book_append_sheet(workbook, worksheet, 'Sheet1');\r\n\r\n // Generate a blob from the workbook\r\n const excelBlob = new Blob([xlsx__WEBPACK_IMPORTED_MODULE_4__.write(workbook, { type: 'array', bookType: 'xlsx' })], {\r\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\r\n });\r\n\r\n // Create a temporary URL for the blob\r\n const excelUrl = URL.createObjectURL(excelBlob);\r\n\r\n // Create a link element\r\n const link = document.createElement('a');\r\n link.href = excelUrl;\r\n link.download = `${document.getElementById(\"inputText\").value.trim()}.xlsx`;\r\n\r\n // Append the link to the document body and trigger the download\r\n document.body.appendChild(link);\r\n link.click();\r\n\r\n // Clean up\r\n URL.revokeObjectURL(excelUrl);\r\n document.body.removeChild(link);\r\n } else if (format === 'csv') {\r\n // Convert JSON to CSV\r\n const csvContent = jsonData.map(row => {\r\n return Object.values(row).map(value => {\r\n if (typeof value === 'string') {\r\n // Escape double quotes within the value and enclose it in double quotes\r\n return '\"' + value.replace(/\"/g, '\"\"') + '\"';\r\n }\r\n return value;\r\n }).join(',');\r\n }).join('\\n');\r\n\r\n // Create a blob from the CSV content\r\n const csvBlob = new Blob([csvContent], { type: 'text/csv' });\r\n const csvUrl = URL.createObjectURL(csvBlob);\r\n\r\n // Create a link element\r\n const link = document.createElement('a');\r\n link.href = csvUrl;\r\n link.download = `${document.getElementById(\"inputText\").value.trim()}.csv`;\r\n\r\n // Append the link to the document body and trigger the download\r\n document.body.appendChild(link);\r\n link.click();\r\n\r\n // Clean up\r\n URL.revokeObjectURL(csvUrl);\r\n document.body.removeChild(link);\r\n\r\n\r\n } else if (format === 'json') {\r\n // Convert JSON to string\r\n const jsonString = JSON.stringify(jsonData, null, 2);\r\n\r\n // Create a blob from the JSON string\r\n const jsonBlob = new Blob([jsonString], { type: 'application/json' });\r\n\r\n // Create a temporary URL for the blob\r\n const jsonUrl = URL.createObjectURL(jsonBlob);\r\n\r\n // Create a link element\r\n const link = document.createElement('a');\r\n link.href = jsonUrl;\r\n link.download = `${document.getElementById(\"inputText\").value.trim()}.json`;\r\n\r\n // Append the link to the document body and trigger the download\r\n document.body.appendChild(link);\r\n link.click();\r\n\r\n // Clean up\r\n URL.revokeObjectURL(jsonUrl);\r\n document.body.removeChild(link);\r\n } else {\r\n console.error('Unsupported format');\r\n }\r\n}\r\n\r\ndocument.getElementById('copyURLButton').addEventListener('click', function () {\r\n var urlToCopy = window.location.href;\r\n\r\n navigator.clipboard.writeText(urlToCopy)\r\n .then(function () {\r\n console.log('URL copied to clipboard successfully: ' + urlToCopy);\r\n // You can also show a success message here if needed\r\n })\r\n .catch(function (err) {\r\n console.error('Failed to copy URL to clipboard: ', err);\r\n // You can also show an error message here if needed\r\n });\r\n});\r\n\n\n//# sourceURL=webpack://qdrant-frontend/./index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _xenova_transformers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @xenova/transformers */ \"./node_modules/@xenova/transformers/src/transformers.js\");\n/* harmony import */ var ag_grid_community__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ag-grid-community */ \"./node_modules/ag-grid-community/dist/ag-grid-community.auto.esm.js\");\n/* harmony import */ var xlsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! xlsx */ \"./node_modules/xlsx/xlsx.mjs\");\n/* harmony import */ var ag_grid_community_styles_ag_grid_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ag-grid-community/styles/ag-grid.css */ \"./node_modules/ag-grid-community/styles/ag-grid.css\");\n/* harmony import */ var ag_grid_community_styles_ag_theme_quartz_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ag-grid-community/styles/ag-theme-quartz.css */ \"./node_modules/ag-grid-community/styles/ag-theme-quartz.css\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst loadingElement = document.getElementById(\"loading\");\r\nconst submitButton = document.getElementById(\"submit_button\");\r\nconst submit_button_text = document.getElementById(\"submit_button_text\")\r\nconst filterTextBox = document.getElementById('filter-text-box');\r\nconst eGridDiv = document.querySelector('#myGrid');\r\n\r\nlet searchResults;\r\nlet embedder;\r\nlet loadedModelName = null;\r\nlet quantizedFlag = true;\r\nlet thisCollection;\r\nlet lastCollection = \"\";\r\n\r\nconst special_vector = [] // A special vector can be hardcoded here, so that instead of calculating it with the model, this vector is used. Will be displayed bold. \r\n\r\n// Function to update URL with form parameters\r\nfunction updateURL() {\r\n const qdrantURL = document.getElementById('QdrantURL').value;\r\n const qdrantLimit = document.getElementById('QdrantLimit').value;\r\n const hfModel = document.getElementById('HFModel').value;\r\n const quantizedToggle = document.getElementById('quantizedToggle').checked;\r\n\r\n // Select all query containers\r\n const queryContainers = document.querySelectorAll('.queryContainer');\r\n let queries = [];\r\n\r\n queryContainers.forEach((container, index) => {\r\n // Adjusted selectors to match the updated HTML\r\n const inputText = container.querySelector('.inputText').value;\r\n const queryWeight = container.querySelector('.queryWeight').value;\r\n const activeState = container.querySelector(`.activeToggle`).checked;\r\n\r\n // Create an object for each query with its parameters and index\r\n const query = {\r\n index, // Add the index of the query row\r\n inputText,\r\n queryWeight,\r\n activeState\r\n };\r\n\r\n // Add the query object to the queries array\r\n queries.push(query);\r\n });\r\n\r\n // Convert the queries array to a string for the URL parameters\r\n const queriesString = JSON.stringify(queries);\r\n\r\n const params = new URLSearchParams({\r\n qdrantURL,\r\n queries: queriesString, // This now includes the index of each query row\r\n qdrantLimit,\r\n hfModel,\r\n quantizedToggle\r\n });\r\n\r\n window.history.replaceState({}, '', `?${params}`);\r\n}\r\n\r\nfunction setFormInputsFromURL() {\r\n // Parse the current URL\r\n const url = new URL(window.location.href);\r\n const urlParams = url.searchParams;\r\n\r\n // Check if there are any parameters in the URL\r\n if (urlParams.toString() === \"\") {\r\n return; // If no parameters, do nothing\r\n }\r\n\r\n // Update form inputs with URL parameters\r\n const qdrantURLParam = urlParams.get('qdrantURL');\r\n document.getElementById('QdrantURL').value = qdrantURLParam || '';\r\n\r\n const qdrantLimitParam = urlParams.get('qdrantLimit');\r\n document.getElementById('QdrantLimit').value = qdrantLimitParam || '';\r\n\r\n const hfModelParam = urlParams.get('hfModel');\r\n document.getElementById('HFModel').value = hfModelParam || '';\r\n\r\n const quantizedToggleParam = urlParams.get('quantizedToggle');\r\n document.getElementById('quantizedToggle').checked = quantizedToggleParam === 'true';\r\n\r\n // Handle query parameters\r\n const queriesParam = urlParams.get('queries');\r\n if (queriesParam) {\r\n const queries = JSON.parse(queriesParam);\r\n let rowCount = 1; // Reset row count for dynamic rows\r\n\r\n // Directly update the first row if it's part of the queries\r\n if (queries.length > 0 && queries[0].hasOwnProperty('inputText')) {\r\n const firstQuery = queries.shift(); // Remove the first query from the array\r\n\r\n const inputText0 = document.getElementById('inputText0')\r\n inputText0.value = firstQuery.inputText || '';\r\n\r\n if (inputText0.value === \"special_vector\") {\r\n // If the condition is met, apply italic text and grey background\r\n inputText0.style.fontStyle = 'italic';\r\n //event.target.style.backgroundColor = 'grey';\r\n } else {\r\n // If the condition is not met, remove italic text and grey background\r\n inputText0.style.fontStyle = 'normal';\r\n //event.target.style.backgroundColor = '';\r\n }\r\n\r\n document.getElementById('weight0').value = firstQuery.queryWeight || '';\r\n document.getElementById('activeToggle0').checked = firstQuery.activeState;\r\n }\r\n\r\n // Remove existing query rows\r\n //const queryRowsContainer = document.getElementById('queryRowsContainer');\r\n // Assuming you want to clear all existing dynamic rows before adding new ones\r\n //queryRowsContainer.innerHTML = '';\r\n\r\n // Dynamically create query rows based on URL parameters\r\n queries.forEach((query, index) => {\r\n addRow(query, index + 1); // Pass query data and the new row number\r\n });\r\n }\r\n}\r\n\r\n// Function to remove a row\r\nfunction removeRow(rowToRemove) {\r\n rowToRemove.remove();\r\n // Adjust IDs of all remaining rows\r\n let remainingRows = document.querySelectorAll('.queryContainer');\r\n for (let i = 0; i < remainingRows.length; i++) {\r\n remainingRows[i].querySelectorAll('input, button').forEach(function (element) {\r\n const currentId = element.id;\r\n const newId = currentId.replace(/\\d+$/, i + 1); // Adjust the ID to reflect the new row count\r\n element.id = newId;\r\n });\r\n }\r\n}\r\n\r\nfunction addRow(queryData, rowNumber) {\r\n const originalRow = document.getElementById('initialQueryContainer');\r\n const clone = originalRow.cloneNode(true);\r\n clone.id = 'queryContainer' + rowNumber; // Adjust the ID of the cloned row\r\n\r\n // Adjust IDs of all elements within the cloned row\r\n clone.querySelectorAll('input, button').forEach(function (element) {\r\n const currentId = element.id;\r\n const newId = currentId.replace(/\\d+$/, rowNumber); // Replace the last digit(s) with the current rowNumber\r\n element.id = newId;\r\n });\r\n\r\n // Set values from queryData\r\n clone.querySelector('.inputText').value = queryData.inputText || '';\r\n\r\n // Set values from queryData\r\n const inputTextX = clone.querySelector('.inputText')\r\n inputTextX.value = queryData.inputText || '';\r\n\r\n if (inputTextX.value === \"special_vector\") {\r\n // If the condition is met, apply italic text and grey background\r\n inputTextX.style.fontStyle = 'italic';\r\n //event.target.style.backgroundColor = 'grey';\r\n } else {\r\n // If the condition is not met, remove italic text and grey background\r\n inputTextX.style.fontStyle = 'normal';\r\n //event.target.style.backgroundColor = '';\r\n }\r\n\r\n\r\n clone.querySelector('.queryWeight').value = queryData.queryWeight || '';\r\n clone.querySelector('.activeToggle').checked = queryData.activeState;\r\n\r\n const minusButton = clone.querySelector('.queryButton');\r\n // must use SVG here as emoji create problems with npm\r\n minusButton.innerHTML = `\r\n \r\n `\r\n minusButton.title = 'Remove query';\r\n minusButton.addEventListener('click', function () { removeRow(clone); }); // Attach event listener to the minus button\r\n\r\n document.getElementById('queryRowsContainer').appendChild(clone);\r\n}\r\n\r\nasync function loadModel(model, quantized = true) {\r\n if (model !== loadedModelName || quantized !== quantizedFlag) { // Check if model or quantized flag changed\r\n submitButton.setAttribute(\"disabled\", \"\");\r\n loadingElement.style.display = \"\";\r\n submit_button_text.textContent = \"Loading model...\";\r\n\r\n embedder = await (0,_xenova_transformers__WEBPACK_IMPORTED_MODULE_0__.pipeline)(\"feature-extraction\", model, { quantized: quantized });\r\n loadedModelName = model;\r\n quantizedFlag = quantized; // Update quantized flag\r\n console.log(\"Model loaded:\", loadedModelName, \" quantized: \", quantized);\r\n } else {\r\n console.log(\"Model already loaded:\", loadedModelName, \" quantized: \", quantized);\r\n }\r\n}\r\n\r\nsubmitButton.onclick = () => {\r\n const modelName = document.getElementById(\"HFModel\").value;\r\n const quantized = document.getElementById(\"quantizedToggle\").checked;\r\n loadModel(modelName, quantized).then(() => {\r\n sendRequest();\r\n });\r\n};\r\n\r\ndownload_csv.onclick = () => {\r\n exportData(searchResults, \"csv\");\r\n};\r\n\r\ndownload_xlsx.onclick = () => {\r\n exportData(searchResults, \"excel\");\r\n};\r\n\r\ndownload_json.onclick = () => {\r\n exportData(searchResults, \"json\");\r\n};\r\n\r\n////////////////////////////////////////////////////////////////////\r\n\r\nasync function searchPoints(collectionName, vectorData, filter, limit, offset, withPayload, withVector, scoreThreshold) {\r\n var reqBody = JSON.stringify({\r\n vector: vectorData,\r\n filter: filter,\r\n limit: limit,\r\n offset: offset,\r\n with_payload: withPayload,\r\n with_vector: withVector,\r\n score_threshold: scoreThreshold,\r\n });\r\n\r\n const requestOptions = {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: reqBody,\r\n };\r\n\r\n thisCollection = document.getElementById(\"QdrantURL\").value\r\n const response = await fetch(`${thisCollection}/points/search`, requestOptions);\r\n const data = await response.json();\r\n\r\n return data;\r\n}\r\n\r\n\r\nfunction getQueryTextsAndWeigths() {\r\n const queryContainers = document.querySelectorAll('.queryContainer');\r\n\r\n const activeQueries = Array.from(queryContainers).filter(container => {\r\n const activeToggle = container.querySelector('.activeToggle');\r\n return activeToggle.checked;\r\n }).map(container => {\r\n const inputText = container.querySelector('.inputText').value.trim();\r\n const weight = container.querySelector('.queryWeight').value;\r\n return { inputText, weight };\r\n });\r\n\r\n const jsonObject = JSON.stringify(activeQueries);\r\n\r\n console.log(jsonObject); // Logs the JSON string of active queries\r\n\r\n return jsonObject\r\n}\r\n\r\nasync function processInputText(inputText) {\r\n\r\n if (inputText == \"special_vector\") {\r\n return special_vector\r\n }\r\n else {\r\n const output = await embedder(inputText, { pooling: 'mean', normalize: true });\r\n const vectorArray = Array.from(output[\"data\"]);\r\n return vectorArray;\r\n }\r\n}\r\n\r\nasync function processQueries() {\r\n const jsonObject = getQueryTextsAndWeigths();\r\n const queries = JSON.parse(jsonObject);\r\n\r\n // Step 1: Calculate the vector for each text\r\n const vectors = await Promise.all(queries.map(async query => {\r\n const { inputText } = query\r\n return await processInputText(inputText)\r\n }));\r\n\r\n // Step 2: Calculate the weighted average vector\r\n const weightedAverageVector = vectors.reduce((acc, vector, index) => {\r\n const weight = queries[index].weight;\r\n return acc.map((val, i) => val + vector[i] * weight);\r\n }, new Array(vectors[0].length).fill(0));\r\n\r\n // Normalize the weighted average vector\r\n const magnitude = Math.sqrt(weightedAverageVector.reduce((sum, val) => sum + val * val, 0));\r\n const normalizedWeightedAverageVector = weightedAverageVector.map(val => val / magnitude);\r\n\r\n console.log(normalizedWeightedAverageVector); // Logs the normalized weighted average vector\r\n return normalizedWeightedAverageVector\r\n}\r\n\r\n// Define global variables for the grid API and options\r\nlet gridApi;\r\nlet gridOptions;\r\n\r\n\r\nasync function sendRequest() {\r\n try {\r\n gridApi.showLoadingOverlay();\r\n }\r\n catch (error) {\r\n }\r\n loadingElement.style.display = \"\";\r\n submit_button_text.textContent = \"Loading results...\";\r\n submitButton.setAttribute(\"disabled\", \"\");\r\n\r\n let inputText = document.getElementsByClassName(\"inputText\")[0].value.trim();\r\n //const trimmedInputTexts = Array.from(document.getElementsByClassName(\"inputText\")).map(input => input.value.trim());\r\n\r\n if (inputText !== \"\") {\r\n //let output = await embedder(inputText, { pooling: 'mean', normalize: true });\r\n const collectionName = \"test_collection\";\r\n const vectorData = await processQueries();//Array.from(output[\"data\"]);\r\n const filter = {};\r\n const limit = parseInt(document.getElementById(\"QdrantLimit\").value);\r\n const offset = 0;\r\n const withPayload = true;\r\n const withVector = false;\r\n const scoreThreshold = null;\r\n\r\n try {\r\n\r\n searchResults = await searchPoints(collectionName, vectorData, filter, limit, offset, withPayload, withVector, scoreThreshold);\r\n //console.log(searchResults);\r\n\r\n // Extract payload keys\r\n const payloadKeys = Object.keys(searchResults.result[0].payload);\r\n\r\n function isHyperlink(value) {\r\n return /^https?:\\/\\//.test(value);\r\n }\r\n\r\n // Custom cell renderer\r\n function customRenderer(params) {\r\n const nestedKey = Object.keys(params.data.payload).find(key => typeof params.data.payload[key] === 'object');\r\n const value = params.data.payload[params.colDef.field.split('.')[1]];\r\n\r\n if (params.colDef.field.endsWith(`.${nestedKey}`)) {\r\n return typeof value === 'object' ? JSON.stringify(value) : value; // Render nested element as string\r\n } else if (isHyperlink(value)) {\r\n return `${value}`;\r\n } else {\r\n return value;\r\n }\r\n }\r\n\r\n // Update your column definition\r\n const columnDefs = [\r\n { headerName: 'id', field: 'id' },\r\n { headerName: 'score', field: 'score' },\r\n ...payloadKeys.map(key => ({\r\n headerName: key,\r\n field: `payload.${key}`,\r\n maxWidth: 300,\r\n editable: true,\r\n valueGetter: params => params.data.payload[key],\r\n tooltipValueGetter: (params) => params.value,\r\n filter: true,\r\n autoHeight: true,\r\n cellRenderer: customRenderer // Use the custom cell renderer\r\n })),\r\n ];\r\n\r\n\r\n // Check if the grid has already been initialized\r\n if (gridApi && thisCollection === lastCollection) {\r\n // If the grid is already initialized, update the row data\r\n gridApi.setRowData(searchResults.result);\r\n } else {\r\n\r\n try {\r\n //gridOptions.api.destroy()\r\n //document.getElementById(\"myGrid\").innerHTML = \"\";\r\n if (thisCollection !== lastCollection) {\r\n // update column headers if needed\r\n gridApi.updateGridOptions({ columnDefs: columnDefs })\r\n }\r\n gridApi.setRowData(searchResults.result);\r\n loadingElement.style.display = \"none\";\r\n submit_button_text.textContent = \"Submit\";\r\n submitButton.removeAttribute(\"disabled\");\r\n\r\n lastCollection = thisCollection\r\n return\r\n }\r\n catch { }\r\n\r\n // If the grid is not initialized, create the grid\r\n gridOptions = {\r\n autoSizeStrategy: {\r\n type: 'fitCellContents'\r\n },\r\n domLayout: 'autoHeight', // Add this line to enable auto height\r\n columnDefs: columnDefs,\r\n rowData: searchResults.result,\r\n tooltipShowDelay: 0,\r\n overlayLoadingTemplate:\r\n '
',\r\n overlayNoRowsTemplate:\r\n 'This is a custom \\'no rows\\' overlay',\r\n\r\n };\r\n\r\n gridApi = (0,ag_grid_community__WEBPACK_IMPORTED_MODULE_1__.createGrid)(eGridDiv, gridOptions);\r\n document.getElementById(\"exportDropdown\").removeAttribute(\"disabled\")\r\n document.getElementById(\"quickFilter\").style.display = \"\";\r\n\r\n }\r\n\r\n loadingElement.style.display = \"none\";\r\n submit_button_text.textContent = \"Submit\";\r\n submitButton.removeAttribute(\"disabled\");\r\n\r\n // on first click add the quick filter listener\r\n if (lastCollection == \"\" && !filterTextBox._listenerInitialized) {\r\n function onFilterTextBoxChanged() {\r\n gridApi.setGridOption(\r\n 'quickFilterText',\r\n document.getElementById('filter-text-box').value\r\n );\r\n }\r\n\r\n filterTextBox.addEventListener('input', () => {\r\n onFilterTextBoxChanged();\r\n });\r\n\r\n // Mark listener as initialized\r\n filterTextBox._listenerInitialized = true;\r\n\r\n }\r\n\r\n\r\n lastCollection = thisCollection\r\n\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n}\r\n\r\nasync function exportData(data, format) {\r\n // Function to flatten the payload object within each object\r\n function flattenPayload(jsonData) {\r\n // Check if jsonData is an array\r\n if (!Array.isArray(jsonData)) {\r\n console.error('jsonData is not an array');\r\n return jsonData;\r\n }\r\n\r\n // Check if every element in the array is an object\r\n if (!jsonData.every(item => typeof item === 'object')) {\r\n console.error('One or more elements in jsonData are not objects');\r\n return jsonData;\r\n }\r\n\r\n // Map over the array and flatten the payload object\r\n return jsonData.map(item => {\r\n const { payload, ...rest } = item;\r\n return { ...rest, ...payload };\r\n });\r\n }\r\n\r\n // Flatten the payload object within each object\r\n let jsonData = flattenPayload(data.result);\r\n\r\n // Based on the format parameter, generate the output accordingly\r\n if (format === 'excel') {\r\n // Create a new workbook\r\n const workbook = xlsx__WEBPACK_IMPORTED_MODULE_4__.utils.book_new();\r\n\r\n // Convert JSON to worksheet\r\n const worksheet = xlsx__WEBPACK_IMPORTED_MODULE_4__.utils.json_to_sheet(jsonData);\r\n\r\n // Add the worksheet to the workbook\r\n xlsx__WEBPACK_IMPORTED_MODULE_4__.utils.book_append_sheet(workbook, worksheet, 'Sheet1');\r\n\r\n // Generate a blob from the workbook\r\n const excelBlob = new Blob([xlsx__WEBPACK_IMPORTED_MODULE_4__.write(workbook, { type: 'array', bookType: 'xlsx' })], {\r\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\r\n });\r\n\r\n // Create a temporary URL for the blob\r\n const excelUrl = URL.createObjectURL(excelBlob);\r\n\r\n // Create a link element\r\n const link = document.createElement('a');\r\n link.href = excelUrl;\r\n link.download = `${document.getElementById(\"inputText\").value.trim()}.xlsx`;\r\n\r\n // Append the link to the document body and trigger the download\r\n document.body.appendChild(link);\r\n link.click();\r\n\r\n // Clean up\r\n URL.revokeObjectURL(excelUrl);\r\n document.body.removeChild(link);\r\n } else if (format === 'csv') {\r\n // Convert JSON to CSV\r\n const csvContent = jsonData.map(row => {\r\n return Object.values(row).map(value => {\r\n if (typeof value === 'string') {\r\n // Escape double quotes within the value and enclose it in double quotes\r\n return '\"' + value.replace(/\"/g, '\"\"') + '\"';\r\n }\r\n return value;\r\n }).join(',');\r\n }).join('\\n');\r\n\r\n // Create a blob from the CSV content\r\n const csvBlob = new Blob([csvContent], { type: 'text/csv' });\r\n const csvUrl = URL.createObjectURL(csvBlob);\r\n\r\n // Create a link element\r\n const link = document.createElement('a');\r\n link.href = csvUrl;\r\n link.download = `${document.getElementById(\"inputText\").value.trim()}.csv`;\r\n\r\n // Append the link to the document body and trigger the download\r\n document.body.appendChild(link);\r\n link.click();\r\n\r\n // Clean up\r\n URL.revokeObjectURL(csvUrl);\r\n document.body.removeChild(link);\r\n\r\n\r\n } else if (format === 'json') {\r\n // Convert JSON to string\r\n const jsonString = JSON.stringify(jsonData, null, 2);\r\n\r\n // Create a blob from the JSON string\r\n const jsonBlob = new Blob([jsonString], { type: 'application/json' });\r\n\r\n // Create a temporary URL for the blob\r\n const jsonUrl = URL.createObjectURL(jsonBlob);\r\n\r\n // Create a link element\r\n const link = document.createElement('a');\r\n link.href = jsonUrl;\r\n link.download = `${document.getElementById(\"inputText\").value.trim()}.json`;\r\n\r\n // Append the link to the document body and trigger the download\r\n document.body.appendChild(link);\r\n link.click();\r\n\r\n // Clean up\r\n URL.revokeObjectURL(jsonUrl);\r\n document.body.removeChild(link);\r\n } else {\r\n console.error('Unsupported format');\r\n }\r\n}\r\n\r\ndocument.getElementById('copyURLButton').addEventListener('click', function () {\r\n var urlToCopy = window.location.href;\r\n\r\n navigator.clipboard.writeText(urlToCopy)\r\n .then(function () {\r\n console.log('URL copied to clipboard successfully: ' + urlToCopy);\r\n // You can also show a success message here if needed\r\n })\r\n .catch(function (err) {\r\n console.error('Failed to copy URL to clipboard: ', err);\r\n // You can also show an error message here if needed\r\n });\r\n});\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n // Initialize a counter for the current row count\r\n let rowCount = 1; // Assuming the initial row is already present\r\n\r\n // Function to clone the row and replace the plus button with a minus button\r\n function TaddRow() {\r\n const originalRow = document.getElementById('initialQueryContainer');\r\n const clone = originalRow.cloneNode(true);\r\n clone.id = 'queryContainer' + rowCount; // Adjust the ID of the cloned row\r\n\r\n // Adjust IDs of all elements within the cloned row\r\n clone.querySelectorAll('input, button').forEach(function (element) {\r\n const currentId = element.id;\r\n const newId = currentId.replace(/\\d+$/, rowCount); // Replace the last digit(s) with the current rowCount\r\n element.id = newId;\r\n });\r\n\r\n const minusButton = clone.querySelector('.queryButton');\r\n minusButton.innerHTML = `\r\n \r\n `\r\n minusButton.title = 'Remove query';\r\n minusButton.addEventListener('click', function () { removeRow(clone); }); // Attach event listener to the minus button\r\n\r\n document.getElementById('queryRowsContainer').appendChild(clone);\r\n rowCount++; // Increment the row count\r\n }\r\n\r\n // Add event listener to the plus button\r\n const plusButton = document.querySelector('.btn-light');\r\n plusButton.addEventListener('click', TaddRow);\r\n});\r\n\r\nvar URLModeHidden = document.getElementById(\"copyURLButton\").hidden;\r\n\r\nif (URLModeHidden) {\r\n\r\n} else {\r\n // Call the function initially to set form inputs from URL parameters\r\n document.addEventListener('DOMContentLoaded', function () {\r\n // Your code here\r\n setFormInputsFromURL();\r\n\r\n // Use event delegation to handle inputs dynamically added\r\n document.body.addEventListener('input', function (event) {\r\n if (event.target.matches('.form-control, .form-check-input')) {\r\n updateURL(event);\r\n }\r\n\r\n if (event.target.matches('.inputText')) {\r\n // Check if the trimmed value of the input is \"special_vector\"\r\n if (event.target.value.trim() === \"special_vector\") {\r\n // If the condition is met, apply italic text and grey background\r\n event.target.style.fontStyle = 'italic';\r\n //event.target.style.backgroundColor = 'grey';\r\n } else {\r\n // If the condition is not met, remove italic text and grey background\r\n event.target.style.fontStyle = 'normal';\r\n //event.target.style.backgroundColor = '';\r\n }\r\n }\r\n });\r\n\r\n document.body.addEventListener('click', function (event) {\r\n if (event.target.matches('.queryButton')) {\r\n updateURL(event);\r\n }\r\n });\r\n\r\n });\r\n}\r\n\n\n//# sourceURL=webpack://qdrant-frontend/./index.js?"); /***/ }), diff --git a/index.html b/index.html index 0da44b1..3403b4f 100644 --- a/index.html +++ b/index.html @@ -5,10 +5,17 @@ Qdrant Frontend - - + +