diff --git a/SECURITY.md b/SECURITY.md
index d4927ee..8accd5f 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,10 +4,10 @@ ## Supported Versions
| Version | Supported |
|-----------------|--------------------|
-| stable (v1.9.x) | :white_check_mark: |
+| stable (v2.0.x) | :white_check_mark: |
Beta versions are not officially supported and may contain unknown security vulnerabilities. Use them at your own risk.
## Reporting a Vulnerability
-Use the Issues tab, or for severe vulnerabilities please contact the maintainers via email.
+Use the Issues tab, or for severe vulnerabilities, please contact the maintainers via email.
diff --git a/requirements.txt b/requirements.txt
index 9a4f043..ec31100 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,8 +6,9 @@ safetensors~=0.5.3
numpy<2.0.0
torch~=2.7.0
sentencepiece~=0.2.0
-setuptools~=80.4.0
-huggingface-hub~=0.31.1
+setuptools~=80.7.1
+huggingface-hub~=0.31.2
transformers~=4.51.3
fastapi~=0.115.12
uvicorn~=0.34.2
+certifi~=2025.4.26
diff --git a/src/AutoGGUF.py b/src/AutoGGUF.py
index 12709e9..e704931 100644
--- a/src/AutoGGUF.py
+++ b/src/AutoGGUF.py
@@ -2,6 +2,8 @@
import shutil
import urllib.error
import urllib.request
+import certifi
+import ssl
from datetime import datetime
from functools import partial, wraps
from typing import List
@@ -1148,7 +1150,10 @@ def check_for_updates(self) -> None:
url = "https://api.github.com/repos/leafspark/AutoGGUF/releases/latest"
req = urllib.request.Request(url)
- with urllib.request.urlopen(req) as response:
+ # Create SSL context with certifi certificates
+ ssl_context = ssl.create_default_context(cafile=certifi.where())
+
+ with urllib.request.urlopen(req, context=ssl_context) as response:
if response.status != 200:
raise urllib.error.HTTPError(
url, response.status, "HTTP Error", response.headers, None
diff --git a/src/CustomTitleBar.py b/src/CustomTitleBar.py
index 631d826..b76f4f4 100644
--- a/src/CustomTitleBar.py
+++ b/src/CustomTitleBar.py
@@ -98,7 +98,7 @@ def mouseMoveEvent(self, event) -> None:
def mouseReleaseEvent(self, event) -> None:
self.pressing = False
- def toggle_maximize(self):
+ def toggle_maximize(self) -> None:
if self.isMaximized:
self.parent.showNormal()
if self.normal_size:
diff --git a/src/DownloadThread.py b/src/DownloadThread.py
index 2d56fa8..3664e0b 100644
--- a/src/DownloadThread.py
+++ b/src/DownloadThread.py
@@ -2,6 +2,8 @@
import urllib.request
import urllib.error
import zipfile
+import ssl
+import certifi
from PySide6.QtCore import QThread, Signal
@@ -19,7 +21,10 @@ def run(self) -> None:
try:
req = urllib.request.Request(self.url)
- with urllib.request.urlopen(req) as response:
+ # Create SSL context with certifi certificates
+ ssl_context = ssl.create_default_context(cafile=certifi.where())
+
+ with urllib.request.urlopen(req, context=ssl_context) as response:
if response.status != 200:
raise urllib.error.HTTPError(
self.url, response.status, "HTTP Error", response.headers, None
diff --git a/src/KVOverrideEntry.py b/src/KVOverrideEntry.py
index 9963354..2dd927a 100644
--- a/src/KVOverrideEntry.py
+++ b/src/KVOverrideEntry.py
@@ -22,6 +22,7 @@ def __init__(self, parent=None) -> None:
self.key_input = QLineEdit()
self.key_input.setPlaceholderText("Key")
+
# Set validator for key input (letters and dots only)
key_validator = QRegularExpressionValidator(QRegularExpression(r"[A-Za-z.]+"))
self.key_input.setValidator(key_validator)
diff --git a/src/Localizations.py b/src/Localizations.py
index 0dfadaa..a2097e6 100644
--- a/src/Localizations.py
+++ b/src/Localizations.py
@@ -365,7 +365,7 @@ def __init__(self):
self.ADDING_LORA_ADAPTER = "Adding LoRA Adapter..."
self.DELETING_LORA_ADAPTER = "Deleting LoRA Adapter..."
self.SELECT_LORA_ADAPTER_FILE = "Select LoRA Adapter File"
- self.STARTING_LORA_EXPORT = "Starting LoRA export..."
+ self.STARTING_LORA_EXPORT = "Starting LoRA export"
self.SELECT_OUTPUT_TYPE = "Select Output Type (GGUF or GGML)"
self.BASE_MODEL = "Base Model"
self.SELECT_BASE_MODEL_FILE = "Select Base Model File (GGUF)"
diff --git a/src/ModelInfoDialog.py b/src/ModelInfoDialog.py
index d2b0245..1215894 100644
--- a/src/ModelInfoDialog.py
+++ b/src/ModelInfoDialog.py
@@ -24,8 +24,21 @@ def __init__(self, model_info, parent=None) -> None:
def format_model_info(self, model_info) -> str:
html = "
Model Information
"
html += f"Architecture: {model_info.get('architecture', 'N/A')}
"
- html += f"Quantization Type: {model_info.get('quantization_type', 'N/A')}
"
- html += f"KV Pairs: {model_info.get('kv_pairs', 'N/A')}
"
+
+ # Format quantization types
+ quant_types = model_info.get("quantization_type", [])
+ if quant_types:
+ # Clean up the format: remove "- type " prefix and join with " | "
+ formatted_types = []
+ for qtype in quant_types:
+ # Remove "- type " prefix if present
+ clean_type = qtype.replace("- type ", "").strip()
+ formatted_types.append(clean_type)
+ quant_display = " | ".join(formatted_types)
+ else:
+ quant_display = "N/A"
+
+ html += f"Quantization Type: {quant_display}
"
html += f"Tensors: {model_info.get('tensors', 'N/A')}
"
html += "Key-Value Pairs:
"
diff --git a/src/QuantizationThread.py b/src/QuantizationThread.py
index 8222204..89c9ae3 100644
--- a/src/QuantizationThread.py
+++ b/src/QuantizationThread.py
@@ -59,6 +59,34 @@ def run(self) -> None:
self.error_signal.emit(str(e))
def parse_model_info(self, line) -> None:
+ # Mapping of technical keys to human-readable names
+ key_mappings = {
+ "general.architecture": "Architecture",
+ "general.name": "Model Name",
+ "general.file_type": "File Type",
+ "general.quantization_version": "Quantization Version",
+ "llama.block_count": "Layers",
+ "llama.context_length": "Context Length",
+ "llama.embedding_length": "Embedding Size",
+ "llama.feed_forward_length": "Feed Forward Length",
+ "llama.attention.head_count": "Attention Heads",
+ "llama.attention.head_count_kv": "Key-Value Heads",
+ "llama.attention.layer_norm_rms_epsilon": "RMS Norm Epsilon",
+ "llama.rope.freq_base": "RoPE Frequency Base",
+ "llama.rope.dimension_count": "RoPE Dimensions",
+ "llama.vocab_size": "Vocabulary Size",
+ "tokenizer.ggml.model": "Tokenizer Model",
+ "tokenizer.ggml.pre": "Tokenizer Preprocessing",
+ "tokenizer.ggml.tokens": "Tokens",
+ "tokenizer.ggml.token_type": "Token Types",
+ "tokenizer.ggml.merges": "BPE Merges",
+ "tokenizer.ggml.bos_token_id": "Begin of Sequence Token ID",
+ "tokenizer.ggml.eos_token_id": "End of Sequence Token ID",
+ "tokenizer.chat_template": "Chat Template",
+ "tokenizer.ggml.padding_token_id": "Padding Token ID",
+ "tokenizer.ggml.unk_token_id": "Unknown Token ID",
+ }
+
# Parse output for model information
if "llama_model_loader: loaded meta data with" in line:
parts = line.split()
@@ -66,10 +94,25 @@ def parse_model_info(self, line) -> None:
self.model_info["tensors"] = parts[9]
elif "general.architecture" in line:
self.model_info["architecture"] = line.split("=")[-1].strip()
- elif line.startswith("llama_model_loader: - kv"):
- key = line.split(":")[2].strip()
- value = line.split("=")[-1].strip()
- self.model_info.setdefault("kv_data", {})[key] = value
+ elif line.startswith("llama_model_loader: - kv") and "=" in line:
+ # Split on '=' and take the parts
+ parts = line.split("=", 1) # Split only on first '='
+ left_part = parts[0].strip()
+ value = parts[1].strip()
+
+ # Extract key and type from left part
+ # Format: "llama_model_loader: - kv N: key type"
+ kv_parts = left_part.split(":")
+ if len(kv_parts) >= 3:
+ key_type_part = kv_parts[2].strip() # This is "key type"
+ key = key_type_part.rsplit(" ", 1)[
+ 0
+ ] # Everything except last word (type)
+
+ # Use human-readable name if available, otherwise use original key
+ display_key = key_mappings.get(key, key)
+
+ self.model_info.setdefault("kv_data", {})[display_key] = value
elif line.startswith("llama_model_loader: - type"):
parts = line.split(":")
if len(parts) > 1:
diff --git a/src/TaskListItem.py b/src/TaskListItem.py
index 826abff..8bf0b02 100644
--- a/src/TaskListItem.py
+++ b/src/TaskListItem.py
@@ -95,13 +95,10 @@ def show_task_context_menu(self, position) -> None:
def show_task_properties(self, item) -> None:
self.logger.debug(SHOWING_PROPERTIES_FOR_TASK.format(item.text()))
- task_item = self.task_list.itemWidget(item)
for thread in self.quant_threads:
- if thread.log_file == task_item.log_file:
- model_info_dialog = ModelInfoDialog(thread.model_info, self)
-
- model_info_dialog.exec()
- break
+ model_info_dialog = ModelInfoDialog(thread.model_info, self)
+ model_info_dialog.exec()
+ break
def cancel_task(self, item) -> None:
# TODO: fix possibly buggy signal behavior
diff --git a/src/globals.py b/src/globals.py
index 97af270..42f7f02 100644
--- a/src/globals.py
+++ b/src/globals.py
@@ -1,7 +1,7 @@
import os
import re
import sys
-from typing import Any, List, TextIO, Union
+from typing import Any, IO, List, TextIO, Union
from PySide6.QtWidgets import (
QMessageBox,
@@ -86,9 +86,9 @@ def show_about(self) -> None:
A tool for managing and converting GGUF models.
This application is licensed under the Apache License 2.0.
-Copyright (c) 2025 leafspark.
+Copyright (c) 2024-2025 leafspark.
It also utilizes llama.cpp, licensed under the MIT License.
-Copyright (c) 2023-2024 The ggml authors."""
+Copyright (c) 2023-2025 The ggml authors."""
QMessageBox.about(self, "About AutoGGUF", about_text)
@@ -97,7 +97,7 @@ def ensure_directory(path) -> None:
os.makedirs(path)
-def open_file_safe(file_path, mode="r") -> TextIO:
+def open_file_safe(file_path, mode="r") -> IO[Any]:
encodings = ["utf-8", "latin-1", "ascii", "utf-16"]
for encoding in encodings:
try:
diff --git a/src/ui_update.py b/src/ui_update.py
index e53b7ca..45ac2c4 100644
--- a/src/ui_update.py
+++ b/src/ui_update.py
@@ -159,7 +159,9 @@ def update_cuda_backends(self) -> None:
for item in os.listdir(llama_bin):
item_path = os.path.join(llama_bin, item)
if os.path.isdir(item_path) and "cudart-llama" not in item.lower():
- if "cu1" in item.lower(): # Only include CUDA-capable backends
+ if (
+ "cu1" in item.lower() or "cuda-1" in item.lower()
+ ): # Only include CUDA-capable backends
self.backend_combo_cuda.addItem(item, userData=item_path)
if self.backend_combo_cuda.count() == 0:
diff --git a/src/utils.py b/src/utils.py
index 186488e..32bc284 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -1,6 +1,10 @@
from typing import Any, Union
-import requests
+import urllib.request
+import urllib.error
+import json
+import ssl
+import certifi
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QFileDialog, QInputDialog, QMenu
@@ -188,16 +192,28 @@ def refresh_releases(self) -> None:
owner, repo = get_repo_from_env()
url = f"https://api.github.com/repos/{owner}/{repo}/releases"
- response = requests.get(url)
- response.raise_for_status()
+ # Create SSL context with certifi certificates
+ ssl_context = ssl.create_default_context(cafile=certifi.where())
+
+ # Create request
+ req = urllib.request.Request(url)
+
+ # Make the request
+ with urllib.request.urlopen(req, context=ssl_context) as response:
+ if response.status != 200:
+ raise urllib.error.HTTPError(
+ url, response.status, "HTTP Error", response.headers, None
+ )
+
+ releases = json.loads(response.read().decode("utf-8"))
- releases = response.json()
self.release_combo.clear()
for release in releases:
self.release_combo.addItem(release["tag_name"], userData=release)
self.release_combo.currentIndexChanged.connect(self.update_assets)
self.update_assets()
+
except ValueError as e:
show_error(self.logger, f"Invalid repository configuration: {str(e)}")
- except requests.exceptions.RequestException as e:
+ except (urllib.error.URLError, urllib.error.HTTPError) as e:
show_error(self.logger, ERROR_FETCHING_RELEASES.format(str(e)))