diff --git a/.github/workflows/build_desktop.yml b/.github/workflows/build_desktop.yml deleted file mode 100644 index 845487e8..00000000 --- a/.github/workflows/build_desktop.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: desktop_dev_build - -on: - push: - branches: - - master - paths: - - src/khoj/** - - pyproject.toml - - Khoj.spec - - .github/workflows/build_desktop.yml - workflow_dispatch: - -jobs: - publish_desktop_apps: - name: 🖥️ Publish Desktop Apps - - strategy: - matrix: - include: - - os: ubuntu-20.04 - extension: deb - - os: macos-latest - extension: dmg - - os: windows-latest - extension: exe - - runs-on: ${{ matrix.os }} - permissions: - contents: write - steps: - - uses: actions/checkout@v3 - - - name: Set up Python 3.9 - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: ⏬️ Install Dependencies - shell: bash - run: | - if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt update && sudo apt install libegl1 libxcb-xinerama0 python3-tk -y - fi - python -m pip install --upgrade pip - pip install pyinstaller - - - name: ⬇️ Install Khoj App - run: | - pip install --upgrade . - - - name: 📦 Package Khoj App - shell: bash - run: | - # Setup Environment for Reproducible Builds - export PYTHONHASHSEED=42 - export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) - - pyinstaller --noconfirm Khoj.spec - if [ "$RUNNER_OS" == "Windows" ]; then - mv dist/Khoj.exe dist/khoj_dev_amd64.exe - fi - - - name: 💻 Create Mac App DMG - if: matrix.os == 'macos-latest' - run: | - # Install Mac DMG Creator - brew install create-dmg - # Copy app to separate dmg folder - mkdir -p dist/dmg && cp -r dist/Khoj.app dist/dmg - # Create disk image with the app - create-dmg \ - --volname "Khoj" \ - --volicon "src/khoj/interface/web/assets/icons/favicon.icns" \ - --window-pos 200 120 \ - --window-size 600 300 \ - --icon-size 100 \ - --icon "Khoj.app" 175 120 \ - --hide-extension "Khoj.app" \ - --app-drop-link 425 120 \ - "dist/khoj_dev_amd64.dmg" \ - "dist/dmg/" - - - uses: ruby/setup-ruby@v1 - if: matrix.os == 'ubuntu-20.04' - with: - ruby-version: '3.0' - - - name: 🐧 Create Debian Package - if: matrix.os == 'ubuntu-20.04' - shell: bash - run: | - # Install Debian Packager - gem install fpm - - # Copy app files into expected output directory structure - mkdir -p package/opt package/usr/share/applications package/usr/share/icons/hicolor/128x128/apps - cp -r dist/Khoj package/opt/Khoj - cp src/khoj/interface/web/assets/icons/favicon-128x128.png package/usr/share/icons/hicolor/128x128/apps/Khoj.png - cp Khoj.desktop package/usr/share/applications - - # Fix permissions to be usable by non-root users - find package/usr/share -type f -exec chmod 644 -- {} + - chmod 755 package/opt/Khoj - - # Package the app - fpm -C package -s dir -t deb -n Khoj -p dist/khoj_dev_amd64.deb - - - uses: actions/upload-artifact@v3 - with: - name: khoj_dev_amd64.${{matrix.extension}} - path: dist/khoj_dev_amd64.${{matrix.extension}} - retention-days: 3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 546f13f5..7f261aa8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,111 +64,3 @@ jobs: src/interface/obsidian/main.js src/interface/obsidian/manifest.json src/interface/obsidian/styles.css - - publish_desktop_apps: - name: 🖥️ Publish Desktop Apps - strategy: - matrix: - include: - - os: ubuntu-20.04 - extension: deb - - os: macos-latest - extension: dmg - - os: windows-latest - extension: exe - runs-on: ${{ matrix.os }} - permissions: - contents: write - steps: - - uses: actions/checkout@v3 - - - name: Set up Python 3.9 - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: ⏬️ Install Dependencies - shell: bash - run: | - if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt update && sudo apt install libegl1 libxcb-xinerama0 python3-tk -y - fi - python -m pip install --upgrade pip - pip install pyinstaller - - - name: ⬇️ Install Khoj App - run: | - pip install --upgrade . - - - name: 📦 Package Khoj App - shell: bash - run: | - # Setup Environment for Reproducible Builds - export PYTHONHASHSEED=42 - export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) - - pyinstaller --noconfirm Khoj.spec - if [ "$RUNNER_OS" == "Windows" ]; then - mv dist/Khoj.exe dist/khoj_"$GITHUB_REF_NAME"_amd64.exe - fi - - - name: 💻 Create Mac App DMG - if: matrix.os == 'macos-latest' - run: | - # Install Mac DMG Creator - brew install create-dmg - # Copy app to separate dmg folder - mkdir -p dist/dmg && cp -r dist/Khoj.app dist/dmg - # Create disk image with the app - create-dmg \ - --volname "Khoj" \ - --volicon "src/khoj/interface/web/assets/icons/favicon.icns" \ - --window-pos 200 120 \ - --window-size 600 300 \ - --icon-size 100 \ - --icon "Khoj.app" 175 120 \ - --hide-extension "Khoj.app" \ - --app-drop-link 425 120 \ - "dist/khoj_"$GITHUB_REF_NAME"_amd64.dmg" \ - "dist/dmg/" - - - uses: ruby/setup-ruby@v1 - if: matrix.os == 'ubuntu-20.04' - with: - ruby-version: '3.0' - - name: 🐧 Create Debian Package - if: matrix.os == 'ubuntu-20.04' - shell: bash - env: - DEBIAN_PACKAGE_VERSION: ${{ inputs.version }} - run: | - # Install Debian Packager - gem install fpm - - # Copy app files into expected output directory structure - mkdir -p package/opt package/usr/share/applications package/usr/share/icons/hicolor/128x128/apps - cp -r dist/Khoj package/opt/Khoj - cp src/khoj/interface/web/assets/icons/favicon-128x128.png package/usr/share/icons/hicolor/128x128/apps/Khoj.png - cp Khoj.desktop package/usr/share/applications - - # Fix permissions to be usable by non-root users - find package/usr/share -type f -exec chmod 644 -- {} + - chmod 755 package/opt/Khoj - - # Package the app - if [ -z "$DEBIAN_PACKAGE_VERSION" ]; then - DEBIAN_PACKAGE_VERSION=$(echo $GITHUB_REF_NAME | sed -E 's/v(.*)/\1/g') - fi - fpm -C package -s dir -t deb -n Khoj --version $DEBIAN_PACKAGE_VERSION -p dist/khoj_"$GITHUB_REF_NAME"_amd64.deb - - - uses: actions/upload-artifact@v3 - with: - name: khoj_${{github.ref_name}}_amd64.${{matrix.extension}} - path: dist/khoj_${{github.ref_name}}_amd64.${{matrix.extension}} - - - name: 🌈 Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - generate_release_notes: true - files: dist/khoj_${{github.ref_name}}_amd64.${{matrix.extension}} diff --git a/Khoj.desktop b/Khoj.desktop deleted file mode 100644 index b3a1cf75..00000000 --- a/Khoj.desktop +++ /dev/null @@ -1,7 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Khoj -Comment=An AI personal assistant for your Digital Brain -Path=/opt -Exec=/opt/Khoj -Icon=Khoj diff --git a/Khoj.spec b/Khoj.spec deleted file mode 100644 index cf3e790d..00000000 --- a/Khoj.spec +++ /dev/null @@ -1,123 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- -from os.path import join -from platform import system -from PyInstaller.utils.hooks import copy_metadata -import sysconfig - -datas = [ - ('src/khoj/interface/web', 'khoj/interface/web'), - (f'{sysconfig.get_paths()["purelib"]}/transformers', 'transformers'), - (f'{sysconfig.get_paths()["purelib"]}/langchain', 'langchain'), - (f'{sysconfig.get_paths()["purelib"]}/PIL', 'PIL'), - (f'{sysconfig.get_paths()["purelib"]}/gpt4all', 'gpt4all'), -] -datas += copy_metadata('torch') -datas += copy_metadata('tqdm') -datas += copy_metadata('regex') -datas += copy_metadata('requests') -datas += copy_metadata('packaging') -datas += copy_metadata('filelock') -datas += copy_metadata('numpy') -datas += copy_metadata('tokenizers') -datas += copy_metadata('pillow') -datas += copy_metadata('huggingface_hub') -datas += copy_metadata('safetensors') -datas += copy_metadata('pyyaml') - -block_cipher = None - -a = Analysis( - ['src/khoj/main.py'], - pathex=[], - binaries=[], - datas=datas, - hiddenimports=['huggingface_hub.repository', 'PIL', 'PIL._tkinter_finder', 'tiktoken_ext', 'tiktoken_ext.openai_public'], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False, -) - -# Filter out unused and/or duplicate shared libs -torch_lib_paths = { - 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] - -os_path_separator = '\\' if system() == 'Windows' else '/' -a.datas = [entry for entry in a.datas if not f'torch{os_path_separator}_C.cp' in entry[0]] -a.datas = [entry for entry in a.datas if not f'torch{os_path_separator}_dl.cp' in entry[0]] - -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) - -if system() != 'Darwin': - # Add Splash screen to show on app launch - splash = Splash( - 'src/khoj/interface/web/assets/icons/favicon-128x128.png', - binaries=a.binaries, - datas=a.datas, - text_pos=(10, 160), - text_size=12, - text_color='black', - 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/khoj/interface/web/assets/icons/favicon-128x128.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/khoj/interface/web/assets/icons/favicon.icns', - ) - app = BUNDLE( - exe, - name='Khoj.app', - icon='src/khoj/interface/web/assets/icons/favicon.icns', - bundle_identifier=None, - ) diff --git a/docs/setup.md b/docs/setup.md index 25890126..4354678c 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -27,7 +27,7 @@ For more detailed Windows installation and troubleshooting, see [Windows Install Run the following command from your terminal to start the Khoj backend and open Khoj in your browser. ```shell -khoj --gui +khoj ``` Note: To start Khoj automatically in the background use [Task scheduler](https://www.windowscentral.com/how-create-automated-task-using-task-scheduler-windows-10) on Windows or [Cron](https://en.wikipedia.org/wiki/Cron) on Mac, Linux (e.g with `@reboot khoj`) @@ -73,6 +73,9 @@ pip install --upgrade --pre khoj-assistant ## Uninstall 1. (Optional) Hit `Ctrl-C` in the terminal running the khoj server to stop it 2. Delete the khoj directory in your home folder (i.e `~/.khoj` on Linux, Mac or `C:\Users\\.khoj` on Windows) +5. You might want to `rm -rf` the following directories: +- `~/.khoj` +- `~/.cache/gpt4all` 3. Uninstall the khoj server with `pip uninstall khoj-assistant` 4. (Optional) Uninstall khoj.el or the khoj obsidian plugin in the standard way on Emacs, Obsidian diff --git a/manifest.json b/manifest.json index a147b97a..d048deeb 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "version": "0.11.4", "minAppVersion": "0.15.0", "description": "An AI Personal Assistant for your Digital Brain", - "author": "Debanjum Singh Solanky", - "authorUrl": "https://github.com/debanjum", + "author": "Khoj", + "authorUrl": "https://github.com/khoj-ai/", "isDesktopOnly": true } diff --git a/pyproject.toml b/pyproject.toml index 688992e1..20a67505 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,6 @@ dependencies = [ "tenacity >= 8.2.2", "pillow == 9.3.0", "pydantic >= 1.10.10", - "pyside6 >= 6.5.1", "pyyaml == 6.0", "rich >= 13.3.1", "schedule == 1.1.0", diff --git a/src/khoj/interface/desktop/__init__.py b/src/khoj/interface/desktop/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/khoj/interface/desktop/main_window.py b/src/khoj/interface/desktop/main_window.py deleted file mode 100644 index 45f82701..00000000 --- a/src/khoj/interface/desktop/main_window.py +++ /dev/null @@ -1,75 +0,0 @@ -# Standard Packages -import webbrowser -import os -import signal - -# External Packages -from PySide6 import QtGui, QtWidgets -from PySide6.QtCore import Qt - -# Internal Packages -from khoj.utils import constants -from PySide6.QtCore import QThread - - -class ServerThread(QThread): - def __init__(self, start_server_func, parent=None): - super(ServerThread, self).__init__(parent) - self.start_server_func = start_server_func - - def __del__(self): - self.wait() - - def run(self): - self.start_server_func() - - def exit(self): - os.kill(os.getpid(), signal.SIGTERM) - super(ServerThread, self).exit() - - -class MainWindow(QtWidgets.QMainWindow): - """Create Window to Navigate users to the web UI""" - - def __init__(self, host: str, port: int): - super(MainWindow, self).__init__() - - # Initialize Configure Window - self.setWindowTitle("Khoj") - - # Set Window Icon - icon_path = constants.web_directory / "assets/icons/favicon-128x128.png" - self.setWindowIcon(QtGui.QIcon(f"{icon_path.absolute()}")) - - # Initialize Configure Window Layout - self.wlayout = QtWidgets.QVBoxLayout() - - # Add a Label that says "Khoj Configuration" to the Window - self.wlayout.addWidget(QtWidgets.QLabel("Welcome to Khoj")) - - # Add a Button to open the Web UI at http://host:port/config - self.open_web_ui_button = QtWidgets.QPushButton("Open Web UI") - self.open_web_ui_button.clicked.connect(lambda: webbrowser.open(f"http://{host}:{port}/config")) - - self.wlayout.addWidget(self.open_web_ui_button) - - # Set the central widget of the Window. Widget will expand - # to take up all the space in the window by default. - self.config_window = QtWidgets.QWidget() - self.config_window.setLayout(self.wlayout) - self.setCentralWidget(self.config_window) - self.position_window() - - def position_window(self): - "Position the window at center of X axis and near top on Y axis" - window_rectangle = self.geometry() - screen_center = self.screen().availableGeometry().center() - window_rectangle.moveCenter(screen_center) - self.move(window_rectangle.topLeft().x(), 25) - - def show_on_top(self): - "Bring Window on Top" - self.show() - self.setWindowState(Qt.WindowState.WindowActive) - self.activateWindow() # For Bringing to Top on Windows - self.raise_() # For Bringing to Top from Minimized State on OSX diff --git a/src/khoj/interface/desktop/system_tray.py b/src/khoj/interface/desktop/system_tray.py deleted file mode 100644 index 73fb0b7c..00000000 --- a/src/khoj/interface/desktop/system_tray.py +++ /dev/null @@ -1,43 +0,0 @@ -# Standard Packages -import webbrowser - -# External Packages -from PySide6 import QtGui, QtWidgets - -# Internal Packages -from khoj.utils import constants, state -from khoj.interface.desktop.main_window import MainWindow - - -def create_system_tray(gui: QtWidgets.QApplication, main_window: MainWindow): - """Create System Tray with Menu. Menu contain options to - 1. Open Search Page on the Web Interface - 2. Open App Configuration Screen - 3. Quit Application - """ - - # Create the system tray with icon - icon_path = constants.web_directory / "assets/icons/favicon-128x128.png" - icon = QtGui.QIcon(f"{icon_path.absolute()}") - tray = QtWidgets.QSystemTrayIcon(icon) - tray.setVisible(True) - - # Create the menu and menu actions - menu = QtWidgets.QMenu() - menu_actions = [ - ("Search", lambda: webbrowser.open(f"http://{state.host}:{state.port}/")), - ("Configure", lambda: webbrowser.open(f"http://{state.host}:{state.port}/config")), - ("App", main_window.show), - ("Quit", gui.quit), - ] - - # Add the menu actions to the menu - for action_text, action_function in menu_actions: - menu_action = QtGui.QAction(action_text, menu) - menu_action.triggered.connect(action_function) # type: ignore[attr-defined] - menu.addAction(menu_action) - - # Add the menu to the system tray - tray.setContextMenu(menu) - - return tray diff --git a/src/khoj/main.py b/src/khoj/main.py index e586e86e..ab88480d 100644 --- a/src/khoj/main.py +++ b/src/khoj/main.py @@ -1,6 +1,5 @@ # Standard Packages import os -import signal import sys import locale @@ -12,8 +11,6 @@ if sys.stderr is None: import logging import threading import warnings -from platform import system -import webbrowser from importlib.metadata import version # Ignore non-actionable warnings @@ -70,81 +67,13 @@ def run(): logger.info("🌘 Starting Khoj") - if not args.gui: - # Setup task scheduler - poll_task_scheduler() + # Setup task scheduler + poll_task_scheduler() - # Start Server - configure_routes(app) - initialize_server(args.config, required=False) - start_server(app, host=args.host, port=args.port, socket=args.socket) - else: - from PySide6 import QtWidgets - from PySide6.QtCore import QTimer - - from khoj.interface.desktop.main_window import MainWindow, ServerThread - from khoj.interface.desktop.system_tray import create_system_tray - - # Setup GUI - gui = QtWidgets.QApplication([]) - main_window = MainWindow(args.host, args.port) - - # System tray is only available on Windows, MacOS. - # On Linux (Gnome) the System tray is not supported. - # Since only the Main Window is available - # Quitting it should quit the application - if system() in ["Windows", "Darwin"]: - gui.setQuitOnLastWindowClosed(False) - tray = create_system_tray(gui, main_window) - tray.show() - - # Setup Server - initialize_server(args.config, required=False) - configure_routes(app) - server = ServerThread(start_server_func=lambda: start_server(app, host=args.host, port=args.port), parent=gui) - - url = f"http://{args.host}:{args.port}" - logger.info(f"🌗 Khoj is running at {url}") - try: - startup_url = url if args.config else f"{url}/config" - webbrowser.open(startup_url) - except: - logger.warning(f"🚧 Unable to open browser. Please open {url} manually to configure or use Khoj.") - - # Show Main Window on First Run Experience or if on Linux - if args.config is None or system() not in ["Windows", "Darwin"]: - main_window.show() - - # Setup Signal Handlers - signal.signal(signal.SIGINT, sigint_handler) - # Invoke Python interpreter every 500ms to handle signals, run scheduled tasks - timer = QTimer() - timer.start(500) - timer.timeout.connect(schedule.run_pending) - - # Start Application - server.start() - gui.aboutToQuit.connect(server.exit) - - # 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() - - -def sigint_handler(*args): - from PySide6 import QtWidgets - - QtWidgets.QApplication.quit() + # Start Server + configure_routes(app) + initialize_server(args.config, required=False) + start_server(app, host=args.host, port=args.port, socket=args.socket) def set_state(args): @@ -171,12 +100,3 @@ def poll_task_scheduler(): timer_thread.daemon = True timer_thread.start() schedule.run_pending() - - -def run_gui(): - sys.argv += ["--gui"] - run() - - -if __name__ == "__main__": - run_gui() diff --git a/src/khoj/utils/cli.py b/src/khoj/utils/cli.py index 787289fe..78a9ccf9 100644 --- a/src/khoj/utils/cli.py +++ b/src/khoj/utils/cli.py @@ -17,7 +17,6 @@ def cli(args=None): parser.add_argument( "--config-file", "-c", default="~/.khoj/khoj.yml", type=pathlib.Path, help="YAML file to configure Khoj" ) - parser.add_argument("--gui", action="store_true", default=False, help="Show native desktop GUI. Default: false") parser.add_argument( "--regenerate", action="store_true", diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index fcdca7cb..f8977043 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -106,11 +106,6 @@ def load_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") - - def get_class_by_name(name: str) -> object: "Returns the class object from name string" module_name, class_name = name.rsplit(".", 1) diff --git a/tests/test_cli.py b/tests/test_cli.py index fa9a991e..9de3a853 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -16,7 +16,6 @@ def test_cli_minimal_default(): # Assert assert actual_args.config_file == resolve_absolute_path(Path("~/.khoj/khoj.yml")) assert actual_args.regenerate == False - assert actual_args.gui == False assert actual_args.verbose == 0 @@ -36,11 +35,10 @@ def test_cli_invalid_config_file_path(): # ---------------------------------------------------------------------------------------------------- def test_cli_config_from_file(): # Act - actual_args = cli(["-c=tests/data/config.yml", "--regenerate", "--gui", "-vvv"]) + actual_args = cli(["-c=tests/data/config.yml", "--regenerate", "-vvv"]) # Assert assert actual_args.config_file == resolve_absolute_path(Path("tests/data/config.yml")) - assert actual_args.gui == True assert actual_args.regenerate == True assert actual_args.config is not None assert actual_args.verbose == 3