diff --git a/dj_backend_server/api/data_sources/pdf_handler.py b/dj_backend_server/api/data_sources/pdf_handler.py index 555d9eeb..6890fa8d 100644 --- a/dj_backend_server/api/data_sources/pdf_handler.py +++ b/dj_backend_server/api/data_sources/pdf_handler.py @@ -23,10 +23,12 @@ from web.utils.delete_foler import delete_folder from web.models.failed_jobs import FailedJob from web.models.pdf_data_sources import PdfDataSource +from pypdfium2 import PdfDocument + @csrf_exempt -def pdf_handler(shared_folder: str, namespace: str, delete_folder_flag: Optional[bool] = False, text_data: Optional[str] = None): +def pdf_handler(shared_folder: str, namespace: str, delete_folder_flag: Optional[bool] = False, ocr_pdf_file: Optional[bool] = False, text_data: Optional[str] = None): """ This function handles PDF files and other types of files in a shared folder. It processes the text data if provided directly, otherwise it reads from the files in the shared folder. It processes each file based on its extension, converts .doc, .docx, .xls, @@ -36,21 +38,25 @@ def pdf_handler(shared_folder: str, namespace: str, delete_folder_flag: Optional shared_folder (str): The name of the shared folder where the files are located. namespace (str): The namespace for the vector database. delete_folder_flag (bool): A flag indicating whether to delete the folder after processing the files. + ocr_pdf_file (bool): A flag indicating whether to send or not PDF to OCR API services. text_data (Optional[str], optional): The text data to be processed. If this is provided, the function will not read from the files. Defaults to None. Raises: Exception: If an error occurs during the processing of the files or the conversion of the text data to a vector database. """ - print ("Debug: pdf_handler") + # If text data is provided directly, process it without reading from files if text_data: process_text_data(text_data, namespace) print ("Debug: text_data is provided directly, process it without reading from files") return - # Convert delete_folder_flag to boolean (send 0 - FALSE or 1 - TRUE) + # Convert delete_folder_flag and ocr_pdf_file to boolean (send 0 - FALSE or 1 - TRUE) delete_folder_flag = bool(delete_folder_flag) if delete_folder_flag is not None else False + ocr_pdf_file = bool(ocr_pdf_file) if ocr_pdf_file is not None else False + print (f"Debug: delete_folder_flag: {delete_folder_flag}, ocr_pdf_file: {ocr_pdf_file}") + # Check if the shared_folder is provided, if not, return early as there are no files to process if not shared_folder: print("No shared folder provided for file processing.") @@ -58,26 +64,29 @@ def pdf_handler(shared_folder: str, namespace: str, delete_folder_flag: Optional try: #TODO: When will be multiple external library to choose, need to change. - if os.environ.get("PDF_LIBRARY") == "external": - if shared_folder: - directory_path = os.path.join("website_data_sources", shared_folder) - print(f"Debug: Processing folder {directory_path}") - - if os.path.exists(directory_path): - print(f"Debug: Directory exists. Files: {os.listdir(directory_path)}") - else: - print(f"Debug: No shared folder provided for file processing.") - return - - # Process each file in the directory based on its extension - for filename in os.listdir(directory_path): - file_path = os.path.join(directory_path, filename) - if filename.endswith(".pdf"): + directory_path = os.path.join("website_data_sources", shared_folder) + print(f"Debug: Processing folder {directory_path}") + + if not os.path.exists(directory_path): + print(f"Debug: Directory {directory_path} does not exist.") + return + + print(f"Debug: Directory exists. Files: {os.listdir(directory_path)}") + + # Process each file in the directory based on its extension + for filename in os.listdir(directory_path): + file_path = os.path.join(directory_path, filename) + if filename.endswith(".pdf"): + if ocr_pdf_file == True: process_pdf(file_path, directory_path) - elif filename.endswith((".txt", ".csv", ".json")): - save_as_txt(file_path) - elif filename.endswith((".doc", ".docx", ".xls", ".xlsx")): - convert_to_txt(file_path) + print(f"Debug: OCR PDF file {ocr_pdf_file}") + else: + process_pdf_with_pypdfium(file_path, directory_path) + print(f"Debug: Not need to send to OCR API {ocr_pdf_file}") + elif filename.endswith((".txt", ".csv", ".json")): + save_as_txt(file_path) + elif filename.endswith((".doc", ".docx", ".xls", ".xlsx")): + convert_to_txt(file_path) txt_to_vectordb(shared_folder, namespace, delete_folder_flag) @@ -89,11 +98,29 @@ def pdf_handler(shared_folder: str, namespace: str, delete_folder_flag: Optional print("Exception occurred:", e) traceback.print_exc() +@csrf_exempt +def process_pdf_with_pypdfium(file_path, directory_path): + pdf = PdfDocument(file_path) + text_pages = [] + + for page_index in range(len(pdf)): + page = pdf.get_page(page_index) + text_page = page.get_textpage() # get a text page handle for this page + text = text_page.get_text_range() # extract text from the text page + text_pages.append(text) + text_page.close() # close the text page handle + + text = ''.join(text_pages) + txt_file_path = os.path.splitext(file_path)[0] + '.txt' + print(f"Debug: Writing text to {txt_file_path}, AAA: {directory_path}, BBB: {text}") + + with open(txt_file_path, 'w') as f: + f.write(text) + + pdf.close() @csrf_exempt def process_pdf(FilePath,directory_path): - #pdf_data_source = PdfDataSource.objects.get(folder_name=FilePath) - #pdf_data_source = PdfDataSource.objects.get(folder_name=directory_path) UserName = os.environ.get("OCR_USERNAME") LicenseCode = os.environ.get("OCR_LICCODE") gettext = True diff --git a/dj_backend_server/api/pdf_handler.py b/dj_backend_server/api/pdf_handler.py index 5ecf1a48..d6289754 100644 --- a/dj_backend_server/api/pdf_handler.py +++ b/dj_backend_server/api/pdf_handler.py @@ -18,6 +18,7 @@ def upload_pdf_api(request): API endpoint for uploading PDF files. It expects a POST request with the following parameters: - 'X-Bot-Token' header: A token to authenticate the chatbot. - 'delete_folder_flag': A flag indicating whether to delete the folder after processing (0 or 1). + - 'ocr_pdf_file': A flag indicating that the file need to be send to OCR API. (0 or 1). - 'pdffiles': The PDF file(s) to be uploaded. Can be a single file or multiple files. """ @@ -28,6 +29,7 @@ def upload_pdf_api(request): return JsonResponse({'error': 'Invalid token'}, status=403) delete_folder_flag = request.POST.get('delete_folder_flag', '0') == '1' + ocr_pdf_file = request.POST.get('ocr_pdf_file', '0') == '1' files = request.FILES.getlist('pdffiles') text_data = request.POST.get('text_data', '') @@ -37,6 +39,6 @@ def upload_pdf_api(request): print (f"text_data: {data_source}") # Trigger the PdfDataSourceWasAdded event - pdf_data_source_added.send(sender='create_via_pdf_flow', bot_id=bot.id, data_source_id=data_source.id, delete_folder_flag=delete_folder_flag) + pdf_data_source_added.send(sender='create_via_pdf_flow', bot_id=bot.id, data_source_id=data_source.id, delete_folder_flag=delete_folder_flag, ocr_pdf_file=ocr_pdf_file, text_data=text_data) return JsonResponse({'message': 'PDF uploaded and chatbot created successfully', 'data_source_id': data_source.id, 'bot_id': bot.id}) \ No newline at end of file diff --git a/dj_backend_server/api/tasks.py b/dj_backend_server/api/tasks.py index cb8d50f1..0d07185c 100644 --- a/dj_backend_server/api/tasks.py +++ b/dj_backend_server/api/tasks.py @@ -5,8 +5,8 @@ from web.workers.crawler import start_recursive_crawler @shared_task -def pdf_handler_task(shared_folder, namespace, delete_folder_flag): - return pdf_handler(shared_folder=shared_folder, namespace=namespace, delete_folder_flag=delete_folder_flag) +def pdf_handler_task(shared_folder, namespace, delete_folder_flag, ocr_pdf_file): + return pdf_handler(shared_folder=shared_folder, namespace=namespace, delete_folder_flag=delete_folder_flag, ocr_pdf_file=ocr_pdf_file) @shared_task def website_handler_task(shared_folder, namespace): diff --git a/dj_backend_server/api/views/views_ingest.py b/dj_backend_server/api/views/views_ingest.py index d2e94f98..459dc093 100644 --- a/dj_backend_server/api/views/views_ingest.py +++ b/dj_backend_server/api/views/views_ingest.py @@ -35,7 +35,8 @@ def ingest(request): if type_ == 'pdf': delete_folder_flag = data.get('delete_folder_flag', False) - pdf_handler_task.delay(shared_folder, namespace, delete_folder_flag) + ocr_pdf_file = data.get('ocr_pdf_file', False) + pdf_handler_task.delay(shared_folder, namespace, delete_folder_flag, ocr_pdf_file) elif type_ == 'website': print("Calling website handler task") website_handler_task.delay(shared_folder, namespace) diff --git a/dj_backend_server/requirements.txt b/dj_backend_server/requirements.txt index c6f10859..a006c0ab 100644 --- a/dj_backend_server/requirements.txt +++ b/dj_backend_server/requirements.txt @@ -27,10 +27,10 @@ grpcio-tools==1.56.2 h11==0.14.0 h2==4.1.0 hpack==4.0.0 -httpcore==0.17.3 -httpx==0.24.1 +httpcore1.0.2 +httpx=0.25.2 hyperframe==6.0.1 -idna==3.4 +idna==3.6 kombu==5.3.1 langchain==0.0.247 langsmith==0.0.15 @@ -54,7 +54,7 @@ pycparser==2.21 pydantic==1.10.12 PyMySQL==1.1.0 pypdf==3.14.0 -pypdfium2==4.18.0 +pypdfium2==4.26.0 python-dateutil==2.8.2 python-dotenv==1.0.0 python-docx==1.1.0 @@ -78,4 +78,5 @@ urllib3==1.26.16 vine==5.0.0 wcwidth==0.2.6 yarl==1.9.2 -django-cors-headers==4.3.1 \ No newline at end of file +django-cors-headers==4.3.1 +ollama==0.1.4 diff --git a/dj_backend_server/web/listeners/ingest_pdf_data_source.py b/dj_backend_server/web/listeners/ingest_pdf_data_source.py index 1c192030..c1ff1043 100644 --- a/dj_backend_server/web/listeners/ingest_pdf_data_source.py +++ b/dj_backend_server/web/listeners/ingest_pdf_data_source.py @@ -13,6 +13,7 @@ def ingest_pdf_datasource(sender, **kwargs): bot_id = kwargs['bot_id'] pdf_data_source_id = kwargs['data_source_id'] delete_folder_flag = kwargs['delete_folder_flag'] + ocr_pdf_file = kwargs.get('ocr_pdf_file', False) try: pdf_data_source = PdfDataSource.objects.get(id=pdf_data_source_id) @@ -24,6 +25,7 @@ def ingest_pdf_datasource(sender, **kwargs): 'shared_folder': pdf_data_source.folder_name, 'namespace': str(bot_id), 'delete_folder_flag': delete_folder_flag, + 'ocr_pdf_file': ocr_pdf_file, } try: diff --git a/dj_backend_server/web/static/dashboard/js/vendors/clipboard.js b/dj_backend_server/web/static/dashboard/js/vendors/clipboard.js new file mode 100644 index 00000000..aeb826f0 --- /dev/null +++ b/dj_backend_server/web/static/dashboard/js/vendors/clipboard.js @@ -0,0 +1,890 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["ClipboardJS"] = factory(); + else + root["ClipboardJS"] = factory(); +})(this, function() { +return /******/ (function() { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 686: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + "default": function() { return /* binding */ clipboard; } +}); + +// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js +var tiny_emitter = __webpack_require__(279); +var tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter); +// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js +var listen = __webpack_require__(370); +var listen_default = /*#__PURE__*/__webpack_require__.n(listen); +// EXTERNAL MODULE: ./node_modules/select/src/select.js +var src_select = __webpack_require__(817); +var select_default = /*#__PURE__*/__webpack_require__.n(src_select); +;// CONCATENATED MODULE: ./src/common/command.js +/** + * Executes a given operation type. + * @param {String} type + * @return {Boolean} + */ +function command(type) { + try { + return document.execCommand(type); + } catch (err) { + return false; + } +} +;// CONCATENATED MODULE: ./src/actions/cut.js + + +/** + * Cut action wrapper. + * @param {String|HTMLElement} target + * @return {String} + */ + +var ClipboardActionCut = function ClipboardActionCut(target) { + var selectedText = select_default()(target); + command('cut'); + return selectedText; +}; + +/* harmony default export */ var actions_cut = (ClipboardActionCut); +;// CONCATENATED MODULE: ./src/common/create-fake-element.js +/** + * Creates a fake textarea element with a value. + * @param {String} value + * @return {HTMLElement} + */ +function createFakeElement(value) { + var isRTL = document.documentElement.getAttribute('dir') === 'rtl'; + var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS + + fakeElement.style.fontSize = '12pt'; // Reset box model + + fakeElement.style.border = '0'; + fakeElement.style.padding = '0'; + fakeElement.style.margin = '0'; // Move element out of screen horizontally + + fakeElement.style.position = 'absolute'; + fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically + + var yPosition = window.pageYOffset || document.documentElement.scrollTop; + fakeElement.style.top = "".concat(yPosition, "px"); + fakeElement.setAttribute('readonly', ''); + fakeElement.value = value; + return fakeElement; +} +;// CONCATENATED MODULE: ./src/actions/copy.js + + + +/** + * Create fake copy action wrapper using a fake element. + * @param {String} target + * @param {Object} options + * @return {String} + */ + +var fakeCopyAction = function fakeCopyAction(value, options) { + var fakeElement = createFakeElement(value); + options.container.appendChild(fakeElement); + var selectedText = select_default()(fakeElement); + command('copy'); + fakeElement.remove(); + return selectedText; +}; +/** + * Copy action wrapper. + * @param {String|HTMLElement} target + * @param {Object} options + * @return {String} + */ + + +var ClipboardActionCopy = function ClipboardActionCopy(target) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + container: document.body + }; + var selectedText = ''; + + if (typeof target === 'string') { + selectedText = fakeCopyAction(target, options); + } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) { + // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange + selectedText = fakeCopyAction(target.value, options); + } else { + selectedText = select_default()(target); + command('copy'); + } + + return selectedText; +}; + +/* harmony default export */ var actions_copy = (ClipboardActionCopy); +;// CONCATENATED MODULE: ./src/actions/default.js +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + + + +/** + * Inner function which performs selection from either `text` or `target` + * properties and then executes copy or cut operations. + * @param {Object} options + */ + +var ClipboardActionDefault = function ClipboardActionDefault() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + // Defines base properties passed from constructor. + var _options$action = options.action, + action = _options$action === void 0 ? 'copy' : _options$action, + container = options.container, + target = options.target, + text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'. + + if (action !== 'copy' && action !== 'cut') { + throw new Error('Invalid "action" value, use either "copy" or "cut"'); + } // Sets the `target` property using an element that will be have its content copied. + + + if (target !== undefined) { + if (target && _typeof(target) === 'object' && target.nodeType === 1) { + if (action === 'copy' && target.hasAttribute('disabled')) { + throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); + } + + if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { + throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); + } + } else { + throw new Error('Invalid "target" value, use a valid Element'); + } + } // Define selection strategy based on `text` property. + + + if (text) { + return actions_copy(text, { + container: container + }); + } // Defines which selection strategy based on `target` property. + + + if (target) { + return action === 'cut' ? actions_cut(target) : actions_copy(target, { + container: container + }); + } +}; + +/* harmony default export */ var actions_default = (ClipboardActionDefault); +;// CONCATENATED MODULE: ./src/clipboard.js +function clipboard_typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return clipboard_typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + + + + + + +/** + * Helper function to retrieve attribute value. + * @param {String} suffix + * @param {Element} element + */ + +function getAttributeValue(suffix, element) { + var attribute = "data-clipboard-".concat(suffix); + + if (!element.hasAttribute(attribute)) { + return; + } + + return element.getAttribute(attribute); +} +/** + * Base class which takes one or more elements, adds event listeners to them, + * and instantiates a new `ClipboardAction` on each click. + */ + + +var Clipboard = /*#__PURE__*/function (_Emitter) { + _inherits(Clipboard, _Emitter); + + var _super = _createSuper(Clipboard); + + /** + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + * @param {Object} options + */ + function Clipboard(trigger, options) { + var _this; + + _classCallCheck(this, Clipboard); + + _this = _super.call(this); + + _this.resolveOptions(options); + + _this.listenClick(trigger); + + return _this; + } + /** + * Defines if attributes would be resolved using internal setter functions + * or custom functions that were passed in the constructor. + * @param {Object} options + */ + + + _createClass(Clipboard, [{ + key: "resolveOptions", + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + this.action = typeof options.action === 'function' ? options.action : this.defaultAction; + this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; + this.text = typeof options.text === 'function' ? options.text : this.defaultText; + this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body; + } + /** + * Adds a click event listener to the passed trigger. + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + */ + + }, { + key: "listenClick", + value: function listenClick(trigger) { + var _this2 = this; + + this.listener = listen_default()(trigger, 'click', function (e) { + return _this2.onClick(e); + }); + } + /** + * Defines a new `ClipboardAction` on each click event. + * @param {Event} e + */ + + }, { + key: "onClick", + value: function onClick(e) { + var trigger = e.delegateTarget || e.currentTarget; + var action = this.action(trigger) || 'copy'; + var text = actions_default({ + action: action, + container: this.container, + target: this.target(trigger), + text: this.text(trigger) + }); // Fires an event based on the copy operation result. + + this.emit(text ? 'success' : 'error', { + action: action, + text: text, + trigger: trigger, + clearSelection: function clearSelection() { + if (trigger) { + trigger.focus(); + } + + window.getSelection().removeAllRanges(); + } + }); + } + /** + * Default `action` lookup function. + * @param {Element} trigger + */ + + }, { + key: "defaultAction", + value: function defaultAction(trigger) { + return getAttributeValue('action', trigger); + } + /** + * Default `target` lookup function. + * @param {Element} trigger + */ + + }, { + key: "defaultTarget", + value: function defaultTarget(trigger) { + var selector = getAttributeValue('target', trigger); + + if (selector) { + return document.querySelector(selector); + } + } + /** + * Allow fire programmatically a copy action + * @param {String|HTMLElement} target + * @param {Object} options + * @returns Text copied. + */ + + }, { + key: "defaultText", + + /** + * Default `text` lookup function. + * @param {Element} trigger + */ + value: function defaultText(trigger) { + return getAttributeValue('text', trigger); + } + /** + * Destroy lifecycle. + */ + + }, { + key: "destroy", + value: function destroy() { + this.listener.destroy(); + } + }], [{ + key: "copy", + value: function copy(target) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + container: document.body + }; + return actions_copy(target, options); + } + /** + * Allow fire programmatically a cut action + * @param {String|HTMLElement} target + * @returns Text cutted. + */ + + }, { + key: "cut", + value: function cut(target) { + return actions_cut(target); + } + /** + * Returns the support of the given action, or all actions if no action is + * given. + * @param {String} [action] + */ + + }, { + key: "isSupported", + value: function isSupported() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; + var actions = typeof action === 'string' ? [action] : action; + var support = !!document.queryCommandSupported; + actions.forEach(function (action) { + support = support && !!document.queryCommandSupported(action); + }); + return support; + } + }]); + + return Clipboard; +}((tiny_emitter_default())); + +/* harmony default export */ var clipboard = (Clipboard); + +/***/ }), + +/***/ 828: +/***/ (function(module) { + +var DOCUMENT_NODE_TYPE = 9; + +/** + * A polyfill for Element.matches() + */ +if (typeof Element !== 'undefined' && !Element.prototype.matches) { + var proto = Element.prototype; + + proto.matches = proto.matchesSelector || + proto.mozMatchesSelector || + proto.msMatchesSelector || + proto.oMatchesSelector || + proto.webkitMatchesSelector; +} + +/** + * Finds the closest parent that matches a selector. + * + * @param {Element} element + * @param {String} selector + * @return {Function} + */ +function closest (element, selector) { + while (element && element.nodeType !== DOCUMENT_NODE_TYPE) { + if (typeof element.matches === 'function' && + element.matches(selector)) { + return element; + } + element = element.parentNode; + } +} + +module.exports = closest; + + +/***/ }), + +/***/ 438: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var closest = __webpack_require__(828); + +/** + * Delegates event to a selector. + * + * @param {Element} element + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @param {Boolean} useCapture + * @return {Object} + */ +function _delegate(element, selector, type, callback, useCapture) { + var listenerFn = listener.apply(this, arguments); + + element.addEventListener(type, listenerFn, useCapture); + + return { + destroy: function() { + element.removeEventListener(type, listenerFn, useCapture); + } + } +} + +/** + * Delegates event to a selector. + * + * @param {Element|String|Array} [elements] + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @param {Boolean} useCapture + * @return {Object} + */ +function delegate(elements, selector, type, callback, useCapture) { + // Handle the regular Element usage + if (typeof elements.addEventListener === 'function') { + return _delegate.apply(null, arguments); + } + + // Handle Element-less usage, it defaults to global delegation + if (typeof type === 'function') { + // Use `document` as the first parameter, then apply arguments + // This is a short way to .unshift `arguments` without running into deoptimizations + return _delegate.bind(null, document).apply(null, arguments); + } + + // Handle Selector-based usage + if (typeof elements === 'string') { + elements = document.querySelectorAll(elements); + } + + // Handle Array-like based usage + return Array.prototype.map.call(elements, function (element) { + return _delegate(element, selector, type, callback, useCapture); + }); +} + +/** + * Finds closest match and invokes callback. + * + * @param {Element} element + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @return {Function} + */ +function listener(element, selector, type, callback) { + return function(e) { + e.delegateTarget = closest(e.target, selector); + + if (e.delegateTarget) { + callback.call(element, e); + } + } +} + +module.exports = delegate; + + +/***/ }), + +/***/ 879: +/***/ (function(__unused_webpack_module, exports) { + +/** + * Check if argument is a HTML element. + * + * @param {Object} value + * @return {Boolean} + */ +exports.node = function(value) { + return value !== undefined + && value instanceof HTMLElement + && value.nodeType === 1; +}; + +/** + * Check if argument is a list of HTML elements. + * + * @param {Object} value + * @return {Boolean} + */ +exports.nodeList = function(value) { + var type = Object.prototype.toString.call(value); + + return value !== undefined + && (type === '[object NodeList]' || type === '[object HTMLCollection]') + && ('length' in value) + && (value.length === 0 || exports.node(value[0])); +}; + +/** + * Check if argument is a string. + * + * @param {Object} value + * @return {Boolean} + */ +exports.string = function(value) { + return typeof value === 'string' + || value instanceof String; +}; + +/** + * Check if argument is a function. + * + * @param {Object} value + * @return {Boolean} + */ +exports.fn = function(value) { + var type = Object.prototype.toString.call(value); + + return type === '[object Function]'; +}; + + +/***/ }), + +/***/ 370: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var is = __webpack_require__(879); +var delegate = __webpack_require__(438); + +/** + * Validates all params and calls the right + * listener function based on its target type. + * + * @param {String|HTMLElement|HTMLCollection|NodeList} target + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listen(target, type, callback) { + if (!target && !type && !callback) { + throw new Error('Missing required arguments'); + } + + if (!is.string(type)) { + throw new TypeError('Second argument must be a String'); + } + + if (!is.fn(callback)) { + throw new TypeError('Third argument must be a Function'); + } + + if (is.node(target)) { + return listenNode(target, type, callback); + } + else if (is.nodeList(target)) { + return listenNodeList(target, type, callback); + } + else if (is.string(target)) { + return listenSelector(target, type, callback); + } + else { + throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList'); + } +} + +/** + * Adds an event listener to a HTML element + * and returns a remove listener function. + * + * @param {HTMLElement} node + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenNode(node, type, callback) { + node.addEventListener(type, callback); + + return { + destroy: function() { + node.removeEventListener(type, callback); + } + } +} + +/** + * Add an event listener to a list of HTML elements + * and returns a remove listener function. + * + * @param {NodeList|HTMLCollection} nodeList + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenNodeList(nodeList, type, callback) { + Array.prototype.forEach.call(nodeList, function(node) { + node.addEventListener(type, callback); + }); + + return { + destroy: function() { + Array.prototype.forEach.call(nodeList, function(node) { + node.removeEventListener(type, callback); + }); + } + } +} + +/** + * Add an event listener to a selector + * and returns a remove listener function. + * + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenSelector(selector, type, callback) { + return delegate(document.body, selector, type, callback); +} + +module.exports = listen; + + +/***/ }), + +/***/ 817: +/***/ (function(module) { + +function select(element) { + var selectedText; + + if (element.nodeName === 'SELECT') { + element.focus(); + + selectedText = element.value; + } + else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { + var isReadOnly = element.hasAttribute('readonly'); + + if (!isReadOnly) { + element.setAttribute('readonly', ''); + } + + element.select(); + element.setSelectionRange(0, element.value.length); + + if (!isReadOnly) { + element.removeAttribute('readonly'); + } + + selectedText = element.value; + } + else { + if (element.hasAttribute('contenteditable')) { + element.focus(); + } + + var selection = window.getSelection(); + var range = document.createRange(); + + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + + selectedText = selection.toString(); + } + + return selectedText; +} + +module.exports = select; + + +/***/ }), + +/***/ 279: +/***/ (function(module) { + +function E () { + // Keep this empty so it's easier to inherit from + // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3) +} + +E.prototype = { + on: function (name, callback, ctx) { + var e = this.e || (this.e = {}); + + (e[name] || (e[name] = [])).push({ + fn: callback, + ctx: ctx + }); + + return this; + }, + + once: function (name, callback, ctx) { + var self = this; + function listener () { + self.off(name, listener); + callback.apply(ctx, arguments); + }; + + listener._ = callback + return this.on(name, listener, ctx); + }, + + emit: function (name) { + var data = [].slice.call(arguments, 1); + var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); + var i = 0; + var len = evtArr.length; + + for (i; i < len; i++) { + evtArr[i].fn.apply(evtArr[i].ctx, data); + } + + return this; + }, + + off: function (name, callback) { + var e = this.e || (this.e = {}); + var evts = e[name]; + var liveEvents = []; + + if (evts && callback) { + for (var i = 0, len = evts.length; i < len; i++) { + if (evts[i].fn !== callback && evts[i].fn._ !== callback) + liveEvents.push(evts[i]); + } + } + + // Remove event from queue to prevent memory leak + // Suggested by https://github.com/lazd + // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910 + + (liveEvents.length) + ? e[name] = liveEvents + : delete e[name]; + + return this; + } +}; + +module.exports = E; +module.exports.TinyEmitter = E; + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ !function() { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function() { return module['default']; } : +/******/ function() { return module; }; +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ !function() { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = function(exports, definition) { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ !function() { +/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } +/******/ }(); +/******/ +/************************************************************************/ +/******/ // module exports must be returned from runtime so entry inlining is disabled +/******/ // startup +/******/ // Load entry module and return exports +/******/ return __webpack_require__(686); +/******/ })() +.default; +}); \ No newline at end of file diff --git a/dj_backend_server/web/static/dashboard/js/vendors/clipboard.min.js b/dj_backend_server/web/static/dashboard/js/vendors/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/dj_backend_server/web/static/dashboard/js/vendors/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1 diff --git a/dj_backend_server/web/templates/layout/app.html b/dj_backend_server/web/templates/layout/app.html index ec3abdf2..f7f0c3fa 100644 --- a/dj_backend_server/web/templates/layout/app.html +++ b/dj_backend_server/web/templates/layout/app.html @@ -65,6 +65,7 @@ + diff --git a/dj_backend_server/web/templates/onboarding/other-data-sources-pdf.html b/dj_backend_server/web/templates/onboarding/other-data-sources-pdf.html index de067ccb..fac78dc9 100644 --- a/dj_backend_server/web/templates/onboarding/other-data-sources-pdf.html +++ b/dj_backend_server/web/templates/onboarding/other-data-sources-pdf.html @@ -90,6 +90,18 @@

