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:
1 parent
482b28513e
commit
08e154894a
24 files changed
+1110
-91
No files matched your search
+111
-3
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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' }}>
|
||||
© {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";
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user