chore: expurgo de blobs excedentes e realinhamento de estado
release.yml (push) Invalid workflow file
release.yml (push) Invalid workflow file
This commit is contained in:
1 parent
593efd42e0
commit
a7cbd1a3b4
114 files changed
+20716
-4258
No files matched your search
+8
-19
@@ -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
@@ -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');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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, '')
|
||||
|
||||
@@ -211,3 +211,11 @@ export function formatSearchLog(log: SearchLog): string {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,3 +38,11 @@ export const deepResearchLog = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user