Upload files as sources ✨ + +
+
+ +
+
+ +
+
+
We are accepting PDF/DOCX/XLSX/TXT(CSV/JSON). 🫶
diff --git a/dj_backend_server/web/templates/onboarding/step-2-pdf.html b/dj_backend_server/web/templates/onboarding/step-2-pdf.html index b19be1e3..49a36291 100644 --- a/dj_backend_server/web/templates/onboarding/step-2-pdf.html +++ b/dj_backend_server/web/templates/onboarding/step-2-pdf.html @@ -118,6 +118,16 @@

Upload PDF files as sources Delete PDF files after sending them for processing.

+
+
+
+ +
+
+ +
diff --git a/dj_backend_server/web/templates/settings-theme.html b/dj_backend_server/web/templates/settings-theme.html index 44d2f286..0904af18 100644 --- a/dj_backend_server/web/templates/settings-theme.html +++ b/dj_backend_server/web/templates/settings-theme.html @@ -5,10 +5,10 @@ {% block content %} -
+

{{ bot.name }}: try & share ✨

    @@ -29,49 +29,35 @@

    {{ bot.name }}: try &

    Try & Share!

    Here you can try and play with your bot, also you can share it or embed - it in your web apps
    + it in your web apps. Anyone with this link will be able to interact + with your chatbot
    + +
    -
    -
    +
    + + + + + -
    - -
    +
    -
    - - -
    -
    +
    +
    - - +

