'use client'; import { useState, useEffect, lazy, Suspense } from 'react'; import { useState, useEffect, lazy, Suspense } from 'react'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { Slider } from '@/components/ui/slider'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Badge } from '@/components/ui/badge'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { useSettingsStore } from '@/store/settings-store'; import { DomainTagsInput } from '@/components/settings/domain-tags-input'; import { SearxngOnboarding } from '@/components/settings/searxng-onboarding'; import { SearxngInstallWizard } from '@/components/settings/searxng-install-wizard'; import { DockerSettingsPanel } from '@/components/settings/docker-settings-panel'; const HardwareDashboard = lazy(() => import('@/components/settings/HardwareDashboard').then(m => ({ default: m.HardwareDashboard }))); import { RuntimeSettingsPanel } from '@/components/settings/runtime-settings-panel'; import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; import { CheckCircle2, XCircle, Loader2, Download, Trash2, Copy, ExternalLink, Plus, X, BookOpen, GraduationCap, Newspaper, Code, DollarSign, Edit, RotateCcw, Terminal, Power, ArrowLeft, Sparkles, RefreshCw, Settings, Square } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { useRouter } from 'next/navigation'; import { SourcesConfig, SourceCategory } from '@/lib/types'; import { TitleBar } from '@/components/titlebar'; import { useAppUpdater } from '@/hooks/use-app-updater'; import { getVersion } from '@tauri-apps/api/app'; import { useAppUpdater } from '@/hooks/use-app-updater'; import { getVersion } from '@tauri-apps/api/app'; interface Model { name: string; modified_at: string; size: number; } export default function SettingsPage() { const settings = useSettingsStore(); const { toast } = useToast(); const router = useRouter(); const [isCheckingConnection, setIsCheckingConnection] = useState(false); const [showSearxngOnboarding, setShowSearxngOnboarding] = useState(false); const [showSearxngWizard, setShowSearxngWizard] = useState(false); const [connectionStatus, setConnectionStatus] = useState<'idle' | 'success' | 'error'>('idle'); const [availableModels, setAvailableModels] = useState([]); const [isLoadingModels, setIsLoadingModels] = useState(false); const [storagePath, setStoragePath] = useState(''); const [showClearDialog, setShowClearDialog] = useState(false); const [isExporting, setIsExporting] = useState(false); const [isClearing, setIsClearing] = useState(false); const [editingCategory, setEditingCategory] = useState(null); const [isSavingSources, setIsSavingSources] = useState(false); const [recentLogs, setRecentLogs] = useState([]); const [isLoadingLogs, setIsLoadingLogs] = useState(false); const [appVersion, setAppVersion] = useState(''); const [activeTab, setActiveTab] = useState('overview'); const updater = useAppUpdater(); const [appVersion, setAppVersion] = useState(''); const [activeTab, setActiveTab] = useState('overview'); const updater = useAppUpdater(); // Load models, storage path, and sources config on mount useEffect(() => { loadModels(); loadStoragePath(); settings.fetchSources(); // Load app version getVersion() .then(setAppVersion) .catch(() => setAppVersion('0.1.0')); // Load app version getVersion() .then(setAppVersion) .catch(() => setAppVersion('0.1.0')); }, [settings.ollamaUrl]); // Load logs periodically useEffect(() => { const loadLogs = async () => { try { setIsLoadingLogs(true); const logs = await invoke('get_recent_logs', { lines: 100 }); setRecentLogs(logs); } catch (error) { console.error('Failed to load logs:', error); } finally { setIsLoadingLogs(false); } }; loadLogs(); const logsInterval = setInterval(loadLogs, 5000); // Refresh logs every 5 seconds // Periodic model check (fallback) const modelsInterval = setInterval(loadModels, 30000); // Refresh models every 30 seconds return () => { clearInterval(logsInterval); clearInterval(modelsInterval); }; }, []); // Listen for model download completion useEffect(() => { let unlisten: (() => void) | undefined; const setupListener = async () => { unlisten = await listen('download-progress', (event) => { try { const payload = JSON.parse(event.payload); if (payload.status === 'success') { loadModels(); toast({ title: 'Novo modelo detectado', description: 'A lista de modelos foi atualizada.', }); } } catch (e) { console.error('Error parsing download progress:', e); } }); }; setupListener(); return () => { if (unlisten) unlisten(); }; }, []); const loadModels = async () => { setIsLoadingModels(true); try { const models = await invoke>('get_ollama_tags'); // Mapear para o formato esperado pela interface Model const mappedModels: Model[] = (models || []).map(model => ({ name: model.name, size: model.size ?? 0, modified_at: model.modified_at ?? '', })); setAvailableModels(mappedModels); } catch (error) { console.error('Failed to load models:', error); setAvailableModels([]); } finally { setIsLoadingModels(false); } }; const loadStoragePath = async () => { try { const path = await invoke('get_app_data_dir'); setStoragePath(path); } catch (error) { console.error('Failed to load storage path:', error); setStoragePath('N/A'); } }; const checkConnection = async () => { setIsCheckingConnection(true); setConnectionStatus('idle'); try { // Verificar conexão tentando listar modelos await invoke('get_ollama_tags'); setConnectionStatus('success'); toast({ title: 'Conexão bem-sucedida', description: 'Ollama está acessível neste endpoint.', }); // Reload models after successful connection loadModels(); } catch (error) { setConnectionStatus('error'); toast({ title: 'Erro de conexão', description: error instanceof Error ? error.message : 'Falha ao conectar', variant: 'destructive', }); } finally { setIsCheckingConnection(false); } }; const handleExportChats = async () => { setIsExporting(true); try { await invoke('export_chat_sessions'); toast({ title: 'Exportação concluída', description: 'Todos os chats foram exportados com sucesso.', }); } catch (error) { console.error('Failed to export chats:', error); toast({ title: 'Erro na exportação', description: error instanceof Error ? error.message : 'Falha ao exportar', variant: 'destructive', }); } finally { setIsExporting(false); } }; const handleExportAllData = async () => { setIsExporting(true); try { const zipPath = await invoke('export_all_data'); toast({ title: 'Backup completo exportado', description: `Arquivo salvo em: ${zipPath}`, }); } catch (error) { console.error('Failed to export all data:', error); toast({ title: 'Erro na exportação', description: error instanceof Error ? error.message : 'Falha ao exportar', variant: 'destructive', }); } finally { setIsExporting(false); } }; const handleClearHistory = async () => { setIsClearing(true); try { await invoke('clear_chat_history'); toast({ title: 'Histórico apagado', description: 'Todas as conversas foram removidas.', }); setShowClearDialog(false); } catch (error) { console.error('Failed to clear history:', error); toast({ title: 'Erro ao apagar', description: error instanceof Error ? error.message : 'Falha ao apagar histórico', variant: 'destructive', }); } finally { setIsClearing(false); } }; const copyStoragePath = async () => { try { await navigator.clipboard.writeText(storagePath); toast({ title: 'Caminho copiado', description: 'O caminho foi copiado para a área de transferência.', }); } catch (error) { console.error('Failed to copy path:', error); } }; return (

Configurações

Gerencie as preferências do OllaHub

Visão Geral General Web Search Sources SearXNG Tasks System {/* Tab: Visão Geral (Hardware Dashboard) */} Dashboard de Hardware Monitoramento em tempo real do sistema (atualizado a cada 1 segundo) {activeTab === 'overview' && (
{[1, 2, 3, 4].map((i) => (
))}
}> )} {activeTab === 'overview' && (
{[1, 2, 3, 4].map((i) => (
))}
}> )}
{/* Tab: General */} Conexão Ollama Configure o endpoint do Ollama local
settings.setOllamaUrl(e.target.value)} placeholder="http://localhost:11434" className="flex-1 min-w-0" />
{isLoadingModels ? (
Carregando modelos...
) : availableModels.length > 0 ? ( ) : ( settings.setSelectedModel(e.target.value)} placeholder="Digite o nome do modelo..." /> )}
System Prompt Defina a personalidade e comportamento padrão da IA