Lora-trainer / ProjectView.jsx
Allex21's picture
Upload 24 files
5bb2330 verified
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ArrowLeft, Play, Pause, Download, RefreshCw } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import '../App.css';
const ProjectView = ({ projectId, onBack }) => {
const [project, setProject] = useState(null);
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
fetchProject();
fetchLogs();
// Auto-refresh if project is running
const interval = setInterval(() => {
if (project?.status === 'running') {
fetchProject();
fetchLogs();
}
}, 5000);
return () => clearInterval(interval);
}, [projectId, project?.status]);
const fetchProject = async () => {
try {
const response = await fetch(`/api/projects/${projectId}`);
const data = await response.json();
setProject(data);
} catch (error) {
console.error('Erro ao buscar projeto:', error);
} finally {
setLoading(false);
}
};
const fetchLogs = async () => {
try {
const response = await fetch(`/api/projects/${projectId}/logs`);
const data = await response.json();
setLogs(data.logs || []);
} catch (error) {
console.error('Erro ao buscar logs:', error);
}
};
const handleRefresh = async () => {
setRefreshing(true);
await fetchProject();
await fetchLogs();
setRefreshing(false);
};
const handleStartTraining = async () => {
try {
await fetch(`/api/projects/${projectId}/start-training`, {
method: 'POST',
});
fetchProject();
} catch (error) {
console.error('Erro ao iniciar treinamento:', error);
}
};
const handleStopTraining = async () => {
try {
await fetch(`/api/projects/${projectId}/stop-training`, {
method: 'POST',
});
fetchProject();
} catch (error) {
console.error('Erro ao parar treinamento:', error);
}
};
const getStatusColor = (status) => {
switch (status) {
case 'completed':
return 'bg-green-500';
case 'running':
return 'bg-blue-500';
case 'failed':
return 'bg-red-500';
case 'pending':
return 'bg-yellow-500';
default:
return 'bg-gray-500';
}
};
const getStatusText = (status) => {
switch (status) {
case 'completed':
return 'Concluído';
case 'running':
return 'Treinando';
case 'failed':
return 'Falhou';
case 'pending':
return 'Pendente';
case 'cancelled':
return 'Cancelado';
default:
return 'Desconhecido';
}
};
// Generate mock training data for chart
const generateTrainingData = () => {
if (!project || !project.current_epoch) return [];
const data = [];
for (let i = 1; i <= project.current_epoch; i++) {
data.push({
epoch: i,
loss: Math.max(0.1, 1.0 - (i / project.num_epochs) * 0.8 + (Math.random() - 0.5) * 0.1)
});
}
return data;
};
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-lg">Carregando projeto...</div>
</div>
);
}
if (!project) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-lg text-red-600">Projeto não encontrado</div>
</div>
);
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button variant="outline" onClick={onBack} className="flex items-center gap-2">
<ArrowLeft className="w-4 h-4" />
Voltar
</Button>
<div>
<h1 className="text-3xl font-bold">{project.name}</h1>
<p className="text-gray-600 mt-2">
{project.description || 'Sem descrição'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
onClick={handleRefresh}
disabled={refreshing}
className="flex items-center gap-2"
>
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
Atualizar
</Button>
{project.status === 'pending' || project.status === 'failed' ? (
<Button onClick={handleStartTraining} className="flex items-center gap-2">
<Play className="w-4 h-4" />
Iniciar Treinamento
</Button>
) : project.status === 'running' ? (
<Button
variant="destructive"
onClick={handleStopTraining}
className="flex items-center gap-2"
>
<Pause className="w-4 h-4" />
Parar Treinamento
</Button>
) : project.status === 'completed' ? (
<Button variant="outline" className="flex items-center gap-2">
<Download className="w-4 h-4" />
Download Modelo
</Button>
) : null}
</div>
</div>
{/* Status Card */}
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle>Status do Projeto</CardTitle>
<Badge className={getStatusColor(project.status)}>
{getStatusText(project.status)}
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<div className="text-sm text-gray-600">Modelo Base</div>
<div className="font-medium">{project.base_model}</div>
</div>
<div>
<div className="text-sm text-gray-600">Progresso</div>
<div className="font-medium">
Época {project.current_epoch || 0} de {project.num_epochs}
</div>
</div>
<div>
<div className="text-sm text-gray-600">Criado em</div>
<div className="font-medium">
{new Date(project.created_at).toLocaleDateString('pt-BR')}
</div>
</div>
</div>
{project.status === 'running' && (
<div className="mt-4 space-y-2">
<div className="flex justify-between text-sm">
<span>Progresso do Treinamento</span>
<span>{Math.round((project.progress || 0) * 100)}%</span>
</div>
<Progress value={(project.progress || 0) * 100} />
{project.current_loss && (
<div className="text-sm text-gray-600">
Loss atual: {project.current_loss.toFixed(4)}
</div>
)}
</div>
)}
{project.error_message && (
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-md">
<div className="text-sm text-red-800">
<strong>Erro:</strong> {project.error_message}
</div>
</div>
)}
</CardContent>
</Card>
{/* Tabs */}
<Tabs defaultValue="config" className="space-y-4">
<TabsList>
<TabsTrigger value="config">Configuração</TabsTrigger>
<TabsTrigger value="training">Treinamento</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
</TabsList>
<TabsContent value="config" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle>Parâmetros LoRA</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">Rank:</span>
<span>{project.rank}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Alpha:</span>
<span>{project.alpha}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Dropout:</span>
<span>{project.dropout}</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Parâmetros de Treinamento</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">Taxa de Aprendizado:</span>
<span>{project.learning_rate}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Batch Size:</span>
<span>{project.batch_size}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Épocas:</span>
<span>{project.num_epochs}</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Otimizações</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">Otimizador 8-bit:</span>
<span>{project.use_8bit_optimizer ? 'Sim' : 'Não'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Gradient Checkpointing:</span>
<span>{project.use_gradient_checkpointing ? 'Sim' : 'Não'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Precisão Mista:</span>
<span>{project.mixed_precision}</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Dataset</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">Caminho:</span>
<span className="text-sm truncate">
{project.dataset_path || 'Não carregado'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Número de Imagens:</span>
<span>{project.num_images || 'N/A'}</span>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="training" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Gráfico de Loss</CardTitle>
<CardDescription>
Acompanhe a evolução da loss durante o treinamento
</CardDescription>
</CardHeader>
<CardContent>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={generateTrainingData()}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="epoch" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="loss"
stroke="#3b82f6"
strokeWidth={2}
dot={{ fill: '#3b82f6' }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="logs" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Logs de Treinamento</CardTitle>
<CardDescription>
Logs em tempo real do processo de treinamento
</CardDescription>
</CardHeader>
<CardContent>
<div className="bg-gray-900 text-green-400 p-4 rounded-md font-mono text-sm h-64 overflow-y-auto">
{logs.length === 0 ? (
<div className="text-gray-500">Nenhum log disponível</div>
) : (
logs.map((log, index) => (
<div key={index} className="mb-1">
{log}
</div>
))
)}
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
};
export default ProjectView;