-
+
+
+ + - -
- 2. Please read this documentation to see all the options for the - search widget -
@@ -220,21 +222,23 @@

Try & Share!

{% block scripts %} {% endblock scripts %} diff --git a/dj_backend_server/web/templates/settings.html b/dj_backend_server/web/templates/settings.html index c92c7763..a4a1beb6 100644 --- a/dj_backend_server/web/templates/settings.html +++ b/dj_backend_server/web/templates/settings.html @@ -37,7 +37,8 @@

General Settings

- + +
@@ -48,9 +49,18 @@

General Settings

- - + + +
+ + + +
@@ -216,5 +226,24 @@

Delete the bot

document.getElementById('promptMessage').value = Prompts[promptCase]; } + new ClipboardJS('.btn'); + + var clipboard = new ClipboardJS('.btn'); + + clipboard.on('success', function(e) { + var copySuccessMessage = document.getElementById('copy-success-message'); + copySuccessMessage.textContent = 'Copied'; + setTimeout(function() { + copySuccessMessage.textContent = ''; + }, 1500); + + e.clearSelection(); + }); + + clipboard.on('error', function(e) { + console.error('Action:', e.action); + console.error('Trigger:', e.trigger); + }); + {% endblock %} \ No newline at end of file diff --git a/dj_backend_server/web/views/views_onboarding.py b/dj_backend_server/web/views/views_onboarding.py index 0f08ac83..8c988954 100644 --- a/dj_backend_server/web/views/views_onboarding.py +++ b/dj_backend_server/web/views/views_onboarding.py @@ -53,7 +53,6 @@ def data_sources_pdf(request): return render(request, 'onboarding/step-2-pdf.html', context) def config(request, id): - print(f"id LEHEL {id}") return render(request, 'onboarding/step-3.html') def done(request, id): diff --git a/dj_backend_server/web/views/views_pdf_data_source.py b/dj_backend_server/web/views/views_pdf_data_source.py index 9fd89308..35040cca 100644 --- a/dj_backend_server/web/views/views_pdf_data_source.py +++ b/dj_backend_server/web/views/views_pdf_data_source.py @@ -9,6 +9,7 @@ @require_POST def create(request, id): delete_folder_flag = 'delete_folder_flag' in request.POST + ocr_pdf_file = 'ocr_pdf_file' in request.POST if request.FILES.getlist('pdffiles'): # Get the Chatbot object bot = get_object_or_404(Chatbot, id=id) @@ -31,7 +32,7 @@ def create(request, id): # If no such file exists, proceed with the upload as usual handle_pdf_data_source = HandlePdfDataSource(bot, [upload_file]) data_source = handle_pdf_data_source.handle() - pdf_data_source_added.send(sender=None, bot_id=bot.id, data_source_id=data_source.id, delete_folder_flag=delete_folder_flag) + pdf_data_source_added.send(sender=None, bot_id=bot.id, data_source_id=data_source.id, delete_folder_flag=delete_folder_flag, ocr_pdf_file=ocr_pdf_file) # Redirect to the chatbot settings page with a success message return redirect('chatbot.settings-data', id=bot.id)