- 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.
99 lines
4.0 KiB
Python
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
|