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

+8 -19
View File
@@ -108,27 +108,16 @@ export async function extractKeyFacts(
.replace('{{url}}', entry.sourceUrl)
.replace('{{content}}', entry.content.substring(0, 4000)); // Limitar tamanho
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt,
stream: false,
options: {
temperature: 0.3,
num_predict: 600,
},
}),
const { invoke } = await import('@tauri-apps/api/core');
const rawResponse = await invoke<string>('generate_completion', {
model,
prompt,
options: {
temperature: 0.3,
num_predict: 600,
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
const rawResponse = (data.response || '').trim();
// Limpar e extrair JSON
let jsonText = rawResponse
.replace(/^```json\s*/i, '')
+52 -20
View File
@@ -154,27 +154,17 @@ export async function analyzeQueryContext(
try {
const prompt = CONTEXTUAL_ANALYSIS_PROMPT.replace('{{query}}', query);
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt,
stream: false,
options: {
temperature: 0.3, // Baixa temperatura para análise mais determinística
num_predict: 800, // Espaço suficiente para JSON completo
},
}),
const { invoke } = await import('@tauri-apps/api/core');
const rawResponse = await invoke<string>('generate_completion', {
model,
prompt,
options: {
temperature: 0.3,
num_predict: 800,
format: 'json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const rawResponse = (data.response || '').trim();
// Limpar resposta (remover markdown code blocks se houver)
let jsonText = rawResponse
.replace(/^```json\s*/i, '')
@@ -188,7 +178,7 @@ export async function analyzeQueryContext(
jsonText = jsonMatch[0];
}
const analysis = JSON.parse(jsonText) as Partial<QueryContext>;
const analysis = tryParseJsonWithRepair(jsonText) as Partial<QueryContext>;
// Validar e preencher campos obrigatórios
const context: QueryContext = {
@@ -274,3 +264,45 @@ function createFallbackContext(query: string): QueryContext {
};
}
function repairJson(text: string): string {
let s = text.trim();
s = s.replace(/,\s*([}\]])/g, '$1');
let inString = false;
let escape = false;
let braceCount = 0;
let bracketCount = 0;
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (escape) { escape = false; continue; }
if (c === '\\') { escape = true; continue; }
if (c === '"') { inString = !inString; continue; }
if (!inString) {
if (c === '{') braceCount++;
else if (c === '}') braceCount--;
else if (c === '[') bracketCount++;
else if (c === ']') bracketCount--;
}
}
if (braceCount > 0) s += '}'.repeat(braceCount);
if (bracketCount > 0) s += ']'.repeat(bracketCount);
const lastClose = Math.max(s.lastIndexOf('}'), s.lastIndexOf(']'));
if (lastClose > 0) {
const after = s.slice(lastClose + 1);
if (/\S/.test(after)) {
s = s.slice(0, lastClose + 1);
}
}
return s;
}
function tryParseJsonWithRepair(text: string): unknown {
try { return JSON.parse(text); } catch {}
const repaired = repairJson(text);
try { return JSON.parse(repaired); } catch {}
const lastClose = Math.max(repaired.lastIndexOf('}'), repaired.lastIndexOf(']'));
if (lastClose > 0) {
const truncated = repaired.slice(0, lastClose + 1);
return JSON.parse(truncated);
}
throw new Error('Invalid JSON');
}
+626
View File
@@ -0,0 +1,626 @@
export interface ProblemSolution {
id: string;
problem: string; // Texto exato ou padrão para matching
os: 'windows' | 'linux' | 'macos' | 'all';
links: Array<{
title: string;
url: string;
}>;
commands?: {
title: string;
code: string;
description?: string;
}[];
description?: string;
}
export const PROBLEM_SOLUTIONS: ProblemSolution[] = [
// ========== WINDOWS ==========
{
id: 'windows-build-old',
problem: 'Windows build',
os: 'windows',
description: 'Versão do Windows muito antiga. É necessário Windows 10 20H1 (build 19041) ou superior, ou Windows 11.',
links: [
{
title: 'Instalar Atualizações do Windows',
url: 'https://support.microsoft.com/windows/install-windows-updates-3c5ae7fc-9fb6-9af1-1984-b5e0412c556a'
},
{
title: 'Baixar Windows 10',
url: 'https://www.microsoft.com/software-download/windows10'
},
{
title: 'Baixar Windows 11',
url: 'https://www.microsoft.com/software-download/windows11'
}
],
commands: [
{
title: 'Verificar build atual',
code: '[System.Environment]::OSVersion.Version',
description: 'Execute no PowerShell para verificar a versão atual do Windows'
}
]
},
{
id: 'windows-vm-platform',
problem: 'VirtualMachinePlatform não habilitado',
os: 'windows',
description: 'A plataforma de máquina virtual precisa estar habilitada para suportar WSL 2.',
links: [
{
title: 'Microsoft Learn - WSL Manual Install',
url: 'https://learn.microsoft.com/windows/wsl/install-manual'
}
],
commands: [
{
title: 'Habilitar VirtualMachinePlatform (PowerShell como Administrador)',
code: 'dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart',
description: 'Execute no PowerShell como Administrador. Reinicie o sistema após a execução.'
}
]
},
{
id: 'windows-wsl-not-enabled',
problem: 'WSL não habilitado',
os: 'windows',
description: 'Windows Subsystem for Linux precisa estar habilitado para executar Docker no Windows.',
links: [
{
title: 'Microsoft Learn - Instalar WSL',
url: 'https://learn.microsoft.com/windows/wsl/install'
},
{
title: 'Microsoft Learn - WSL Manual',
url: 'https://learn.microsoft.com/windows/wsl/install-manual'
}
],
commands: [
{
title: 'Habilitar WSL (PowerShell como Administrador)',
code: 'dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart',
description: 'Execute no PowerShell como Administrador. Reinicie o sistema após a execução.'
}
]
},
{
id: 'windows-wsl2-not-configured',
problem: 'WSL 2 não configurado',
os: 'windows',
description: 'WSL 2 precisa ser configurado como versão padrão do WSL.',
links: [
{
title: 'Microsoft Learn - Instalar WSL',
url: 'https://learn.microsoft.com/windows/wsl/install'
},
{
title: 'Microsoft Learn - WSL Manual',
url: 'https://learn.microsoft.com/windows/wsl/install-manual'
}
],
commands: [
{
title: 'Configurar WSL 2 como padrão',
code: 'wsl --set-default-version 2',
description: 'Execute após habilitar WSL e VirtualMachinePlatform e reiniciar o sistema.'
},
{
title: 'Comandos completos (PowerShell como Administrador)',
code: `# Habilitar WSL
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
# Habilitar Virtual Machine Platform
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
# Reiniciar e depois definir WSL 2 como padrão
wsl --set-default-version 2`,
description: 'Execute todos os comandos em sequência. Reinicie após os dois primeiros comandos.'
}
]
},
{
id: 'windows-ram-insufficient',
problem: 'RAM insuficiente',
os: 'windows',
description: 'Sistema requer no mínimo 4GB de RAM para executar Docker no Windows.',
links: [
{
title: 'Adicionar mais memória ao computador',
url: 'https://support.microsoft.com/windows/add-more-memory-to-your-computer-0f7e2d8a-d2c9-c858-5dca-8d3b3759a0ff'
}
],
commands: [
{
title: 'Verificar processos consumindo mais memória',
code: 'Get-Process | Sort-Object -Property WS -Descending | Select-Object -First 10',
description: 'Lista os 10 processos que mais consomem memória. Feche aplicativos desnecessários.'
}
]
},
{
id: 'windows-disk-insufficient',
problem: 'Espaço em disco insuficiente',
os: 'windows',
description: 'É necessário pelo menos 10GB de espaço livre em disco para instalar e executar Docker.',
links: [
{
title: 'Liberar espaço em disco no Windows',
url: 'https://support.microsoft.com/windows/free-up-drive-space-in-windows-85529ccb-c365-490d-b548-831022bc9b32'
}
],
commands: [
{
title: 'Abrir Limpeza de Disco',
code: 'cleanmgr.exe',
description: 'Abre a ferramenta de limpeza de disco do Windows.'
}
]
},
{
id: 'windows-cpu-cores',
problem: 'CPU cores insuficientes',
os: 'windows',
description: 'Recomenda-se pelo menos 2 cores de CPU para melhor desempenho.',
commands: [
{
title: 'Verificar número de cores lógicos',
code: '(Get-WmiObject Win32_Processor).NumberOfLogicalProcessors',
description: 'Mostra o número de processadores lógicos disponíveis.'
}
]
},
// ========== LINUX ==========
{
id: 'linux-kernel-old',
problem: 'Kernel',
os: 'linux',
description: 'Kernel Linux muito antigo. É necessário kernel 3.10 ou superior para executar Docker.',
links: [
{
title: 'Ubuntu/Debian - Gerenciamento de Pacotes',
url: 'https://ubuntu.com/server/docs/package-management'
},
{
title: 'Arch/Manjaro - Kernel',
url: 'https://wiki.archlinux.org/title/Kernel'
},
{
title: 'Fedora/RHEL - Kernel',
url: 'https://docs.fedoraproject.org/en-US/quick-docs/kernel/'
}
],
commands: [
{
title: 'Ubuntu/Debian - Atualizar sistema e kernel',
code: `sudo apt update && sudo apt upgrade -y
sudo apt install linux-generic`,
description: 'Atualiza todos os pacotes e instala o kernel genérico mais recente.'
},
{
title: 'Arch/Manjaro - Atualizar kernel',
code: 'sudo pacman -Syu linux',
description: 'Atualiza o sistema e o kernel.'
},
{
title: 'Fedora - Atualizar kernel',
code: 'sudo dnf upgrade kernel',
description: 'Atualiza o kernel para a versão mais recente.'
}
]
},
{
id: 'linux-curl-missing',
problem: 'Pacote curl não instalado',
os: 'linux',
description: 'O pacote curl é necessário para baixar arquivos e chaves GPG durante a instalação do Docker.',
links: [
{
title: 'curl.se - Download oficial',
url: 'https://curl.se/download.html'
},
{
title: 'Ubuntu packages - curl',
url: 'https://packages.ubuntu.com/search?keywords=curl'
}
],
commands: [
{
title: 'Ubuntu/Debian',
code: 'sudo apt update && sudo apt install curl',
description: 'Instala curl no Ubuntu ou Debian.'
},
{
title: 'Arch/Manjaro',
code: 'sudo pacman -S curl',
description: 'Instala curl no Arch Linux ou Manjaro.'
},
{
title: 'Fedora/RHEL',
code: 'sudo dnf install curl',
description: 'Instala curl no Fedora ou RHEL.'
},
{
title: 'openSUSE',
code: 'sudo zypper install curl',
description: 'Instala curl no openSUSE.'
}
]
},
{
id: 'linux-ca-certificates-missing',
problem: 'Pacote ca-certificates não instalado',
os: 'linux',
description: 'Certificados CA são necessários para verificar conexões HTTPS seguras.',
links: [
{
title: 'Ubuntu packages - ca-certificates',
url: 'https://packages.ubuntu.com/search?keywords=ca-certificates'
},
{
title: 'Debian wiki - ca-certificates',
url: 'https://wiki.debian.org/ca-certificates'
}
],
commands: [
{
title: 'Ubuntu/Debian',
code: 'sudo apt update && sudo apt install ca-certificates',
description: 'Instala certificados CA no Ubuntu ou Debian.'
},
{
title: 'Arch/Manjaro',
code: 'sudo pacman -S ca-certificates',
description: 'Instala certificados CA no Arch Linux ou Manjaro.'
},
{
title: 'Fedora/RHEL',
code: 'sudo dnf install ca-certificates',
description: 'Instala certificados CA no Fedora ou RHEL.'
},
{
title: 'openSUSE',
code: 'sudo zypper install ca-certificates',
description: 'Instala certificados CA no openSUSE.'
}
]
},
{
id: 'linux-gnupg-missing',
problem: 'Pacote gnupg não instalado',
os: 'linux',
description: 'GnuPG é necessário para verificar chaves GPG durante a instalação do Docker.',
links: [
{
title: 'GnuPG oficial - Download',
url: 'https://gnupg.org/download/'
},
{
title: 'Ubuntu packages - gnupg',
url: 'https://packages.ubuntu.com/search?keywords=gnupg'
}
],
commands: [
{
title: 'Ubuntu/Debian',
code: 'sudo apt update && sudo apt install gnupg',
description: 'Instala GnuPG no Ubuntu ou Debian.'
},
{
title: 'Arch/Manjaro',
code: 'sudo pacman -S gnupg',
description: 'Instala GnuPG no Arch Linux ou Manjaro.'
},
{
title: 'Fedora/RHEL',
code: 'sudo dnf install gnupg',
description: 'Instala GnuPG no Fedora ou RHEL.'
},
{
title: 'openSUSE',
code: 'sudo zypper install gpg2',
description: 'Instala GnuPG no openSUSE.'
}
]
},
{
id: 'linux-sudo-missing',
problem: 'Privilégios sudo necessários',
os: 'linux',
description: 'Privilégios de administrador são necessários para instalar pacotes e configurar o sistema.',
links: [
{
title: 'Ubuntu - RootSudo',
url: 'https://help.ubuntu.com/community/RootSudo'
},
{
title: 'Arch Linux - Sudo',
url: 'https://wiki.archlinux.org/title/Sudo'
}
],
commands: [
{
title: 'Adicionar usuário ao grupo sudo (Ubuntu/Debian)',
code: 'sudo usermod -aG sudo $USER',
description: 'Adiciona o usuário atual ao grupo sudo. Faça logout e login novamente para aplicar.'
},
{
title: 'Adicionar ao grupo wheel (Arch/Fedora)',
code: 'sudo usermod -aG wheel $USER',
description: 'Adiciona o usuário atual ao grupo wheel. Faça logout e login novamente para aplicar.'
}
]
},
{
id: 'linux-ram-insufficient',
problem: 'RAM insuficiente',
os: 'linux',
description: 'Sistema requer no mínimo 2GB de RAM para executar Docker.',
commands: [
{
title: 'Verificar processos consumindo mais memória',
code: 'top',
description: 'Mostra processos em tempo real. Pressione "q" para sair.'
},
{
title: 'Verificar processos (htop - se instalado)',
code: 'htop',
description: 'Interface mais amigável. Instale com: sudo apt install htop (Ubuntu/Debian)'
},
{
title: 'Verificar uso de memória',
code: 'free -h',
description: 'Mostra uso de memória em formato legível.'
}
]
},
{
id: 'linux-disk-insufficient',
problem: 'Espaço em disco insuficiente',
os: 'linux',
description: 'É necessário pelo menos 10GB de espaço livre em disco.',
commands: [
{
title: 'Verificar espaço em disco',
code: 'df -h',
description: 'Mostra espaço usado e disponível em todos os sistemas de arquivos.'
},
{
title: 'Verificar diretórios ocupando mais espaço',
code: 'du -h --max-depth=1 / | sort -hr | head -10',
description: 'Lista os 10 diretórios que mais ocupam espaço (execute com sudo para resultados completos).'
},
{
title: 'Limpar cache de pacotes (Ubuntu/Debian)',
code: 'sudo apt clean && sudo apt autoremove',
description: 'Remove pacotes antigos e cache para liberar espaço.'
}
]
},
{
id: 'linux-cpu-cores',
problem: 'CPU cores insuficientes',
os: 'linux',
description: 'Recomenda-se pelo menos 2 cores de CPU para melhor desempenho.',
commands: [
{
title: 'Verificar número de cores',
code: 'nproc',
description: 'Mostra o número de processadores disponíveis.'
},
{
title: 'Informações detalhadas da CPU',
code: 'lscpu | grep "^CPU(s):"',
description: 'Mostra informações detalhadas sobre a CPU.'
}
]
},
// ========== macOS ==========
{
id: 'macos-version-old',
problem: 'macOS',
os: 'macos',
description: 'Versão do macOS muito antiga. Recomenda-se macOS 11 (Big Sur) ou superior.',
links: [
{
title: 'Apple - Atualizar macOS',
url: 'https://support.apple.com/HT201541'
},
{
title: 'Apple - Como atualizar',
url: 'https://support.apple.com/guide/mac-help/update-macos-mchlpx1065'
}
]
},
{
id: 'macos-homebrew-missing',
problem: 'Homebrew não instalado',
os: 'macos',
description: 'Homebrew é recomendado para facilitar a instalação do Docker no macOS.',
links: [
{
title: 'Homebrew oficial',
url: 'https://brew.sh/'
}
],
commands: [
{
title: 'Instalar Homebrew (Intel e Apple Silicon)',
code: '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
description: 'Instala Homebrew. Siga as instruções na tela.'
},
{
title: 'Configurar PATH para Apple Silicon (após instalação)',
code: `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"`,
description: 'Adiciona Homebrew ao PATH em Macs com Apple Silicon (M1/M2/M3).'
}
]
},
{
id: 'macos-rosetta-missing',
problem: 'Rosetta 2 não instalado',
os: 'macos',
description: 'Rosetta 2 é necessário para executar aplicativos x86_64 em Macs com Apple Silicon.',
links: [
{
title: 'Apple - Rosetta 2',
url: 'https://support.apple.com/HT211861'
},
{
title: 'Apple Developer - Rosetta',
url: 'https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment'
}
],
commands: [
{
title: 'Instalar Rosetta 2',
code: 'softwareupdate --install-rosetta --agree-to-license',
description: 'Instala Rosetta 2. Pode solicitar confirmação.'
},
{
title: 'Verificar se Rosetta está instalado',
code: 'ls /Library/Apple/usr/share/rosetta',
description: 'Verifica se Rosetta 2 está instalado. Se o diretório existir, está instalado.'
},
{
title: 'Testar execução x86_64',
code: 'arch -x86_64 uname -m',
description: 'Deve retornar "x86_64" se Rosetta estiver funcionando.'
}
]
},
{
id: 'macos-ram-insufficient',
problem: 'RAM insuficiente',
os: 'macos',
description: 'Sistema requer no mínimo 4GB de RAM para executar Docker no macOS.',
links: [
{
title: 'Apple - Memória Mac',
url: 'https://support.apple.com/mac/memory'
}
],
commands: [
{
title: 'Verificar processos consumindo mais memória',
code: 'top -o MEM',
description: 'Mostra processos ordenados por uso de memória. Pressione "q" para sair.'
},
{
title: 'Abrir Monitor de Atividade',
code: 'open -a "Activity Monitor"',
description: 'Abre o Monitor de Atividade do macOS.'
}
]
},
{
id: 'macos-disk-insufficient',
problem: 'Espaço em disco insuficiente',
os: 'macos',
description: 'É necessário pelo menos 4GB de espaço livre em disco.',
links: [
{
title: 'Apple - Liberar espaço de armazenamento',
url: 'https://support.apple.com/guide/mac-help/free-up-storage-space-mh31934'
}
],
commands: [
{
title: 'Abrir Gerenciamento de Armazenamento',
code: 'open "x-apple.systempreferences:com.apple.preference.storage"',
description: 'Abre as preferências de armazenamento do macOS.'
}
]
},
{
id: 'macos-cpu-cores',
problem: 'CPU cores insuficientes',
os: 'macos',
description: 'Recomenda-se pelo menos 2 cores de CPU para melhor desempenho.',
commands: [
{
title: 'Verificar número de cores',
code: 'sysctl -n hw.ncpu',
description: 'Mostra o número de cores de CPU disponíveis.'
}
]
},
// ========== GERAL ==========
{
id: 'os-not-supported',
problem: 'Sistema operacional não suportado',
os: 'all',
description: 'Este sistema operacional não é suportado. Docker requer Windows, Linux ou macOS.',
links: [
{
title: 'Docker - Requisitos do Sistema',
url: 'https://docs.docker.com/desktop/supported-platforms/'
}
]
}
];
/**
* Encontra a solução correspondente para um problema
* @param problemText Texto do problema retornado pela verificação
* @param os Sistema operacional atual
* @returns Solução correspondente ou null se não encontrada
*/
export function findProblemSolution(problemText: string, os: string): ProblemSolution | null {
const normalizedProblem = problemText.toLowerCase();
const normalizedOs = os.toLowerCase();
// Buscar por matching exato primeiro
let solution = PROBLEM_SOLUTIONS.find(s => {
const matchesOs = s.os === 'all' || s.os === normalizedOs ||
(normalizedOs === 'mac' && s.os === 'macos') ||
(normalizedOs === 'windows' && s.os === 'windows') ||
(normalizedOs === 'linux' && s.os === 'linux');
if (!matchesOs) return false;
const problemLower = s.problem.toLowerCase();
return normalizedProblem.includes(problemLower) || problemLower.includes(normalizedProblem);
});
// Se não encontrou, tentar matching parcial por palavras-chave
if (!solution) {
const keywords: { [key: string]: string } = {
'windows build': 'windows-build-old',
'virtualmachineplatform': 'windows-vm-platform',
'wsl não habilitado': 'windows-wsl-not-enabled',
'wsl 2 não configurado': 'windows-wsl2-not-configured',
'ram insuficiente': normalizedOs === 'windows' ? 'windows-ram-insufficient' :
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-ram-insufficient' :
'linux-ram-insufficient',
'espaço em disco': normalizedOs === 'windows' ? 'windows-disk-insufficient' :
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-disk-insufficient' :
'linux-disk-insufficient',
'cpu cores': normalizedOs === 'windows' ? 'windows-cpu-cores' :
normalizedOs === 'mac' || normalizedOs === 'macos' ? 'macos-cpu-cores' :
'linux-cpu-cores',
'kernel': 'linux-kernel-old',
'pacote curl': 'linux-curl-missing',
'pacote ca-certificates': 'linux-ca-certificates-missing',
'pacote gnupg': 'linux-gnupg-missing',
'privilégios sudo': 'linux-sudo-missing',
'homebrew': 'macos-homebrew-missing',
'rosetta': 'macos-rosetta-missing',
'sistema operacional não suportado': 'os-not-supported'
};
for (const [keyword, id] of Object.entries(keywords)) {
if (normalizedProblem.includes(keyword)) {
solution = PROBLEM_SOLUTIONS.find(s => s.id === id);
if (solution) break;
}
}
}
return solution || null;
}
+8 -4
View File
@@ -53,14 +53,15 @@ export async function checkLocalInstallerExists(os: OS): Promise<boolean> {
* Faz download do instalador (oficial ou fallback)
* Retorna o caminho do arquivo baixado
*/
export async function downloadInstaller(os: OS): Promise<string> {
export async function downloadInstaller(os: OS, expectedSha256?: string): Promise<string> {
const url = OLLAMA_DOWNLOAD_URLS[os];
const filename = LOCAL_INSTALLER_FILES[os];
try {
const filePath = await invoke<string>('download_installer', {
url,
filename
filename,
expected_sha256: expectedSha256 || null
});
return filePath;
} catch (error) {
@@ -87,9 +88,12 @@ export async function getDownloadedInstallerPath(os: OS): Promise<string | null>
/**
* Executa o instalador baixado
*/
export async function runInstaller(filePath: string): Promise<void> {
export async function runInstaller(filePath: string, expectedSha256?: string): Promise<void> {
try {
await invoke('run_installer', { filePath });
await invoke('run_installer', {
file_path: filePath,
expected_sha256: expectedSha256 || null
});
} catch (error) {
throw new Error(`Falha ao executar instalador: ${error}`);
}
+77
View File
@@ -3,6 +3,7 @@ import type { QueryContext } from './contextual-analyzer';
import { processContent, type Chunk, type ProcessedContent } from './content-condenser';
import { scoreAndSelectChunks } from './relevance-scorer';
import { summarizeMarkdown } from './content-condenser';
import { chatLog } from './terminal-logger';
export interface CondensationResult {
context: string;
@@ -79,9 +80,21 @@ export function condenseKnowledgeBase(
query: string,
options: CondensationOptions = {}
): CondensationResult {
const condensationStart = Date.now();
chatLog.info(`\n[KnowledgeBase] ========== CONDENSATION START ==========`);
chatLog.info(`[KnowledgeBase] Query: "${query}"`);
chatLog.info(`[KnowledgeBase] Entries: ${entries.length}`);
chatLog.info(`[KnowledgeBase] Options:`, {
maxTokens: options.maxTokens || DEFAULT_OPTIONS.maxTokens,
minRelevanceScore: options.minRelevanceScore || DEFAULT_OPTIONS.minRelevanceScore,
autoSummarize: options.autoSummarize ?? DEFAULT_OPTIONS.autoSummarize,
});
const opts = { ...DEFAULT_OPTIONS, ...options };
if (entries.length === 0) {
chatLog.warn(`[KnowledgeBase] ⚠️ No entries to condense`);
return {
context: '',
totalTokens: 0,
@@ -97,6 +110,9 @@ export function condenseKnowledgeBase(
// Calcular tokens originais
const originalContent = entries.map(e => e.content).join('\n\n');
const originalTokens = estimateTokens(originalContent);
chatLog.info(`[KnowledgeBase] Original content: ${originalContent.length} chars`);
chatLog.info(`[KnowledgeBase] Original tokens (estimated): ${originalTokens}`);
// Processar cada entrada
const allChunks: Chunk[] = [];
@@ -106,7 +122,15 @@ export function condenseKnowledgeBase(
chunks: Chunk[];
}> = [];
chatLog.info(`[KnowledgeBase] Processing ${entries.length} entries into chunks...`);
entries.forEach((entry, entryIndex) => {
const entryStart = Date.now();
chatLog.info(`\n[KnowledgeBase] --- Processing Entry ${entryIndex + 1}/${entries.length} ---`);
chatLog.info(`[KnowledgeBase] Title: ${entry.title}`);
chatLog.info(`[KnowledgeBase] URL: ${entry.sourceUrl}`);
chatLog.info(`[KnowledgeBase] Content length: ${entry.content.length} chars`);
const processed = processContent(entry.content, query, {
summarizeThreshold: opts.summarizeThreshold,
});
@@ -119,6 +143,19 @@ export function condenseKnowledgeBase(
sourceTitle: entry.title,
}));
const entryDuration = Date.now() - entryStart;
chatLog.info(`[KnowledgeBase] Generated ${chunksWithSource.length} chunks in ${entryDuration}ms`);
// Log detalhes dos chunks
if (chunksWithSource.length > 0) {
chunksWithSource.slice(0, 3).forEach((chunk, chunkIdx) => {
chatLog.info(` Chunk ${chunkIdx + 1}: ${chunk.content.length} chars, relevance: ${chunk.relevanceScore?.toFixed(3) || 'N/A'}`);
});
if (chunksWithSource.length > 3) {
chatLog.info(` ... and ${chunksWithSource.length - 3} more chunks`);
}
}
processedEntries.push({
entry,
processed,
@@ -127,11 +164,18 @@ export function condenseKnowledgeBase(
allChunks.push(...chunksWithSource);
});
chatLog.info(`[KnowledgeBase] Total chunks generated: ${allChunks.length}`);
// Calcular tokens disponíveis (reservar 20% para margem)
const availableTokens = Math.floor(opts.maxTokens * 0.8);
chatLog.info(`[KnowledgeBase] Available tokens: ${availableTokens} (max: ${opts.maxTokens}, reserved 20%)`);
chatLog.info(`[KnowledgeBase] Min relevance score: ${opts.minRelevanceScore}`);
// Selecionar chunks mais relevantes (usando contexto se disponível)
const selectionStart = Date.now();
chatLog.info(`[KnowledgeBase] Selecting most relevant chunks...`);
let selectedChunks = scoreAndSelectChunks(
allChunks,
query,
@@ -139,6 +183,19 @@ export function condenseKnowledgeBase(
opts.minRelevanceScore,
opts.context
);
const selectionDuration = Date.now() - selectionStart;
chatLog.info(`[KnowledgeBase] Selected ${selectedChunks.length} chunks from ${allChunks.length} total in ${selectionDuration}ms`);
// Log top chunks selecionados
if (selectedChunks.length > 0) {
chatLog.info(`[KnowledgeBase] Top 5 selected chunks:`);
selectedChunks.slice(0, 5).forEach((chunk, idx) => {
chatLog.info(` [${idx + 1}] Score: ${chunk.relevanceScore?.toFixed(3) || 'N/A'}, Length: ${chunk.content.length} chars`);
chatLog.info(` Source: ${chunk.sourceTitle || 'Unknown'}`);
chatLog.info(` Preview: ${chunk.content.substring(0, 100)}...`);
});
}
// Se não selecionou chunks suficientes ou método de fallback está ativo
let method: 'chunks' | 'summarized' | 'fallback' = 'chunks';
@@ -196,6 +253,26 @@ export function condenseKnowledgeBase(
const totalTokens = estimateTokens(context);
const compressionRatio = originalTokens > 0 ? totalTokens / originalTokens : 0;
const condensationDuration = Date.now() - condensationStart;
// Log resultado final
chatLog.info(`\n[KnowledgeBase] ========== CONDENSATION RESULTS ==========`);
chatLog.info(`[KnowledgeBase] Method: ${method}`);
chatLog.info(`[KnowledgeBase] Duration: ${condensationDuration}ms`);
chatLog.info(`[KnowledgeBase] Original tokens: ${originalTokens}`);
chatLog.info(`[KnowledgeBase] Final tokens: ${totalTokens}`);
chatLog.info(`[KnowledgeBase] Compression ratio: ${(compressionRatio * 100).toFixed(1)}%`);
chatLog.info(`[KnowledgeBase] Chunks used: ${selectedChunks.length} / ${allChunks.length}`);
chatLog.info(`[KnowledgeBase] Final context length: ${context.length} chars`);
// Log estatísticas por fonte
chatLog.info(`[KnowledgeBase] Chunks per source:`);
entries.forEach((entry, idx) => {
const chunksUsed = selectedChunks.filter(c => c.sourceIndex === idx).length;
chatLog.info(` ${entry.title}: ${chunksUsed} chunks`);
});
chatLog.info(`[KnowledgeBase] ============================================\n`);
return {
context: context.trim(),
+4 -8
View File
@@ -29,16 +29,12 @@ export async function detectModelContextWindow(modelName: string): Promise<numbe
try {
// Tentar obter informações do modelo via Ollama API
const response = await fetch('http://localhost:11434/api/show', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: modelName,
}),
const { invoke } = await import('@tauri-apps/api/core');
const data = await invoke<any>('get_ollama_model_info', {
modelName,
});
if (response.ok) {
const data = await response.json();
if (data) {
// Ollama retorna 'context_length' ou 'parameter_size' que pode indicar contexto
// Mapear tamanhos conhecidos de modelos
+142
View File
@@ -0,0 +1,142 @@
import { LocalModel } from '@/hooks/use-local-models';
// Interfaces based on the JSON structure
export interface ModelVariant {
name: string;
size: string;
context: string;
inputType: string;
}
export interface ModelDefinition {
model: string;
url: string;
description: string;
variantsCount: number;
variants: ModelVariant[];
}
// Import model definitions
// We use a static list as dynamic imports of JSONs are not trivial in client-side bundles without API routes
import aya from '@/data/models/aya.json';
import codellama from '@/data/models/codellama.json';
import commandR from '@/data/models/command-r.json';
import commandRPlus from '@/data/models/command-r-plus.json';
import deepseekCoder from '@/data/models/deepseek-coder.json';
import deepseekLLM from '@/data/models/deepseek-llm.json';
import deepseekV2 from '@/data/models/deepseek-v2.json';
import deepseekV25 from '@/data/models/deepseek-v2.5.json';
import deepseekV3 from '@/data/models/deepseek-v3.json';
import falcon from '@/data/models/falcon.json';
import gemma from '@/data/models/gemma.json';
import gemma2 from '@/data/models/gemma2.json';
import llama2 from '@/data/models/llama2.json';
import llama2Uncensored from '@/data/models/llama2-uncensored.json';
import llama3 from '@/data/models/llama3.json';
import llama31 from '@/data/models/llama3.1.json';
import llama32 from '@/data/models/llama3.2.json';
import llama32Vision from '@/data/models/llama3.2-vision.json';
import llama33 from '@/data/models/llama3.3.json';
import llava from '@/data/models/llava.json';
import mistral from '@/data/models/mistral.json';
import mistralNemo from '@/data/models/mistral-nemo.json';
import mixtral from '@/data/models/mixtral.json';
import moondream from '@/data/models/moondream.json';
import neuralChat from '@/data/models/neural-chat.json';
import openhermes from '@/data/models/openhermes.json';
import orcaMini from '@/data/models/orca-mini.json';
import phi from '@/data/models/phi.json';
import phi3 from '@/data/models/phi3.json';
import phi35 from '@/data/models/phi3.5.json';
import phi4 from '@/data/models/phi4.json';
import qwen from '@/data/models/qwen.json';
import qwen2 from '@/data/models/qwen2.json';
import qwen25 from '@/data/models/qwen2.5.json';
import qwen25Coder from '@/data/models/qwen2.5-coder.json';
import starlingLM from '@/data/models/starling-lm.json';
import tinyllama from '@/data/models/tinyllama.json';
import vicuna from '@/data/models/vicuna.json';
import wizardVicunaUncensored from '@/data/models/wizard-vicuna-uncensored.json';
import yi from '@/data/models/yi.json';
import zephyr from '@/data/models/zephyr.json';
// Aggregate into a registry
export const MODEL_REGISTRY: ModelDefinition[] = [
aya,
codellama,
commandR,
commandRPlus,
deepseekCoder,
deepseekLLM,
deepseekV2,
deepseekV25,
deepseekV3,
falcon,
gemma,
gemma2,
llama2,
llama2Uncensored,
llama3,
llama31,
llama32,
llama32Vision,
llama33,
llava,
mistral,
mistralNemo,
mixtral,
moondream,
neuralChat,
openhermes,
orcaMini,
phi,
phi3,
phi35,
phi4,
qwen,
qwen2,
qwen25,
qwen25Coder,
starlingLM,
tinyllama,
vicuna,
wizardVicunaUncensored,
yi,
zephyr,
] as ModelDefinition[];
// Helper to find a model definition by one of its variant names
export function findModelDefinition(variantName: string): ModelDefinition | undefined {
// Remove tag to match base model name roughly?
// Or search all variants
const cleanName = variantName.split(':')[0];
// First try to find by exact match in variants
const exactMatch = MODEL_REGISTRY.find(def =>
def.variants.some(v => v.name === variantName)
);
if (exactMatch) return exactMatch;
// Then try to match by model ID/name
return MODEL_REGISTRY.find(def => def.model === cleanName);
}
export function getInstalledModels(
registry: ModelDefinition[],
installed: LocalModel[]
): ModelDefinition[] {
const installedNames = new Set(installed.map(m => m.name));
return registry.filter(def =>
def.variants.some(v => installedNames.has(v.name))
);
}
export function getAvailableModels(
registry: ModelDefinition[],
installed: LocalModel[]
): ModelDefinition[] {
const installedNames = new Set(installed.map(m => m.name));
return registry.filter(def =>
!def.variants.some(v => installedNames.has(v.name))
);
}
+8 -19
View File
@@ -118,27 +118,16 @@ export async function enrichQueries(
.replace('{{synonyms}}', context.synonyms.join(', ') || 'nenhum')
.replace('{{domain}}', context.domain || 'geral');
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt,
stream: false,
options: {
temperature: 0.5, // Temperatura média para criatividade controlada
num_predict: 1000, // Espaço suficiente para múltiplas queries
},
}),
const { invoke } = await import('@tauri-apps/api/core');
const rawResponse = await invoke<string>('generate_completion', {
model,
prompt,
options: {
temperature: 0.5, // Temperatura média para criatividade controlada
num_predict: 1000, // Espaço suficiente para múltiplas queries
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const rawResponse = (data.response || '').trim();
// Limpar resposta (remover markdown code blocks se houver)
let jsonText = rawResponse
.replace(/^```json\s*/i, '')
+8
View File
@@ -211,3 +211,11 @@ export function formatSearchLog(log: SearchLog): string {
return lines.join('\n');
}
+8
View File
@@ -38,3 +38,11 @@ export const deepResearchLog = {
+31
View File
@@ -13,3 +13,34 @@ export interface SourcesConfig {
last_updated: string;
}
export interface SearxngConfig {
searxng_url: string;
api_format: string;
default_engines: string[];
default_category: string;
timeout_seconds: number;
verify_ssl: boolean;
enabled: boolean;
log_search_content: boolean;
anonymize_identifiers: boolean;
encrypt_logs: boolean;
retention_days: number;
use_public_instances: boolean;
search_via_scraping: boolean;
}
export interface SearchLog {
id: string;
chat_id?: string;
user_id?: string;
timestamp: string;
searxng_instance?: string;
query: string;
engines?: string[];
filters?: string;
response_time_ms?: number;
total_results?: number;
results_per_engine?: string;
error?: string;
}
+41 -39
View File
@@ -63,7 +63,7 @@ const DEFAULT_CONFIG: FallbackConfig = {
maxRounds: 4,
maxResultsPerRound: 10,
maxTotalResults: 40,
minRelevanceScore: 0.3,
minRelevanceScore: 0.1,
enableQueryExpansion: true,
initialTimeout: TIMEOUT_CONFIG.initialTimeout,
maxTimeoutPerQuery: TIMEOUT_CONFIG.maxTimeoutPerQuery,
@@ -100,37 +100,33 @@ Instruções:
Query expandida:`;
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt: expansionPrompt,
stream: false,
options: {
temperature: 0.5,
num_predict: 100,
},
}),
});
const { invoke } = await import('@tauri-apps/api/core');
const expanded = (await invoke<string>('generate_completion', {
model,
prompt: expansionPrompt,
options: {
temperature: 0.5,
num_predict: 100,
},
}))
.replace(/^["']|["']$/g, '')
.replace(/\.$/, '')
.trim();
if (!response.ok) {
chatLog.warn('Failed to expand query, using original');
const sanitized = expanded
.replace(/<think>[\s\S]*?<\/think>/gi, '')
.replace(/^\s*```[a-zA-Z]*\s*/g, '')
.replace(/\s*```\s*$/g, '')
.replace(/^(here\s+is\s+the\s+query|sure|okay|final\s+query)\s*[:,-]*\s*/i, '')
.trim()
.replace(/[\r\n]+/g, ' ');
if (sanitized.length < 3 || sanitized.length > 150 || /[<>]/.test(sanitized)) {
return originalQuery;
}
const data = await response.json();
const expanded = (data.response || '').trim()
.replace(/^["']|["']$/g, '')
.replace(/\.$/, '')
.trim();
if (expanded.length < 3) {
return originalQuery;
}
chatLog.info(`Query expanded: "${originalQuery}" → "${expanded}"`);
return expanded;
chatLog.info(`Query expanded: "${originalQuery}" → "${sanitized}"`);
return sanitized;
} catch (error) {
chatLog.warn(`Error expanding query, using original: ${error instanceof Error ? error.message : String(error)}`);
return originalQuery;
@@ -155,7 +151,7 @@ export function calculateRelevanceScoreForFallback(
// Contar matches de palavras-chave
let matches = 0;
let totalWords = queryWords.length;
const totalWords = queryWords.length;
for (const word of queryWords) {
if (combinedText.includes(word)) {
@@ -283,7 +279,7 @@ export async function executeProgressiveSearch(
const finalConfig = { ...DEFAULT_CONFIG, ...config };
const attempts: FallbackAttempt[] = [];
let totalResultsAnalyzed = 0;
let allResults: ScrapedContent[] = [];
const allResults: ScrapedContent[] = [];
const seenUrls = new Set<string>();
const queryStartTime = Date.now();
const maxQueryTimeout = finalConfig.maxTimeoutPerQuery || TIMEOUT_CONFIG.maxTimeoutPerQuery;
@@ -337,8 +333,8 @@ export async function executeProgressiveSearch(
chatLog.info(`Round timeout: ${actualRoundTimeout}ms (adaptativo: ${roundTimeout}ms, máximo: ${maxRoundTimeout}ms)`);
const roundStartTime = Date.now();
let roundResults: ScrapedContent[] = [];
let roundQueries: string[] = [];
const roundResults: ScrapedContent[] = [];
const roundQueries: string[] = [];
// Executar todas as queries do round em paralelo com timeout por round
const queryPromises = roundConfig.queries.map(async (query) => {
@@ -351,8 +347,13 @@ export async function executeProgressiveSearch(
// Passar round para searchFn para timeout adaptativo
const results = await searchFn(finalQuery, finalConfig.maxResultsPerRound, round);
return { query: finalQuery, results };
<<<<<<< HEAD
} catch (err) {
chatLog.warn(`[Round ${round}] Query "${finalQuery}" failed: ${err instanceof Error ? err.message : String(err)}`);
=======
} catch (error) {
chatLog.warn(`[Round ${round}] Query "${finalQuery}" failed: ${error instanceof Error ? error.message : String(error)}`);
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
return { query: finalQuery, results: [] };
}
});
@@ -367,7 +368,7 @@ export async function executeProgressiveSearch(
let queryResults: QueryResult[];
try {
queryResults = await Promise.race([roundPromise, timeoutPromise]);
} catch (error) {
} catch (_err) {
chatLog.warn(`[Round ${round}] Timeout do round após ${actualRoundTimeout}ms, usando resultados parciais`);
// Tentar obter resultados parciais das promises que já completaram
queryResults = await Promise.allSettled(queryPromises).then(results =>
@@ -406,13 +407,15 @@ export async function executeProgressiveSearch(
}
// Calcular relevância usando contexto se disponível
let semanticSimilarity: ((ctx: QueryContext, text: string) => number) | null = null;
if (finalConfig.context) {
const mod = await import('./relevance-scorer');
semanticSimilarity = mod.calculateSemanticSimilarity;
}
const relevanceScore = roundResults.length > 0
? roundResults.reduce((sum, r) => {
// Usar contexto para cálculo de relevância se disponível
if (finalConfig.context) {
// Importar função dinamicamente para evitar circular dependency
const { calculateSemanticSimilarity } = require('./relevance-scorer');
return sum + calculateSemanticSimilarity(finalConfig.context, r.markdown || r.title);
if (semanticSimilarity && finalConfig.context) {
return sum + semanticSimilarity(finalConfig.context, r.markdown || r.title);
}
return sum + calculateRelevanceScoreForFallback(r, originalQuery);
}, 0) / roundResults.length
@@ -479,4 +482,3 @@ export async function executeProgressiveSearch(
usedFallback: true,
};
}