Files
LocalMind/src-python/app/model_downloader.py
T
Evandro Fonseca Junior 08e154894a feat: enhance application functionality and telemetry monitoring
- Added new scripts in `package.json` for Python dependency installation and sidecar compilation.
- Integrated telemetry fetching in `src/App.tsx` to monitor CPU, RAM, GPU, and disk usage.
- Implemented model downloading functionality with progress tracking in `src/App.tsx`.
- Updated `Footer` and `Header` components to display telemetry data and settings options.
- Enhanced Tauri configuration for sidecar execution and application window settings.
- Updated Python requirements in `requirements.txt` to include additional dependencies for improved functionality.
2026-01-14 23:57:14 -03:00

99 lines
4.0 KiB
Python

"""
Módulo de download de modelos GGUF do HuggingFace com progresso em tempo real.
Utiliza huggingface_hub com callbacks para reportar progresso via async generator.
"""
import os
from pathlib import Path
from typing import AsyncGenerator, Dict
from huggingface_hub import hf_hub_download
import asyncio
class ModelDownloader:
"""Gerencia downloads de modelos com progresso assíncrono."""
def __init__(self, models_dir: str = "./src-python/app/models"):
self.models_dir = Path(models_dir)
self.models_dir.mkdir(parents=True, exist_ok=True)
self._downloads: Dict[str, float] = {} # Cache de progresso por modelo
def get_model_path(self, repo_id: str, filename: str) -> Path:
"""Retorna o caminho esperado do modelo."""
# huggingface_hub salva em cache_dir/repo_id/filename
# Simplificamos para models_dir/repo_id/filename
repo_name = repo_id.split("/")[-1]
return self.models_dir / repo_name / filename
async def download_with_progress(
self,
repo_id: str,
filename: str
) -> AsyncGenerator[Dict, None]:
"""
Download assíncrono com progresso via callback.
Gera eventos: {"type": "progress", "percent": float} ou {"type": "complete"}
"""
model_path = self.get_model_path(repo_id, filename)
# Verifica se já existe
if model_path.exists():
yield {"type": "complete", "percent": 100, "path": str(model_path)}
return
# Callback para progresso
last_percent = 0
def progress_callback(downloaded: int, total: int):
nonlocal last_percent
if total > 0:
percent = min(100, int((downloaded / total) * 100))
if percent != last_percent:
last_percent = percent
# Armazena para acesso assíncrono
self._downloads[f"{repo_id}/{filename}"] = percent
try:
# Executa download em thread separada para não bloquear
loop = asyncio.get_event_loop()
def run_download():
return hf_hub_download(
repo_id=repo_id,
filename=filename,
local_dir=str(self.models_dir),
local_dir_use_symlinks=False,
resume_download=True,
# huggingface_hub não expõe callback direto, então monitoramos o arquivo
)
# Inicia download em executor
download_task = loop.run_in_executor(None, run_download)
# Monitora progresso enquanto download ocorre
while not download_task.done():
key = f"{repo_id}/{filename}"
percent = self._downloads.get(key, 0)
# Tenta estimar progresso pelo tamanho do arquivo parcial
partial_path = self.get_model_path(repo_id, filename)
if partial_path.exists():
# Estima baseado no tamanho (aproximado, pois não temos total exato)
size_mb = partial_path.stat().st_size / (1024 * 1024)
# Assumindo ~1GB para modelos pequenos (ajustar conforme necessário)
estimated_total_mb = 1200 # ~1.2GB
if size_mb < estimated_total_mb:
percent = min(95, int((size_mb / estimated_total_mb) * 100))
yield {"type": "progress", "percent": percent}
await asyncio.sleep(0.5) # Atualiza a cada 500ms
# Download completo
final_path = download_task.result()
yield {"type": "complete", "percent": 100, "path": final_path}
# Limpa cache
self._downloads.pop(f"{repo_id}/{filename}", None)
except Exception as e:
yield {"type": "error", "message": str(e)}
raise