Spaces:
No application file
No application file
| import React, { useState, useEffect } from 'react'; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Input } from '@/components/ui/input'; | |
| import { Label } from '@/components/ui/label'; | |
| import { Textarea } from '@/components/ui/textarea'; | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; | |
| import { Switch } from '@/components/ui/switch'; | |
| import { Slider } from '@/components/ui/slider'; | |
| import { ArrowLeft, Upload, Save } from 'lucide-react'; | |
| import '../App.css'; | |
| const CreateProject = ({ onBack, onProjectCreated }) => { | |
| const [formData, setFormData] = useState({ | |
| name: '', | |
| description: '', | |
| base_model: '', | |
| rank: 4, | |
| alpha: 32, | |
| dropout: 0.1, | |
| learning_rate: 0.0001, | |
| batch_size: 1, | |
| num_epochs: 10, | |
| use_8bit_optimizer: true, | |
| use_gradient_checkpointing: true, | |
| mixed_precision: 'fp16' | |
| }); | |
| const [availableModels, setAvailableModels] = useState([]); | |
| const [loading, setLoading] = useState(false); | |
| const [datasetFile, setDatasetFile] = useState(null); | |
| useEffect(() => { | |
| fetchAvailableModels(); | |
| }, []); | |
| const fetchAvailableModels = async () => { | |
| try { | |
| const response = await fetch('/api/models'); | |
| const data = await response.json(); | |
| setAvailableModels(data); | |
| } catch (error) { | |
| console.error('Erro ao buscar modelos:', error); | |
| } | |
| }; | |
| const handleInputChange = (field, value) => { | |
| setFormData(prev => ({ | |
| ...prev, | |
| [field]: value | |
| })); | |
| }; | |
| const handleFileChange = (event) => { | |
| const file = event.target.files[0]; | |
| setDatasetFile(file); | |
| }; | |
| const handleSubmit = async (event) => { | |
| event.preventDefault(); | |
| setLoading(true); | |
| try { | |
| // Create project | |
| const response = await fetch('/api/projects', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(formData), | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Erro ao criar projeto'); | |
| } | |
| const project = await response.json(); | |
| // Upload dataset if provided | |
| if (datasetFile) { | |
| const formDataUpload = new FormData(); | |
| formDataUpload.append('dataset', datasetFile); | |
| const uploadResponse = await fetch(`/api/projects/${project.id}/upload-dataset`, { | |
| method: 'POST', | |
| body: formDataUpload, | |
| }); | |
| if (!uploadResponse.ok) { | |
| throw new Error('Erro ao fazer upload do dataset'); | |
| } | |
| } | |
| onProjectCreated(project); | |
| } catch (error) { | |
| console.error('Erro ao criar projeto:', error); | |
| alert('Erro ao criar projeto: ' + error.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="space-y-6"> | |
| {/* Header */} | |
| <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">Criar Novo Projeto LoRA</h1> | |
| <p className="text-gray-600 mt-2"> | |
| Configure os parâmetros para seu novo projeto de treinamento | |
| </p> | |
| </div> | |
| </div> | |
| <form onSubmit={handleSubmit} className="space-y-6"> | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| {/* Basic Information */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>Informações Básicas</CardTitle> | |
| <CardDescription> | |
| Configure as informações básicas do seu projeto | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <div> | |
| <Label htmlFor="name">Nome do Projeto *</Label> | |
| <Input | |
| id="name" | |
| value={formData.name} | |
| onChange={(e) => handleInputChange('name', e.target.value)} | |
| placeholder="Ex: Meu Primeiro LoRA" | |
| required | |
| /> | |
| </div> | |
| <div> | |
| <Label htmlFor="description">Descrição</Label> | |
| <Textarea | |
| id="description" | |
| value={formData.description} | |
| onChange={(e) => handleInputChange('description', e.target.value)} | |
| placeholder="Descreva o objetivo do seu projeto..." | |
| rows={3} | |
| /> | |
| </div> | |
| <div> | |
| <Label htmlFor="base_model">Modelo Base *</Label> | |
| <Select | |
| value={formData.base_model} | |
| onValueChange={(value) => handleInputChange('base_model', value)} | |
| required | |
| > | |
| <SelectTrigger> | |
| <SelectValue placeholder="Selecione um modelo base" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {availableModels.map((model) => ( | |
| <SelectItem key={model.id} value={model.id}> | |
| {model.name} ({model.size}) | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| <div> | |
| <Label htmlFor="dataset">Dataset</Label> | |
| <Input | |
| id="dataset" | |
| type="file" | |
| onChange={handleFileChange} | |
| accept=".zip,.tar,.tar.gz" | |
| className="cursor-pointer" | |
| /> | |
| <p className="text-sm text-gray-500 mt-1"> | |
| Formatos aceitos: ZIP, TAR, TAR.GZ | |
| </p> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| {/* LoRA Parameters */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>Parâmetros LoRA</CardTitle> | |
| <CardDescription> | |
| Configure os parâmetros específicos do LoRA | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <div> | |
| <Label htmlFor="rank">Rank: {formData.rank}</Label> | |
| <Slider | |
| id="rank" | |
| min={1} | |
| max={128} | |
| step={1} | |
| value={[formData.rank]} | |
| onValueChange={(value) => handleInputChange('rank', value[0])} | |
| className="mt-2" | |
| /> | |
| <p className="text-sm text-gray-500 mt-1"> | |
| Menor rank = menos parâmetros, mais eficiente | |
| </p> | |
| </div> | |
| <div> | |
| <Label htmlFor="alpha">Alpha: {formData.alpha}</Label> | |
| <Slider | |
| id="alpha" | |
| min={1} | |
| max={128} | |
| step={1} | |
| value={[formData.alpha]} | |
| onValueChange={(value) => handleInputChange('alpha', value[0])} | |
| className="mt-2" | |
| /> | |
| <p className="text-sm text-gray-500 mt-1"> | |
| Controla a força da adaptação LoRA | |
| </p> | |
| </div> | |
| <div> | |
| <Label htmlFor="dropout">Dropout: {formData.dropout}</Label> | |
| <Slider | |
| id="dropout" | |
| min={0} | |
| max={0.5} | |
| step={0.01} | |
| value={[formData.dropout]} | |
| onValueChange={(value) => handleInputChange('dropout', value[0])} | |
| className="mt-2" | |
| /> | |
| <p className="text-sm text-gray-500 mt-1"> | |
| Previne overfitting durante o treinamento | |
| </p> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| {/* Training Parameters */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>Parâmetros de Treinamento</CardTitle> | |
| <CardDescription> | |
| Configure como o treinamento será executado | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <div> | |
| <Label htmlFor="learning_rate">Taxa de Aprendizado</Label> | |
| <Input | |
| id="learning_rate" | |
| type="number" | |
| step="0.00001" | |
| value={formData.learning_rate} | |
| onChange={(e) => handleInputChange('learning_rate', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| <div> | |
| <Label htmlFor="batch_size">Tamanho do Batch</Label> | |
| <Select | |
| value={formData.batch_size.toString()} | |
| onValueChange={(value) => handleInputChange('batch_size', parseInt(value))} | |
| > | |
| <SelectTrigger> | |
| <SelectValue /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="1">1 (Mais eficiente em memória)</SelectItem> | |
| <SelectItem value="2">2</SelectItem> | |
| <SelectItem value="4">4</SelectItem> | |
| <SelectItem value="8">8</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| <div> | |
| <Label htmlFor="num_epochs">Número de Épocas: {formData.num_epochs}</Label> | |
| <Slider | |
| id="num_epochs" | |
| min={1} | |
| max={50} | |
| step={1} | |
| value={[formData.num_epochs]} | |
| onValueChange={(value) => handleInputChange('num_epochs', value[0])} | |
| className="mt-2" | |
| /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| {/* Optimization Settings */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>Otimizações de GPU</CardTitle> | |
| <CardDescription> | |
| Configure otimizações para reduzir o uso de GPU | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <Label htmlFor="use_8bit_optimizer">Otimizador 8-bit</Label> | |
| <p className="text-sm text-gray-500"> | |
| Reduz uso de memória em ~50% | |
| </p> | |
| </div> | |
| <Switch | |
| id="use_8bit_optimizer" | |
| checked={formData.use_8bit_optimizer} | |
| onCheckedChange={(checked) => handleInputChange('use_8bit_optimizer', checked)} | |
| /> | |
| </div> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <Label htmlFor="use_gradient_checkpointing">Gradient Checkpointing</Label> | |
| <p className="text-sm text-gray-500"> | |
| Troca velocidade por menos memória | |
| </p> | |
| </div> | |
| <Switch | |
| id="use_gradient_checkpointing" | |
| checked={formData.use_gradient_checkpointing} | |
| onCheckedChange={(checked) => handleInputChange('use_gradient_checkpointing', checked)} | |
| /> | |
| </div> | |
| <div> | |
| <Label htmlFor="mixed_precision">Precisão Mista</Label> | |
| <Select | |
| value={formData.mixed_precision} | |
| onValueChange={(value) => handleInputChange('mixed_precision', value)} | |
| > | |
| <SelectTrigger> | |
| <SelectValue /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="fp16">FP16 (Recomendado)</SelectItem> | |
| <SelectItem value="bf16">BF16 (Para GPUs mais novas)</SelectItem> | |
| <SelectItem value="fp32">FP32 (Sem otimização)</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Submit Button */} | |
| <div className="flex justify-end"> | |
| <Button type="submit" disabled={loading} className="flex items-center gap-2"> | |
| <Save className="w-4 h-4" /> | |
| {loading ? 'Criando...' : 'Criar Projeto'} | |
| </Button> | |
| </div> | |
| </form> | |
| </div> | |
| ); | |
| }; | |
| export default CreateProject; | |