diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88ef1adf..a664f12e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: shell: bash run: | if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt install libegl1 libxcb-xinerama0 -y + sudo apt install libegl1 libxcb-xinerama0 python3-tk -y fi python -m pip install --upgrade pip pip install pyinstaller diff --git a/Khoj.spec b/Khoj.spec index e7c7c1f9..3bee99b8 100644 --- a/Khoj.spec +++ b/Khoj.spec @@ -2,8 +2,12 @@ from os.path import join from platform import system from PyInstaller.utils.hooks import copy_metadata +import sysconfig -datas = [('src/interface/web', 'src/interface/web')] +datas = [ + ('src/interface/web', 'src/interface/web'), + (f'{sysconfig.get_paths()["purelib"]}/transformers', 'transformers') +] datas += copy_metadata('tqdm') datas += copy_metadata('regex') datas += copy_metadata('requests') @@ -12,10 +16,8 @@ datas += copy_metadata('filelock') datas += copy_metadata('numpy') datas += copy_metadata('tokenizers') - block_cipher = None - a = Analysis( ['src/main.py'], pathex=[], @@ -32,41 +34,78 @@ a = Analysis( noarchive=False, ) -# Filter out unused, duplicate shared libs -extension = {'Windows': '.dll', 'Darwin': '.dylib', 'Linux': '.so'}[system()] +# Filter out unused and/or duplicate shared libs torch_lib_paths = { - join('torch', 'lib', 'libtorch_cuda' + extension), - join('torch', 'lib', 'libtorch_cpu' + extension), - join('torch', 'lib', 'libtorch_python' + extension) + join('torch', 'lib', 'libtorch_cuda.so'), + join('torch', 'lib', 'libtorch_cpu.so'), } a.datas = [entry for entry in a.datas if not entry[0] in torch_lib_paths] +a.datas = [entry for entry in a.datas if not 'torch/_C.cp' in entry[0]] +a.datas = [entry for entry in a.datas if not 'torch/_dl.cp' in entry[0]] + pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='Khoj', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=False, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch='x86_64', - codesign_identity=None, - entitlements_file=None, - icon='src/interface/web/assets/icons/favicon.icns', -) +if system() != 'Darwin': + # Add Splash screen to show on app launch + splash = Splash( + 'src/interface/web/assets/icons/favicon-144x144.png', + binaries=a.binaries, + datas=a.datas, + text_pos=(10, 50), + text_size=12, + text_color='blue', + minify_script=True, + always_on_top=True + ) -if system() == 'Darwin': + exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + splash, + splash.binaries, + [], + name='Khoj', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch='x86_64', + codesign_identity=None, + entitlements_file=None, + icon='src/interface/web/assets/icons/favicon-144x144.ico', + ) +else: + exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='Khoj', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch='x86_64', + codesign_identity=None, + entitlements_file=None, + icon='src/interface/web/assets/icons/favicon.icns', + ) app = BUNDLE( exe, name='Khoj.app', diff --git a/config/khoj_sample.yml b/config/khoj_sample.yml index 86894c03..ed1a0591 100644 --- a/config/khoj_sample.yml +++ b/config/khoj_sample.yml @@ -22,7 +22,7 @@ content-type: input-filter: # /path/to/images/*.jpg REQUIRED IF input-directories IS NOT SET embeddings-file: "~/.khoj/content/image/image_embeddings.pt" batch-size: 50 - use-xmp-metadata: true + use-xmp-metadata: false music: input-files: # ["/path/to/music-file.org"] REQUIRED IF input-filter IS NOT SET OR diff --git a/src/interface/desktop/file_browser.py b/src/interface/desktop/file_browser.py index fcc2cb29..e14dfd1c 100644 --- a/src/interface/desktop/file_browser.py +++ b/src/interface/desktop/file_browser.py @@ -29,7 +29,7 @@ class FileBrowser(QtWidgets.QWidget): self.lineEdit.textChanged.connect(self.updateFieldHeight) layout.addWidget(self.lineEdit) - self.button = QtWidgets.QPushButton('Select') + self.button = QtWidgets.QPushButton('Add') self.button.clicked.connect(self.storeFilesSelectedInFileDialog) layout.addWidget(self.button) layout.addStretch() @@ -47,7 +47,7 @@ class FileBrowser(QtWidgets.QWidget): return 'Images (*.jp[e]g)' def storeFilesSelectedInFileDialog(self): - filepaths = [] + filepaths = self.getPaths() if self.search_type == SearchType.Image: filepaths.append(QtWidgets.QFileDialog.getExistingDirectory(self, caption='Choose Folder', directory=self.dirpath)) diff --git a/src/interface/desktop/main_window.py b/src/interface/desktop/main_window.py index fbe0fa00..cf4168dd 100644 --- a/src/interface/desktop/main_window.py +++ b/src/interface/desktop/main_window.py @@ -185,7 +185,10 @@ class MainWindow(QtWidgets.QMainWindow): default_search_config = self.get_default_config(search_type = child.search_type) self.new_config['content-type'][child.search_type.value] = merge_dicts(current_search_config, default_search_config) elif isinstance(child, FileBrowser) and child.search_type in self.new_config['content-type']: - self.new_config['content-type'][child.search_type.value]['input-files'] = child.getPaths() if child.getPaths() != [] else None + if child.search_type.value == SearchType.Image: + self.new_config['content-type'][child.search_type.value]['input-directories'] = child.getPaths() if child.getPaths() != [] else None + else: + self.new_config['content-type'][child.search_type.value]['input-files'] = child.getPaths() if child.getPaths() != [] else None def update_processor_settings(self): "Update config with conversation settings from UI" diff --git a/src/interface/web/assets/icons/favicon-144x144.ico b/src/interface/web/assets/icons/favicon-144x144.ico new file mode 100644 index 00000000..9641ed71 Binary files /dev/null and b/src/interface/web/assets/icons/favicon-144x144.ico differ diff --git a/src/main.py b/src/main.py index ef8f90ae..2951b1c2 100644 --- a/src/main.py +++ b/src/main.py @@ -1,7 +1,8 @@ # Standard Packages -from platform import system +import os import signal import sys +from platform import system # External Packages import uvicorn @@ -26,6 +27,9 @@ app.include_router(router) def run(): + # Turn Tokenizers Parallelism Off. App does not support it. + os.environ["TOKENIZERS_PARALLELISM"] = 'false' + # Load config from CLI state.cli_args = sys.argv[1:] args = cli(state.cli_args) @@ -67,6 +71,18 @@ def run(): # Start Application server.start() gui.aboutToQuit.connect(server.terminate) + + # Close Splash Screen if still open + if system() != 'Darwin': + try: + import pyi_splash + # Update the text on the splash screen + pyi_splash.update_text("Khoj setup complete") + # Close Splash Screen + pyi_splash.close() + except: + pass + gui.exec() diff --git a/src/search_type/image_search.py b/src/search_type/image_search.py index 23a64459..b57d4f20 100644 --- a/src/search_type/image_search.py +++ b/src/search_type/image_search.py @@ -211,9 +211,9 @@ def collate_results(hits, image_names, output_directory, image_files_url, count= # Add the image metadata to the results results += [{ "entry": f'{image_files_url}/{target_image_name}', - "score": f"{hit['score']:.3f}", - "image_score": f"{hit['image_score']:.3f}", - "metadata_score": f"{hit['metadata_score']:.3f}", + "score": f"{hit['score']:.9f}", + "image_score": f"{hit['image_score']:.9f}", + "metadata_score": f"{hit['metadata_score']:.9f}", }] return results diff --git a/src/utils/cli.py b/src/utils/cli.py index e855f706..a140bb46 100644 --- a/src/utils/cli.py +++ b/src/utils/cli.py @@ -3,7 +3,7 @@ import argparse import pathlib # Internal Packages -from src.utils.helpers import get_absolute_path, resolve_absolute_path +from src.utils.helpers import resolve_absolute_path from src.utils.yaml import parse_config_from_file diff --git a/src/utils/constants.py b/src/utils/constants.py index 569c5bcb..84c3dfbb 100644 --- a/src/utils/constants.py +++ b/src/utils/constants.py @@ -30,7 +30,7 @@ default_config = { 'input-filter': None, 'embeddings-file': '~/.khoj/content/image/image_embeddings.pt', 'batch-size': 50, - 'use-xmp-metadata': True + 'use-xmp-metadata': False }, 'music': { 'input-files': None, diff --git a/src/utils/helpers.py b/src/utils/helpers.py index 78c2f176..e77e656d 100644 --- a/src/utils/helpers.py +++ b/src/utils/helpers.py @@ -1,5 +1,6 @@ # Standard Packages import pathlib +import sys from os.path import join @@ -54,4 +55,9 @@ def load_model(model_name, model_dir, model_type): if model_path is not None: model.save(model_path) - return model \ No newline at end of file + return model + + +def is_pyinstaller_app(): + "Returns true if the app is running from Native GUI created by PyInstaller" + return getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') \ No newline at end of file