chore: expurgo de blobs excedentes e realinhamento de estado
release.yml (push) Invalid workflow file

This commit is contained in:
Evandro Fonseca Junior committed 2026-02-22 14:06:01 -03:00
1 parent 593efd42e0
commit a7cbd1a3b4
114 files changed
+20716 -4258

No files matched your search

+603
View File
@@ -0,0 +1,603 @@
use std::process::Command;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use tauri::{AppHandle, Manager, Emitter};
use tokio::time::sleep;
use futures_util::StreamExt;
// Importar funções do módulo principal
use crate::{check_ollama_installed, check_ollama_running, poll_ollama_ready, check_ollama_installed_async, check_if_model_installed};
#[derive(serde::Serialize, Clone)]
pub struct SetupState {
pub ollama_installed: bool,
pub ollama_running: bool,
pub default_model_downloaded: bool,
pub default_model_name: Option<String>,
}
/// Verifica o estado atual do setup
pub async fn get_setup_state(_app_handle: &AppHandle) -> Result<SetupState, String> {
let ollama_installed = check_ollama_installed();
let ollama_running = if ollama_installed {
check_ollama_running().await
} else {
false
};
// Verificar se modelo padrão está instalado
let default_model_name = Some("llama3.2:1b".to_string());
let default_model_downloaded = if ollama_running {
check_if_model_installed(default_model_name.as_ref().unwrap().clone())
} else {
false
};
Ok(SetupState {
ollama_installed,
ollama_running,
default_model_downloaded,
default_model_name: if default_model_downloaded {
default_model_name
} else {
None
},
})
}
/// Instala o Ollama silenciosamente
/// No Windows, tenta usar Chocolatey primeiro, depois fallback para instalação direta
pub async fn install_ollama_silently(
app_handle: AppHandle,
installer_path: Option<String>,
) -> Result<(), String> {
let window = app_handle.get_webview_window("main");
// Se já está instalado, apenas verificar se está rodando
if check_ollama_installed() {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_check",
"message": "Ollama já está instalado. Verificando se está rodando..."
}));
}
// Tentar iniciar se não estiver rodando
if !check_ollama_running().await {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_starting",
"message": "Iniciando Ollama..."
}));
}
// Aguardar até 30 segundos para o Ollama iniciar
let max_attempts = 30;
for attempt in 1..=max_attempts {
if check_ollama_running().await {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_ready",
"message": "Ollama está rodando!"
}));
}
return Ok(());
}
sleep(Duration::from_secs(1)).await;
if attempt % 5 == 0 {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_starting",
"message": format!("Aguardando Ollama iniciar... ({}s)", attempt)
}));
}
}
}
return Err("Ollama instalado mas não conseguiu iniciar automaticamente".to_string());
}
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_ready",
"message": "Ollama está rodando!"
}));
}
return Ok(());
}
// No Windows, tentar usar Chocolatey primeiro
#[cfg(target_os = "windows")]
{
use crate::setup_manager;
// Verificar se Chocolatey está instalado
if !setup_manager::check_chocolatey_installed() {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "installing_chocolatey",
"message": "Chocolatey não encontrado. Instalando Chocolatey..."
}));
}
// Tentar instalar Chocolatey
if let Some(ref w) = window {
if let Err(e) = setup_manager::install_chocolatey(w.clone()).await {
log::warn!("Falha ao instalar Chocolatey: {}. Tentando instalação direta...", e);
// Continuar para fallback de instalação direta
} else {
log::info!("Chocolatey instalado com sucesso");
}
} else {
return Err("Window não disponível para instalação do Chocolatey".to_string());
}
}
// Se Chocolatey está disponível, usar para instalar Ollama
if setup_manager::check_chocolatey_installed() {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "installing_ollama",
"message": "Instalando Ollama via Chocolatey..."
}));
}
if let Some(ref w) = window {
match setup_manager::install_ollama_choco(w.clone()).await {
Ok(_) => {
log::info!("Ollama instalado via Chocolatey com sucesso");
// Aguardar um pouco e verificar se está rodando
sleep(Duration::from_secs(3)).await;
if check_ollama_running().await {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_ready",
"message": "Ollama instalado e rodando!"
}));
}
return Ok(());
}
// Se não está rodando, tentar iniciar
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_starting",
"message": "Iniciando Ollama..."
}));
}
// Aguardar até 30 segundos para o Ollama iniciar
let max_attempts = 30;
for attempt in 1..=max_attempts {
if check_ollama_running().await {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_ready",
"message": "Ollama está rodando!"
}));
}
return Ok(());
}
sleep(Duration::from_secs(1)).await;
if attempt % 5 == 0 {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_starting",
"message": format!("Aguardando Ollama iniciar... ({}s)", attempt)
}));
}
}
}
return Err("Ollama instalado mas não conseguiu iniciar automaticamente".to_string());
}
Err(e) => {
log::warn!("Falha ao instalar Ollama via Chocolatey: {}. Tentando instalação direta...", e);
// Continuar para fallback de instalação direta
}
}
}
}
// Fallback: instalação direta via .exe
let installer = if let Some(path) = installer_path {
PathBuf::from(path)
} else {
return Err("Caminho do instalador não fornecido e Chocolatey não disponível".to_string());
};
if !installer.exists() {
return Err(format!("Instalador não encontrado: {:?}", installer));
}
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "installing_ollama",
"message": "Iniciando instalação silenciosa do Ollama..."
}));
}
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
let mut cmd = Command::new(&installer);
cmd.creation_flags(CREATE_NO_WINDOW);
cmd.arg("/VERYSILENT")
.arg("/NORESTART")
.arg("/SUPPRESSMSGBOXES");
log::info!("Executando instalador silencioso: {:?}", installer);
let output = cmd.output()
.map_err(|e| format!("Erro ao executar instalador: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
log::warn!("Instalador retornou código de erro: {}", stderr);
}
}
#[cfg(target_os = "linux")]
{
use std::os::unix::fs::PermissionsExt;
use std::process::Stdio;
let mut perms = std::fs::metadata(&installer)
.map_err(|e| format!("Erro ao obter metadados do arquivo: {}", e))?
.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&installer, perms)
.map_err(|e| format!("Erro ao definir permissões: {}", e))?;
let path_str = installer.to_string_lossy();
let mut cmd = if path_str.ends_with(".sh") {
let mut c = Command::new("sh");
c.arg(&installer).arg("--quiet");
c
} else {
Command::new(&installer)
};
// Capturar stderr para detectar falhas de permissão
cmd.stderr(Stdio::piped());
cmd.stdout(Stdio::piped());
let output = cmd.output()
.map_err(|e| format!("Erro ao executar instalador: {}", e))?;
// Verificar stderr por mensagens de erro de permissão
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr_lower = stderr.to_lowercase();
if stderr_lower.contains("permission denied")
|| stderr_lower.contains("sudo required")
|| stderr_lower.contains("cannot open")
|| stderr_lower.contains("access denied") {
return Err("Instalação requer privilégios de administrador. Execute manualmente: curl https://ollama.com/install.sh | sh".to_string());
}
// Verificar se o comando falhou
if !output.status.success() {
let error_msg = if !stderr.is_empty() {
format!("Instalação falhou: {}", stderr.trim())
} else {
format!("Instalação falhou com código de saída: {:?}", output.status.code())
};
return Err(error_msg);
}
}
#[cfg(target_os = "macos")]
{
let path_str = installer.to_string_lossy();
if path_str.ends_with(".pkg") {
Command::new("installer")
.arg("-pkg")
.arg(&installer)
.arg("-target")
.arg("/")
.arg("-verboseR")
.spawn()
.map_err(|e| format!("Erro ao executar instalador: {}", e))?;
} else {
Command::new("open")
.arg(&installer)
.spawn()
.map_err(|e| format!("Erro ao abrir instalador: {}", e))?;
}
}
// Aguardar instalação e verificar
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "installing_ollama",
"message": "Aguardando instalação concluir..."
}));
}
// Fazer polling para verificar quando Ollama estiver instalado e rodando
let window_clone = window.clone();
let max_attempts = 90;
let mut attempts = 0;
while attempts < max_attempts {
if check_ollama_installed_async().await {
// Aguardar um pouco para o serviço iniciar
sleep(Duration::from_secs(2)).await;
if let Ok(_) = poll_ollama_ready(5).await {
log::info!("Ollama instalado e rodando com sucesso");
if let Some(ref w) = window_clone {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_ready",
"message": "Ollama instalado e rodando!"
}));
}
return Ok(());
}
}
attempts += 1;
sleep(Duration::from_secs(1)).await;
if attempts % 5 == 0 {
if let Some(ref w) = window_clone {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "installing_ollama",
"message": format!("Aguardando instalação... ({}s)", attempts)
}));
}
}
}
// Verificar se pelo menos foi instalado
if check_ollama_installed_async().await {
if let Some(ref w) = window_clone {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "ollama_partial",
"message": "Ollama instalado, mas não iniciou automaticamente"
}));
}
return Err("Ollama instalado mas não iniciou automaticamente".to_string());
}
Err("Timeout: Ollama não foi instalado após 90 segundos".to_string())
}
// Estruturas para processar progresso do download
#[derive(serde::Deserialize)]
struct PullProgress {
status: String,
#[serde(default)]
digest: String,
#[serde(default)]
total: u64,
#[serde(default)]
completed: u64,
}
#[derive(serde::Serialize)]
struct DownloadProgress {
status: String,
percent: Option<u8>,
downloaded: Option<String>,
total: Option<String>,
speed: Option<String>,
raw: String,
}
// Funções auxiliares para formatação
fn format_bytes(bytes: u64) -> Option<String> {
if bytes == 0 {
return None;
}
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
Some(if bytes >= GB {
format!("{:.1} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
})
}
fn format_speed(bytes_per_sec: f64) -> String {
const KB: f64 = 1024.0;
const MB: f64 = KB * 1024.0;
const GB: f64 = MB * 1024.0;
if bytes_per_sec >= GB {
format!("{:.1} GB/s", bytes_per_sec / GB)
} else if bytes_per_sec >= MB {
format!("{:.1} MB/s", bytes_per_sec / MB)
} else if bytes_per_sec >= KB {
format!("{:.1} KB/s", bytes_per_sec / KB)
} else {
format!("{:.0} B/s", bytes_per_sec)
}
}
/// Baixa o modelo padrão durante o setup
pub async fn download_default_model(
app_handle: AppHandle,
model_name: String,
) -> Result<(), String> {
let window = app_handle.get_webview_window("main");
// Verificar se já está instalado
if check_if_model_installed(model_name.clone()) {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "model_ready",
"message": format!("Modelo {} já está instalado!", model_name)
}));
}
return Ok(());
}
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "downloading_model",
"message": format!("Baixando modelo {}...", model_name)
}));
}
// Usar API HTTP do Ollama (mais confiável que CLI)
let client = reqwest::Client::new();
let response = client
.post("http://localhost:11434/api/pull")
.json(&serde_json::json!({ "name": model_name.clone(), "stream": true }))
.send()
.await
.map_err(|e| format!("Erro ao conectar à API do Ollama: {}", e))?;
if !response.status().is_success() {
return Err(format!("API do Ollama retornou erro: {}", response.status()));
}
let mut stream = response.bytes_stream();
let mut buffer = String::new();
let window_clone = window.clone();
let model_name_clone = model_name.clone();
let mut last_completed: u64 = 0;
let mut last_time = Instant::now();
// Processar stream NDJSON (mesmo formato que pull_model_impl_webview)
while let Some(chunk_result) = stream.next().await {
let chunk = chunk_result.map_err(|e| format!("Erro no stream: {}", e))?;
let chunk_str = String::from_utf8_lossy(&chunk);
buffer.push_str(&chunk_str);
// Processar linhas completas (separadas por \n)
while let Some(pos) = buffer.find('\n') {
let line = buffer[..pos].trim().to_string();
buffer = buffer[pos + 1..].to_string();
if line.is_empty() {
continue;
}
// Tentar deserializar como PullProgress
match serde_json::from_str::<PullProgress>(&line) {
Ok(json_progress) => {
// Calcular porcentagem se tiver total/completed
let percent = if json_progress.total > 0 {
Some(((json_progress.completed as f64 / json_progress.total as f64) * 100.0) as u8)
} else {
None
};
// Calcular velocidade
let now = Instant::now();
let delta_time = now.duration_since(last_time).as_secs_f64();
let speed = if delta_time > 0.0 && json_progress.completed > last_completed {
let delta_bytes = json_progress.completed - last_completed;
let bytes_per_sec = delta_bytes as f64 / delta_time;
Some(format_speed(bytes_per_sec))
} else {
None
};
last_completed = json_progress.completed;
last_time = now;
// Criar DownloadProgress estruturado
let progress = DownloadProgress {
status: json_progress.status.clone(),
percent,
downloaded: format_bytes(json_progress.completed),
total: format_bytes(json_progress.total),
speed,
raw: line.clone(),
};
// Emitir evento download-progress para o DownloadContext
if let Ok(json) = serde_json::to_string(&progress) {
if let Some(ref w) = window_clone {
let _ = w.emit("download-progress", json);
}
}
// Manter compatibilidade: também emitir setup-progress
if let Some(ref w) = window_clone {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "downloading_model",
"message": format!("{}: {}", model_name_clone, json_progress.status)
}));
}
// Se status for "success", finalizar
if json_progress.status == "success" {
let success_progress = DownloadProgress {
status: "success".to_string(),
percent: Some(100),
downloaded: format_bytes(json_progress.completed),
total: format_bytes(json_progress.total),
speed: None,
raw: "success".to_string(),
};
if let Ok(json) = serde_json::to_string(&success_progress) {
if let Some(ref w) = window_clone {
let _ = w.emit("download-progress", json);
}
}
break;
}
}
Err(_) => {
// Se não conseguir parsear como JSON, tratar como linha raw (fallback)
let progress = DownloadProgress {
status: "downloading".to_string(),
percent: None,
downloaded: None,
total: None,
speed: None,
raw: line.clone(),
};
if let Ok(json) = serde_json::to_string(&progress) {
if let Some(ref w) = window_clone {
let _ = w.emit("download-progress", json);
}
}
}
}
}
}
// Se chegou aqui, o stream terminou - emitir sucesso final
let success_progress = DownloadProgress {
status: "success".to_string(),
percent: Some(100),
downloaded: format_bytes(last_completed),
total: None,
speed: None,
raw: "success".to_string(),
};
if let Ok(json) = serde_json::to_string(&success_progress) {
if let Some(ref w) = window {
let _ = w.emit("download-progress", json);
}
}
// Verificar se o modelo foi instalado
if check_if_model_installed(model_name.clone()) {
if let Some(ref w) = window {
let _ = w.emit("setup-progress", serde_json::json!({
"status": "model_ready",
"message": format!("Modelo {} baixado com sucesso!", model_name)
}));
}
Ok(())
} else {
Err(format!("Download do modelo {} pode ter falhado", model_name))
}
}