mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-10 05:39:11 +00:00
Fix Image Search and Improve Desktop App
### Fix Image Search
- Do not use XMP metadata by default for image search
- It seems to be buggy currently. The returned results do not make sense with XMP metadata enabled
### Fix Image Search using Desktop App
- Fix configuring Image Search via Desktop GUI
- Set `input-directories`, instead of unused `input-files` for `content-type.image` in `khoj.yml`
- Fix running Image Search via Desktop apps.
- Previously the transformers wasn't getting packaged into the app by pyinstaller
- This is required by image search to run. So the desktop apps would fail to start when image search was enabled
- Resolves #68
- Append selected files, directories via "Add" button in Desktop GUI
- This allows selecting multiple files, directories using Desktop GUI
- Previously selecting multiple image directories had to be entered manually
### Improve Desktop App
- Show Splash Screen to Desktop on App Initialization
- The app takes a while to load during first run
- A splash screen signals that app is loading and not being unresponsive
- Note: _Pyinstaller only supports splash screens on Windows, Linux. Not on Macs._
- Add Khoj icon to the Windows, Linux app. Windows expects a `.ico` icon type
- Only exclude `libtorch_{cuda, cpu, python}` on Linux machine
- Seems those libraries are being used on Mac (and maybe Windows).
- Linux is where the app size benefits from removing these is maximum anyway
- Fix PyInstaller Warnings on App Start
- The warning show up as annoying error popups on Windows
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||||
sudo apt install libegl1 libxcb-xinerama0 -y
|
sudo apt install libegl1 libxcb-xinerama0 python3-tk -y
|
||||||
fi
|
fi
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install pyinstaller
|
pip install pyinstaller
|
||||||
|
|||||||
59
Khoj.spec
59
Khoj.spec
@@ -2,8 +2,12 @@
|
|||||||
from os.path import join
|
from os.path import join
|
||||||
from platform import system
|
from platform import system
|
||||||
from PyInstaller.utils.hooks import copy_metadata
|
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('tqdm')
|
||||||
datas += copy_metadata('regex')
|
datas += copy_metadata('regex')
|
||||||
datas += copy_metadata('requests')
|
datas += copy_metadata('requests')
|
||||||
@@ -12,10 +16,8 @@ datas += copy_metadata('filelock')
|
|||||||
datas += copy_metadata('numpy')
|
datas += copy_metadata('numpy')
|
||||||
datas += copy_metadata('tokenizers')
|
datas += copy_metadata('tokenizers')
|
||||||
|
|
||||||
|
|
||||||
block_cipher = None
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
a = Analysis(
|
||||||
['src/main.py'],
|
['src/main.py'],
|
||||||
pathex=[],
|
pathex=[],
|
||||||
@@ -32,17 +34,56 @@ a = Analysis(
|
|||||||
noarchive=False,
|
noarchive=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Filter out unused, duplicate shared libs
|
# Filter out unused and/or duplicate shared libs
|
||||||
extension = {'Windows': '.dll', 'Darwin': '.dylib', 'Linux': '.so'}[system()]
|
|
||||||
torch_lib_paths = {
|
torch_lib_paths = {
|
||||||
join('torch', 'lib', 'libtorch_cuda' + extension),
|
join('torch', 'lib', 'libtorch_cuda.so'),
|
||||||
join('torch', 'lib', 'libtorch_cpu' + extension),
|
join('torch', 'lib', 'libtorch_cpu.so'),
|
||||||
join('torch', 'lib', 'libtorch_python' + extension)
|
|
||||||
}
|
}
|
||||||
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 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)
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
exe = EXE(
|
||||||
pyz,
|
pyz,
|
||||||
a.scripts,
|
a.scripts,
|
||||||
@@ -65,8 +106,6 @@ exe = EXE(
|
|||||||
entitlements_file=None,
|
entitlements_file=None,
|
||||||
icon='src/interface/web/assets/icons/favicon.icns',
|
icon='src/interface/web/assets/icons/favicon.icns',
|
||||||
)
|
)
|
||||||
|
|
||||||
if system() == 'Darwin':
|
|
||||||
app = BUNDLE(
|
app = BUNDLE(
|
||||||
exe,
|
exe,
|
||||||
name='Khoj.app',
|
name='Khoj.app',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ content-type:
|
|||||||
input-filter: # /path/to/images/*.jpg REQUIRED IF input-directories IS NOT SET
|
input-filter: # /path/to/images/*.jpg REQUIRED IF input-directories IS NOT SET
|
||||||
embeddings-file: "~/.khoj/content/image/image_embeddings.pt"
|
embeddings-file: "~/.khoj/content/image/image_embeddings.pt"
|
||||||
batch-size: 50
|
batch-size: 50
|
||||||
use-xmp-metadata: true
|
use-xmp-metadata: false
|
||||||
|
|
||||||
music:
|
music:
|
||||||
input-files: # ["/path/to/music-file.org"] REQUIRED IF input-filter IS NOT SET OR
|
input-files: # ["/path/to/music-file.org"] REQUIRED IF input-filter IS NOT SET OR
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class FileBrowser(QtWidgets.QWidget):
|
|||||||
self.lineEdit.textChanged.connect(self.updateFieldHeight)
|
self.lineEdit.textChanged.connect(self.updateFieldHeight)
|
||||||
layout.addWidget(self.lineEdit)
|
layout.addWidget(self.lineEdit)
|
||||||
|
|
||||||
self.button = QtWidgets.QPushButton('Select')
|
self.button = QtWidgets.QPushButton('Add')
|
||||||
self.button.clicked.connect(self.storeFilesSelectedInFileDialog)
|
self.button.clicked.connect(self.storeFilesSelectedInFileDialog)
|
||||||
layout.addWidget(self.button)
|
layout.addWidget(self.button)
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
@@ -47,7 +47,7 @@ class FileBrowser(QtWidgets.QWidget):
|
|||||||
return 'Images (*.jp[e]g)'
|
return 'Images (*.jp[e]g)'
|
||||||
|
|
||||||
def storeFilesSelectedInFileDialog(self):
|
def storeFilesSelectedInFileDialog(self):
|
||||||
filepaths = []
|
filepaths = self.getPaths()
|
||||||
if self.search_type == SearchType.Image:
|
if self.search_type == SearchType.Image:
|
||||||
filepaths.append(QtWidgets.QFileDialog.getExistingDirectory(self, caption='Choose Folder',
|
filepaths.append(QtWidgets.QFileDialog.getExistingDirectory(self, caption='Choose Folder',
|
||||||
directory=self.dirpath))
|
directory=self.dirpath))
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
default_search_config = self.get_default_config(search_type = child.search_type)
|
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)
|
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']:
|
elif isinstance(child, FileBrowser) and child.search_type in self.new_config['content-type']:
|
||||||
|
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
|
self.new_config['content-type'][child.search_type.value]['input-files'] = child.getPaths() if child.getPaths() != [] else None
|
||||||
|
|
||||||
def update_processor_settings(self):
|
def update_processor_settings(self):
|
||||||
|
|||||||
BIN
src/interface/web/assets/icons/favicon-144x144.ico
Normal file
BIN
src/interface/web/assets/icons/favicon-144x144.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
18
src/main.py
18
src/main.py
@@ -1,7 +1,8 @@
|
|||||||
# Standard Packages
|
# Standard Packages
|
||||||
from platform import system
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
from platform import system
|
||||||
|
|
||||||
# External Packages
|
# External Packages
|
||||||
import uvicorn
|
import uvicorn
|
||||||
@@ -26,6 +27,9 @@ app.include_router(router)
|
|||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
# Turn Tokenizers Parallelism Off. App does not support it.
|
||||||
|
os.environ["TOKENIZERS_PARALLELISM"] = 'false'
|
||||||
|
|
||||||
# Load config from CLI
|
# Load config from CLI
|
||||||
state.cli_args = sys.argv[1:]
|
state.cli_args = sys.argv[1:]
|
||||||
args = cli(state.cli_args)
|
args = cli(state.cli_args)
|
||||||
@@ -67,6 +71,18 @@ def run():
|
|||||||
# Start Application
|
# Start Application
|
||||||
server.start()
|
server.start()
|
||||||
gui.aboutToQuit.connect(server.terminate)
|
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()
|
gui.exec()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -211,9 +211,9 @@ def collate_results(hits, image_names, output_directory, image_files_url, count=
|
|||||||
# Add the image metadata to the results
|
# Add the image metadata to the results
|
||||||
results += [{
|
results += [{
|
||||||
"entry": f'{image_files_url}/{target_image_name}',
|
"entry": f'{image_files_url}/{target_image_name}',
|
||||||
"score": f"{hit['score']:.3f}",
|
"score": f"{hit['score']:.9f}",
|
||||||
"image_score": f"{hit['image_score']:.3f}",
|
"image_score": f"{hit['image_score']:.9f}",
|
||||||
"metadata_score": f"{hit['metadata_score']:.3f}",
|
"metadata_score": f"{hit['metadata_score']:.9f}",
|
||||||
}]
|
}]
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import argparse
|
|||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
# Internal Packages
|
# 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
|
from src.utils.yaml import parse_config_from_file
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ default_config = {
|
|||||||
'input-filter': None,
|
'input-filter': None,
|
||||||
'embeddings-file': '~/.khoj/content/image/image_embeddings.pt',
|
'embeddings-file': '~/.khoj/content/image/image_embeddings.pt',
|
||||||
'batch-size': 50,
|
'batch-size': 50,
|
||||||
'use-xmp-metadata': True
|
'use-xmp-metadata': False
|
||||||
},
|
},
|
||||||
'music': {
|
'music': {
|
||||||
'input-files': None,
|
'input-files': None,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Standard Packages
|
# Standard Packages
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import sys
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
|
||||||
|
|
||||||
@@ -55,3 +56,8 @@ def load_model(model_name, model_dir, model_type):
|
|||||||
model.save(model_path)
|
model.save(model_path)
|
||||||
|
|
||||||
return model
|
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')
|
||||||
Reference in New Issue
Block a user