feat: enhance application functionality and telemetry monitoring

- Added new scripts in `package.json` for Python dependency installation and sidecar compilation.
- Integrated telemetry fetching in `src/App.tsx` to monitor CPU, RAM, GPU, and disk usage.
- Implemented model downloading functionality with progress tracking in `src/App.tsx`.
- Updated `Footer` and `Header` components to display telemetry data and settings options.
- Enhanced Tauri configuration for sidecar execution and application window settings.
- Updated Python requirements in `requirements.txt` to include additional dependencies for improved functionality.
This commit is contained in:
Evandro Fonseca Junior committed 2026-01-14 23:57:14 -03:00
1 parent 482b28513e
commit 08e154894a
24 files changed
+1110 -91

No files matched your search

+111 -3
View File
@@ -1,5 +1,5 @@
import "@fontsource/inter";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import {
Sheet, Box, Textarea, IconButton, Tooltip, Typography,
Menu, MenuItem, Dropdown, MenuButton, Badge
@@ -13,9 +13,22 @@ import {
// Componentes de Interface Padronizados
import Header from "./components/interface/Header";
import Footer from "./components/interface/Footer";
import { SettingsModal } from "./components/interface/SettingsModal";
const API_BASE = "http://127.0.0.1:8000";
export default function App() {
const [selectedModel, setSelectedModel] = useState("Llama-3.1-8B");
const [stats, setStats] = useState({
cpu: 0,
ram_used: 0,
ram_total: 8,
gpu: { usage: 0, vram_used: 0, vram_total: 0 },
disk_free_gb: 0
});
const [models, setModels] = useState<any[]>([]);
const [settingsOpen, setSettingsOpen] = useState(false);
const [downloadingModel, setDownloadingModel] = useState<string | null>(null);
// Constantes de Design (Tokens)
const ICON_SIZE = 18;
@@ -23,9 +36,98 @@ export default function App() {
const BORDER_STYLE = "border-white/[0.06]";
const SURFACE_COLOR = "bg-[#121212]";
// Polling de telemetria
useEffect(() => {
const fetchTelemetry = async () => {
try {
const response = await fetch(`${API_BASE}/api/telemetry`);
const data = await response.json();
setStats(data);
} catch (error) {
console.error("Erro ao buscar telemetria:", error);
}
};
fetchTelemetry();
const interval = setInterval(fetchTelemetry, 2000); // Atualiza a cada 2s
return () => clearInterval(interval);
}, []);
// Carregar modelos
useEffect(() => {
const fetchModels = async () => {
try {
const response = await fetch(`${API_BASE}/api/models`);
const data = await response.json();
setModels(data.models || []);
} catch (error) {
console.error("Erro ao carregar modelos:", error);
}
};
fetchModels();
}, []);
// Função de download com EventSource
const handleDownload = async (model: any) => {
if (downloadingModel) return;
setDownloadingModel(model.id);
try {
const response = await fetch(`${API_BASE}/api/models/download`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model_id: model.id,
repo_id: model.repo,
filename: model.filename
})
});
if (!response.ok) throw new Error("Falha ao iniciar download");
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) throw new Error("Stream não disponível");
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = JSON.parse(line.slice(6));
if (data.type === "complete") {
setDownloadingModel(null);
// Recarrega lista de modelos
const res = await fetch(`${API_BASE}/api/models`);
const modelsData = await res.json();
setModels(modelsData.models || []);
break;
} else if (data.type === "error") {
setDownloadingModel(null);
console.error("Erro no download:", data.message);
break;
}
// Progresso pode ser usado para atualizar UI se necessário
}
}
}
} catch (error) {
console.error("Erro no download:", error);
setDownloadingModel(null);
}
};
return (
<div className="flex flex-col h-screen bg-[#0A0A0A] text-slate-200 overflow-hidden font-inter selection:bg-cyan-500/30">
<Header />
<Header onSettingsClick={() => setSettingsOpen(true)} />
<main className="flex flex-1 overflow-hidden border-t border-white/[0.05]">
@@ -182,7 +284,13 @@ export default function App() {
</div>
</section>
</main>
<Footer />
<Footer stats={stats} />
<SettingsModal
open={settingsOpen}
onClose={() => setSettingsOpen(false)}
models={models}
onDownload={handleDownload}
/>
</div>
);
}
+52 -70
View File
@@ -1,76 +1,58 @@
import "@fontsource/inter";
import { Typography, Divider, Link, Box } from "@mui/joy";
import { Github, Database, Cpu, ShieldCheck, Terminal } from "lucide-react";
import { LinearProgress, Typography, Box, Tooltip } from "@mui/joy";
import { Cpu, Database, HardDrive, Activity } from "lucide-react";
export default function Footer() {
const currentYear = new Date().getFullYear();
const modelName = "Llama-3.1-8B-Q4_K_M.gguf"; // Exemplo técnico
// Componente de Telemetria Interno
const StatMonitor = ({ icon: Icon, label, value, percent, color }: any) => (
<Box sx={{ width: 120 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
<Typography level="body-xs" sx={{ display: 'flex', alignItems: 'center', gap: 0.5, color: 'slate.400' }}>
<Icon size={12} /> {label}
</Typography>
<Typography level="body-xs" sx={{ color: 'white', fontWeight: 'bold' }}>{value}</Typography>
</Box>
<LinearProgress
determinate
value={percent}
size="sm"
color={color}
sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}
/>
</Box>
);
export default function EnhancedFooter({ stats }: { stats: any }) {
return (
<footer className="w-full bg-[#0A0A0A] border-t border-white/[0.05] px-6 py-4">
{/* Container Principal */}
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
{/* Lado Esquerdo: Metadados do App */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.5)]"></div>
<Typography level="body-xs" sx={{ color: 'white', fontWeight: 600, fontFamily: 'Inter' }}>
SYSTEM SECURE
</Typography>
</div>
<Divider orientation="vertical" sx={{ height: '14px', bgcolor: 'white/10' }} />
<Typography level="body-xs" sx={{ color: 'rgba(255,255,255,0.4)', fontFamily: 'Inter' }}>
&copy; {currentYear} Evandro Fonseca Jr.
</Typography>
</div>
{/* Centro: Telemetria da IA (Onde o engenheiro foca) */}
<div className="flex items-center gap-6 px-4 py-1.5 bg-white/[0.02] border border-white/[0.05] rounded-lg">
<div className="flex items-center gap-2">
<Database size={14} className="text-cyan-500" />
<span className="text-[11px] font-mono text-slate-400 uppercase tracking-wider">Model:</span>
<span className="text-[11px] font-mono text-slate-200">{modelName}</span>
</div>
<div className="flex items-center gap-2">
<Cpu size={14} className="text-purple-500" />
<span className="text-[11px] font-mono text-slate-400 uppercase tracking-wider">VRAM:</span>
<span className="text-[11px] font-mono text-slate-200">5.2 GB / 8.0 GB</span>
</div>
</div>
{/* Lado Direito: Links Técnicos e Segurança */}
<div className="flex items-center gap-5">
<Link
href="#"
startDecorator={<Terminal size={14} />}
sx={{
fontSize: '12px',
color: 'rgba(255,255,255,0.5)',
textDecoration: 'none',
'&:hover': { color: 'white' }
}}
>
API Logs
</Link>
<Link
href="https://github.com"
target="_blank"
sx={{ color: 'rgba(255,255,255,0.5)', '&:hover': { color: 'white' } }}
>
<Github size={18} />
</Link>
<Tooltip title="End-to-End Encrypted Local Session" variant="soft">
<ShieldCheck size={18} className="text-slate-500 cursor-help" />
</Tooltip>
</div>
<footer className="w-full bg-[#0A0A0A] border-t border-white/[0.05] px-6 py-3 flex items-center justify-between">
<div className="flex gap-8">
<StatMonitor
icon={Cpu} label="CPU"
value={`${stats.cpu}%`}
percent={stats.cpu}
color={stats.cpu > 80 ? "danger" : "primary"}
/>
<StatMonitor
icon={Activity} label="GPU"
value={`${stats.gpu.usage}%`}
percent={stats.gpu.usage}
color="success"
/>
<StatMonitor
icon={Database} label="RAM"
value={`${stats.ram_used.toFixed(1)}GB`}
percent={(stats.ram_used / stats.ram_total) * 100}
color="warning"
/>
<StatMonitor
icon={HardDrive} label="DISK FREE"
value={`${stats.disk_free_gb.toFixed(0)}GB`}
percent={100}
color="neutral"
/>
</div>
<Typography level="body-xs" sx={{ color: 'white/30', fontAlpha: 0.5 }}>
ENG_MODE: ACTIVE | OS: {window.navigator.platform}
</Typography>
</footer>
);
}
// Tooltip auxiliar (MUI Joy precisa do import)
import { Tooltip } from "@mui/joy";
}
+2 -1
View File
@@ -6,7 +6,7 @@ import { Home, Settings, Cpu, Sparkles } from "lucide-react";
const appName = import.meta.env.VITE_APPNAME || "LocalMind AI";
export default function Header() {
export default function Header({ onSettingsClick }: { onSettingsClick?: () => void }) {
// Simulando um estado da IA (você conectaria isso ao seu backend real depois)
const isModelLoaded = true;
@@ -85,6 +85,7 @@ export default function Header() {
<IconButton
variant="plain"
color="neutral"
onClick={onSettingsClick}
sx={{
color: '#94a3b8',
'&:hover': { color: 'white', backgroundColor: 'rgba(255,255,255, 0.05)' }
@@ -0,0 +1,51 @@
import { Modal, ModalDialog, ModalClose, Tabs, TabList, Tab, TabPanel, Sheet, List, ListItem, ListItemButton, ListItemContent, Button, Typography } from "@mui/joy";
import { Download, CheckCircle2, Server } from "lucide-react";
export function SettingsModal({ open, onClose, models, onDownload }: any) {
return (
<Modal open={open} onClose={onClose}>
<ModalDialog sx={{ bgcolor: '#0F0F0F', border: '1px solid rgba(255,255,255,0.1)', minWidth: 600 }}>
<ModalClose sx={{ color: 'white' }} />
<Typography level="h4" sx={{ color: 'white', mb: 2 }}>System Configuration</Typography>
<Tabs aria-label="Settings tabs" defaultValue={0} sx={{ bgcolor: 'transparent' }}>
<TabList sx={{ bgcolor: 'rgba(255,255,255,0.03)', p: 0.5, borderRadius: 'lg' }}>
<Tab disableIndicator sx={{ color: 'slate.400', '&.Mui-selected': { color: 'white', bgcolor: 'rgba(255,255,255,0.05)' } }}>
Model Manager
</Tab>
<Tab disableIndicator sx={{ color: 'slate.400' }}>Inference Engine</Tab>
</TabList>
<TabPanel value={0} sx={{ px: 0, py: 2 }}>
<Sheet sx={{ bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 'md', maxHeight: 400, overflow: 'auto' }}>
<List>
{models.map((model: any) => (
<ListItem key={model.id} sx={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<ListItemButton sx={{ gap: 2 }}>
<Server className="text-cyan-500" />
<ListItemContent>
<Typography level="title-sm" sx={{ color: 'white' }}>{model.name} <span className="text-[10px] text-slate-500">{model.quant}</span></Typography>
<Typography level="body-xs" sx={{ color: 'slate.400' }}>{model.size_gb} GB • {model.repo}</Typography>
</ListItemContent>
{model.downloaded ? (
<Button size="sm" variant="soft" color="success" startDecorator={<CheckCircle2 size={16}/>}>Ready</Button>
) : (
<Button size="sm" variant="solid" color="primary" onClick={() => onDownload(model)} startDecorator={<Download size={16}/>}>Download</Button>
)}
</ListItemButton>
</ListItem>
))}
</List>
</Sheet>
</TabPanel>
<TabPanel value={1}>
<Typography level="body-sm" sx={{ color: 'slate-400' }}>
Hiperparâmetros de Engenharia (Threads, Context, GPU Offload) em desenvolvimento...
</Typography>
</TabPanel>
</Tabs>
</ModalDialog>
</Modal>
);
}