feat installer and download model on first launch

This commit is contained in:
fsylvain committed 2026-09-19 23:53:40 +02:00
1 parent 1648b44189
commit 5d4e19f24b
6 files changed
+358 -3

No files matched your search

+51 -1
View File
@@ -1,15 +1,22 @@
import json import json
import os import os
import re import re
import ssl
import time import time
import urllib.request
import uuid import uuid
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
import certifi
# CUDA 13.x moved its runtime DLLs to bin/x64, but llama_cpp's loader only # CUDA 13.x moved its runtime DLLs to bin/x64, but llama_cpp's loader only
# checks CUDA_PATH/bin, so point it at the right folder before importing. # checks CUDA_PATH/bin, so point it at the right folder before importing.
# Only relevant when running from source against a system CUDA toolkit - a
# packaged build ships the runtime DLLs next to llama_cpp's own, so this is a
# no-op there (CUDA_PATH won't be set on a machine with just an NVIDIA driver).
_cuda_path = os.environ.get("CUDA_PATH") _cuda_path = os.environ.get("CUDA_PATH")
if _cuda_path: if _cuda_path:
_cuda_bin_x64 = Path(_cuda_path) / "bin" / "x64" _cuda_bin_x64 = Path(_cuda_path) / "bin" / "x64"
@@ -19,7 +26,16 @@ if _cuda_path:
from ddgs import DDGS # noqa: E402 from ddgs import DDGS # noqa: E402
from llama_cpp import Llama # noqa: E402 from llama_cpp import Llama # noqa: E402
MODEL_PATH = Path(__file__).parent / "Qwen3.5-27B-IQ4_XS.gguf" # Model lives outside the install/source directory - a packaged build's own
# folder may be read-only (Program Files) or discarded on update, and the
# model is far too large to embed in an installer, so it's fetched into the
# user's profile on first run instead.
MODEL_FILENAME = "Qwen3.5-27B-IQ4_XS.gguf"
MODELS_DIR = Path.home() / ".dev_assist_chatbot" / "models"
MODEL_PATH = MODELS_DIR / MODEL_FILENAME
MODEL_DOWNLOAD_URL = (
"https://huggingface.co/unsloth/Qwen3.5-27B-GGUF/resolve/main/Qwen3.5-27B-IQ4_XS.gguf"
)
SYSTEM_PROMPT = ( SYSTEM_PROMPT = (
"You are a knowledgeable developer assistant. Answer questions about " "You are a knowledgeable developer assistant. Answer questions about "
@@ -192,6 +208,40 @@ def build_messages(history: list[tuple[str, str]], question: str) -> list[dict[s
return messages return messages
def download_model(on_progress: Callable[[int, int], None] | None = None) -> None:
"""Fetch MODEL_PATH from MODEL_DOWNLOAD_URL if it isn't already present.
Downloads to a .part file alongside the target and renames on success, so
a crash or interrupted download can't leave a truncated file that
load_model() would then try (and fail) to use. If given, on_progress is
invoked with (bytes_downloaded, total_bytes) as each chunk arrives -
total_bytes is 0 if the server doesn't report a Content-Length."""
if MODEL_PATH.exists():
return
MODELS_DIR.mkdir(parents=True, exist_ok=True)
part_path = MODEL_PATH.with_suffix(MODEL_PATH.suffix + ".part")
request = urllib.request.Request(
MODEL_DOWNLOAD_URL, headers={"User-Agent": "dev-assist-chatbot"}
)
# A fresh/sandboxed Windows install may not have fetched the intermediate
# CA cert for huggingface.co's chain yet (normally done transparently via
# Windows Update on first need, which a locked-down or offline sandbox
# can't reach) - use certifi's bundled CA list instead of the OS trust
# store so this doesn't depend on that happening.
ssl_context = ssl.create_default_context(cafile=certifi.where())
with urllib.request.urlopen(request, context=ssl_context) as response:
total = int(response.headers.get("Content-Length", 0))
downloaded = 0
with open(part_path, "wb") as f:
while chunk := response.read(1024 * 1024):
f.write(chunk)
downloaded += len(chunk)
if on_progress is not None:
on_progress(downloaded, total)
part_path.rename(MODEL_PATH)
def load_model() -> Llama: def load_model() -> Llama:
return Llama( return Llama(
model_path=str(MODEL_PATH), model_path=str(MODEL_PATH),
+54 -2
View File
@@ -408,6 +408,23 @@ def _insert_message_card(cursor: QTextCursor, accent: str, bg: str) -> QTextCurs
return body_cell.firstCursorPosition() return body_cell.firstCursorPosition()
class ModelDownloadThread(QThread):
# qint64, not int - int maps to a 32-bit C int for this signal's
# cross-thread queued connection, which silently wraps a ~15GB byte
# count (e.g. 14_977_484_704 truncates to 2_092_582_816).
progress = pyqtSignal("qint64", "qint64")
done = pyqtSignal()
failed = pyqtSignal(str)
def run(self) -> None:
try:
chatbot.download_model(on_progress=self.progress.emit)
except Exception as e:
self.failed.emit(str(e))
return
self.done.emit()
class ModelLoaderThread(QThread): class ModelLoaderThread(QThread):
loaded = pyqtSignal(object) loaded = pyqtSignal(object)
failed = pyqtSignal(str) failed = pyqtSignal(str)
@@ -586,9 +603,44 @@ class ChatWindow(QMainWindow):
self._init_gpu_monitor() self._init_gpu_monitor()
self.statusBar().showMessage("Loading model...")
self._set_input_enabled(False) self._set_input_enabled(False)
self._download_thread: ModelDownloadThread | None = None
self._loader_thread: ModelLoaderThread | None = None
if chatbot.MODEL_PATH.exists():
self._start_model_load()
else:
self._start_model_download()
def _start_model_download(self) -> None:
self.statusBar().showMessage("Downloading model (first run only)... 0%")
self._append_system(
"Downloading the language model for first-time setup. This is a "
"one-time, multi-gigabyte download and may take a while."
)
self._download_thread = ModelDownloadThread()
self._download_thread.progress.connect(self._on_download_progress)
self._download_thread.done.connect(self._on_download_done)
self._download_thread.failed.connect(self._on_model_failed)
self._download_thread.start()
def _on_download_progress(self, downloaded: int, total: int) -> None:
downloaded_gb = downloaded / (1024**3)
if total:
pct = 100 * downloaded / total
total_gb = total / (1024**3)
self.statusBar().showMessage(
f"Downloading model... {downloaded_gb:.2f}/{total_gb:.2f} GB ({pct:.0f}%)"
)
else:
self.statusBar().showMessage(f"Downloading model... {downloaded_gb:.2f} GB")
def _on_download_done(self) -> None:
self._download_thread = None
self._start_model_load()
def _start_model_load(self) -> None:
self.statusBar().showMessage("Loading model...")
self._loader_thread = ModelLoaderThread() self._loader_thread = ModelLoaderThread()
self._loader_thread.loaded.connect(self._on_model_loaded) self._loader_thread.loaded.connect(self._on_model_loaded)
self._loader_thread.failed.connect(self._on_model_failed) self._loader_thread.failed.connect(self._on_model_failed)
@@ -995,7 +1047,7 @@ class ChatWindow(QMainWindow):
# llama.cpp calls run in native code and can't be cancelled mid-call, # llama.cpp calls run in native code and can't be cancelled mid-call,
# so wait for any in-flight load/generation to finish rather than # so wait for any in-flight load/generation to finish rather than
# tearing down the thread while it's still running (which crashes Qt). # tearing down the thread while it's still running (which crashes Qt).
for thread in (self._loader_thread, self._ask_thread): for thread in (self._download_thread, self._loader_thread, self._ask_thread):
if thread is not None and thread.isRunning(): if thread is not None and thread.isRunning():
thread.wait() thread.wait()
if self._gpu_handle is not None: if self._gpu_handle is not None:
+92
View File
@@ -0,0 +1,92 @@
# PyInstaller spec for Dev Assist Chatbot.
#
# Build (from the repo root, in an environment with the CUDA 13.x toolkit
# installed - the toolkit is only needed at BUILD time, to copy its runtime
# DLLs into the bundle; end users only need an NVIDIA driver):
#
# uv run pyinstaller packaging/build.spec --noconfirm
#
# Output lands in dist/DevAssistChatbot/. That whole folder is the app -
# onedir rather than onefile, since unpacking a multi-GB bundle to a temp
# dir on every launch would make startup unusable.
import os
from pathlib import Path
from PyInstaller.utils.hooks import collect_submodules
PROJECT_ROOT = Path(SPECPATH).parent # noqa: F821 - injected by PyInstaller
VENV_SITE_PACKAGES = PROJECT_ROOT / ".venv" / "Lib" / "site-packages"
LLAMA_CPP_LIB = VENV_SITE_PACKAGES / "llama_cpp" / "lib"
if not LLAMA_CPP_LIB.is_dir():
raise SystemExit(
f"Expected llama_cpp's compiled libs at {LLAMA_CPP_LIB} - run this via "
"'uv run pyinstaller ...' from the repo root so the project venv is used."
)
CUDA_PATH = os.environ.get("CUDA_PATH")
if not CUDA_PATH:
raise SystemExit(
"CUDA_PATH is not set. Building requires the CUDA toolkit that "
"llama-cpp-python was compiled against, so its runtime DLLs can be "
"copied into the bundle (end users won't need the toolkit installed)."
)
CUDA_BIN = Path(CUDA_PATH) / "bin" / "x64"
# llama.cpp's ggml backend (ggml-cuda.dll etc.) is loaded dynamically at
# runtime, not linked at import time, so PyInstaller's static analysis can't
# discover it - every DLL in llama_cpp/lib/ has to be added explicitly. They
# all go into the same output folder so Windows' DLL search (which checks a
# loaded DLL's own directory for its dependencies) can resolve cublas from
# next to ggml-cuda.dll.
CUDA_RUNTIME_DLLS = [
"cudart64_13.dll",
"cublas64_13.dll",
"cublasLt64_13.dll",
"nvrtc64_130_0.dll",
"nvrtc-builtins64_134.dll",
]
binaries = [(str(dll), "llama_cpp/lib") for dll in LLAMA_CPP_LIB.glob("*.dll")]
for name in CUDA_RUNTIME_DLLS:
dll_path = CUDA_BIN / name
if not dll_path.is_file():
raise SystemExit(f"Expected CUDA runtime DLL not found: {dll_path}")
binaries.append((str(dll_path), "llama_cpp/lib"))
# ddgs picks its search backend (duckduckgo, bing, ...) via importlib at
# runtime rather than a static import, so PyInstaller won't find those
# engine modules on its own either.
hidden_imports = collect_submodules("ddgs") + ["pynvml"]
a = Analysis( # noqa: F821
[str(PROJECT_ROOT / "main.py")],
pathex=[str(PROJECT_ROOT)],
binaries=binaries,
datas=[],
hiddenimports=hidden_imports,
hookspath=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure) # noqa: F821
exe = EXE( # noqa: F821
pyz,
a.scripts,
[],
exclude_binaries=True,
name="DevAssistChatbot",
console=False,
)
coll = COLLECT( # noqa: F821
exe,
a.binaries,
a.zipfiles,
a.datas,
# UPX would try (slowly, and sometimes badly) to compress the ~650MB of
# CUDA runtime DLLs; skip it.
upx=False,
name="DevAssistChatbot",
)
+55
View File
@@ -0,0 +1,55 @@
; Inno Setup script for Dev Assist Chatbot.
;
; Build the app first (packaging/build.spec), then compile this with the
; Inno Setup Compiler (ISCC.exe) or the IDE, from the repo root:
;
; iscc packaging\installer.iss
;
; Output lands in packaging\Output\DevAssistChatbot-Setup.exe.
#define MyAppName "Dev Assist Chatbot"
#define MyAppVersion "0.1.0"
#define MyAppExeName "DevAssistChatbot.exe"
[Setup]
AppId={{A9F1D6C2-4B3E-4A7F-9C1D-3E5B7A2F8D41}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputDir=Output
OutputBaseFilename=DevAssistChatbot-Setup
Compression=lzma2
SolidCompression=yes
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
WizardStyle=modern
[Tasks]
Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Additional icons:"
[Files]
Source: "..\dist\DevAssistChatbot\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; Optional: bundle the VC++ redistributable for machines that don't already
; have it (most Windows 11 installs do, via Windows Update or another app).
; Download it from https://aka.ms/vs/17/release/vc_redist.x64.exe, place it
; at packaging\redist\vc_redist.x64.exe, then uncomment these two lines and
; the matching [Run] entry below.
; Source: "redist\vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
; Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/install /quiet /norestart"; StatusMsg: "Installing Visual C++ runtime..."; Check: not VCRedistInstalled
Filename: "{app}\{#MyAppExeName}"; Description: "Launch {#MyAppName}"; Flags: nowait postinstall skipifsilent
[Code]
function VCRedistInstalled: Boolean;
begin
// Present once the VC++ 2015-2022 x64 runtime is installed (also true on
// most Windows 11 machines already, via Windows Update or another app).
Result := RegKeyExists(HKLM64, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64');
end;
+3
View File
@@ -5,6 +5,7 @@ description = ""
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"certifi>=2025.1.31",
"ddgs>=9.16.0", "ddgs>=9.16.0",
"llama-cpp-python>=0.3.35", "llama-cpp-python>=0.3.35",
"nvidia-ml-py>=13.610.43", "nvidia-ml-py>=13.610.43",
@@ -14,6 +15,8 @@ dependencies = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"pyinstaller>=6.11",
"pyinstaller-hooks-contrib>=2025.8",
"pytest>=8.3", "pytest>=8.3",
"ruff>=0.8", "ruff>=0.8",
] ]
Generated
+103
View File
@@ -2,6 +2,24 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.12" requires-python = ">=3.12"
[[package]]
name = "altgraph"
version = "0.17.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" },
]
[[package]]
name = "certifi"
version = "2026.7.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.5.0" version = "8.5.0"
@@ -186,6 +204,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/b7/44edd7de434181c582892e68d1ffe6775ca403ce14aea07cb5a218a936cf/lxml-6.1.3-cp315-cp315t-win_arm64.whl", hash = "sha256:5a721a98c649855963811b59b55755b30566e7f7fc40bdc9803d66dee9f811cf", size = 3822836, upload-time = "2026-09-02T14:51:42.471Z" }, { url = "https://files.pythonhosted.org/packages/f8/b7/44edd7de434181c582892e68d1ffe6775ca403ce14aea07cb5a218a936cf/lxml-6.1.3-cp315-cp315t-win_arm64.whl", hash = "sha256:5a721a98c649855963811b59b55755b30566e7f7fc40bdc9803d66dee9f811cf", size = 3822836, upload-time = "2026-09-02T14:51:42.471Z" },
] ]
[[package]]
name = "macholib"
version = "1.16.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" },
]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "3.0.3" version = "3.0.3"
@@ -340,6 +370,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
] ]
[[package]]
name = "pefile"
version = "2024.8.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" },
]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.6.0" version = "1.6.0"
@@ -396,6 +435,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
] ]
[[package]]
name = "pyinstaller"
version = "6.22.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
{ name = "macholib", marker = "sys_platform == 'darwin'" },
{ name = "packaging" },
{ name = "pefile", marker = "sys_platform == 'win32'" },
{ name = "pyinstaller-hooks-contrib" },
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/41/f90302845945abd4ed647933ff5ee7c6ac93983187be67f897b6cb613331/pyinstaller-6.22.3.tar.gz", hash = "sha256:05eb2f5615503e72939a7224d68b4aff572c6b0438ee4a17d0a4b481f399362d", size = 4437180, upload-time = "2026-09-12T21:54:09.669Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/af/898f70947e27f8e65d55183c780e77112e023c0b4a6fba6255d4d563a27d/pyinstaller-6.22.3-py3-none-macosx_10_13_universal2.whl", hash = "sha256:052f4a1cd4f81092ccb7a18fe8ebc9ecf832a5917317e39fb3775af4e959b253", size = 1071847, upload-time = "2026-09-12T21:53:01.711Z" },
{ url = "https://files.pythonhosted.org/packages/c4/96/99f5184985141c2b024bfa61335e9142ae444555224f303186e59b7bb438/pyinstaller-6.22.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:312da84b4b31ab9750066a823734c11b7c68757fdd67c9f038bf19c330a954e4", size = 758172, upload-time = "2026-09-12T21:53:06.171Z" },
{ url = "https://files.pythonhosted.org/packages/69/9f/9ccb12cc1e6617042cb028fe79f0a756f5f03305bd14b584a4e043fe3844/pyinstaller-6.22.3-py3-none-manylinux2014_i686.whl", hash = "sha256:020d4fe0627f5c73dfbcfad1a6578ef354d5fe4bdccd9d8e5a615528f5a45d2b", size = 772135, upload-time = "2026-09-12T21:53:10.291Z" },
{ url = "https://files.pythonhosted.org/packages/c1/43/4b1d5193a88628c052db89f064433681076959a9f41d0d9f660001b49079/pyinstaller-6.22.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:9e4f7bd64b2103f14b4a6240fb9155e9fe7bc61391b4043bbddb7d3ebc8ab0fa", size = 770461, upload-time = "2026-09-12T21:53:14.592Z" },
{ url = "https://files.pythonhosted.org/packages/cb/83/f173f8757c618d83e116fa2b8e57c5cd796e5ad09792e664eabd6ac5edae/pyinstaller-6.22.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:f82d0fb89f21c3ed6f482ee16f341bc59e8a31dbabb9bae9ef04e53ade4dc0d9", size = 764997, upload-time = "2026-09-12T21:53:19.079Z" },
{ url = "https://files.pythonhosted.org/packages/7b/b6/dd21d61fc5e25377be24eabf13e99e4e46548a5abe2bf7b15d8b3d9cc74a/pyinstaller-6.22.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:451a4ae14b719365bf1a2f0a99dae7b3463060061c3a394c70d5264cfb439528", size = 764974, upload-time = "2026-09-12T21:53:23.176Z" },
{ url = "https://files.pythonhosted.org/packages/6c/fd/d9590942972b1fc205519f7ccffb0d5cd8eb1d6fa61edad319c71d25baed/pyinstaller-6.22.3-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:ea240b5dfa831ff673c97505eee0ec24670bccf71396c83109440348944c83d0", size = 764450, upload-time = "2026-09-12T21:53:27.419Z" },
{ url = "https://files.pythonhosted.org/packages/72/d6/f22c55d1768433b65c460b19a3136dd9769b6c74b786149b829344654c7d/pyinstaller-6.22.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a2b6fc26601e8c2596c3d54981566934a3382e85d05d0fe91b55b6a8dcc6b282", size = 763863, upload-time = "2026-09-12T21:53:31.609Z" },
{ url = "https://files.pythonhosted.org/packages/c5/87/cf28cfd42ceca889171e6dd5e7f1553d4372db83d047049ffb7cf95fcbd4/pyinstaller-6.22.3-py3-none-win32.whl", hash = "sha256:46a3e30a118341a1ec20667a5a95aa7e0aa8c4fa129893c0151b3eb3f801a722", size = 1390905, upload-time = "2026-09-12T21:53:38.133Z" },
{ url = "https://files.pythonhosted.org/packages/24/83/c5422204772a27aef643280fdc887b7ef55679989096b21dceb1b9625fc4/pyinstaller-6.22.3-py3-none-win_amd64.whl", hash = "sha256:500bd58c7bf7e584a8435adccbd763a0b918d5c12b08d74ff50fd79b2915458b", size = 1454907, upload-time = "2026-09-12T21:53:44.812Z" },
{ url = "https://files.pythonhosted.org/packages/67/2e/ebc93968bf63f81b122e205da116b16036b653b7ce2cc8ad86e3ddd19586/pyinstaller-6.22.3-py3-none-win_arm64.whl", hash = "sha256:31df3cc4261e804a53d358b7897c293ec8b88b167a7cca06c78018085c855f97", size = 1402956, upload-time = "2026-09-12T21:53:51.369Z" },
]
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2026.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/60/d881fa1ba8c160c18d8e6f782bb16ec4640c08bc08fc50f704c368ad4f9e/pyinstaller_hooks_contrib-2026.7.tar.gz", hash = "sha256:5fbcaacb22c4f4aac869a127dce283f67a4b4cfcc37d496f2446603e6d68aefa", size = 175992, upload-time = "2026-08-24T21:26:58.713Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/67/350377af7b50416344ab8792756d414eef7629c618a73e9a0b13bb1552d9/pyinstaller_hooks_contrib-2026.7-py3-none-any.whl", hash = "sha256:24257a04c7a5a7a034cf28e39dcee20fbeeb9f043076729480f2e1b69904408a", size = 459445, upload-time = "2026-08-24T21:26:57.274Z" },
]
[[package]] [[package]]
name = "pyqt6" name = "pyqt6"
version = "6.11.0" version = "6.11.0"
@@ -470,6 +549,7 @@ name = "python-inference"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "certifi" },
{ name = "ddgs" }, { name = "ddgs" },
{ name = "llama-cpp-python" }, { name = "llama-cpp-python" },
{ name = "nvidia-ml-py" }, { name = "nvidia-ml-py" },
@@ -479,12 +559,15 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "pyinstaller" },
{ name = "pyinstaller-hooks-contrib" },
{ name = "pytest" }, { name = "pytest" },
{ name = "ruff" }, { name = "ruff" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "certifi", specifier = ">=2025.1.31" },
{ name = "ddgs", specifier = ">=9.16.0" }, { name = "ddgs", specifier = ">=9.16.0" },
{ name = "llama-cpp-python", specifier = ">=0.3.35" }, { name = "llama-cpp-python", specifier = ">=0.3.35" },
{ name = "nvidia-ml-py", specifier = ">=13.610.43" }, { name = "nvidia-ml-py", specifier = ">=13.610.43" },
@@ -494,10 +577,21 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "pyinstaller", specifier = ">=6.11" },
{ name = "pyinstaller-hooks-contrib", specifier = ">=2025.8" },
{ name = "pytest", specifier = ">=8.3" }, { name = "pytest", specifier = ">=8.3" },
{ name = "ruff", specifier = ">=0.8" }, { name = "ruff", specifier = ">=0.8" },
] ]
[[package]]
name = "pywin32-ctypes"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.16.7" version = "0.16.7"
@@ -523,6 +617,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/4b/51327018d056f0dad2c2238f26d1fb0f53707a9d91b75dea6d1b3039f136/ruff-0.16.7-py3-none-win_arm64.whl", hash = "sha256:aab7f39e2c9df6c596216070f98eef1207b94f8516cca20c808826974971855b", size = 10412401, upload-time = "2026-09-10T18:04:04.098Z" }, { url = "https://files.pythonhosted.org/packages/8b/4b/51327018d056f0dad2c2238f26d1fb0f53707a9d91b75dea6d1b3039f136/ruff-0.16.7-py3-none-win_arm64.whl", hash = "sha256:aab7f39e2c9df6c596216070f98eef1207b94f8516cca20c808826974971855b", size = 10412401, upload-time = "2026-09-10T18:04:04.098Z" },
] ]
[[package]]
name = "setuptools"
version = "84.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" },
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.16.0" version = "4.16.0"