YAML Metadata Warning: empty or missing yaml metadata in repo card
Check out the documentation for more information.
import React, { useState, useRef, useEffect, ChangeEvent, DragEvent } from 'react'; import { FaRobot, FaCloudUploadAlt, FaImages, FaFolderOpen, FaTimes, FaEye, FaCheck, FaCopy, FaEdit, FaRedo, FaBolt, FaTrash, FaCog, FaChevronUp, FaPalette, FaSun, FaCropAlt, FaLightbulb, FaExclamationCircle, FaExclamationTriangle, FaInfoCircle, FaCheckCircle, FaServer, FaCode, FaStar, FaTerminal, FaExternalLinkAlt } from 'react-icons/fa';
// Tipos de dados interface ImageData { id: string; name: string; size: number; type: string; dataUrl: string; file: File; analyzed?: boolean; }
interface ColorAnalysis { dominantColors: string[]; paletteType: string; }
interface ImageAnalysis { colors: ColorAnalysis; brightness: string; saturation: string; contrast: string; composition: string; width: number; height: number; aspectRatio: number; }
interface PromptBreakdown { style: string; colors: string; composition: string; length: string; wordCount: number; }
// Componente principal const PromptGenerator: React.FC = () => { // Estados const [loadedImages, setLoadedImages] = useState<ImageData[]>([]); const [currentAnalysis, setCurrentAnalysis] = useState<ImageAnalysis | null>(null); const [promptOutput, setPromptOutput] = useState(''); const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' | 'warning' | 'info' } | null>(null); const [copySuccess, setCopySuccess] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false);
// Configurações const [style, setStyle] = useState('digital_art'); const [detailLevel, setDetailLevel] = useState(8); const [lighting, setLighting] = useState('auto'); const [composition, setComposition] = useState('auto'); const [extraParams, setExtraParams] = useState('');
// Referências const fileInputRef = useRef(null); const notificationRef = useRef(null);
// Templates de estilo const styleTemplates: Record<string, string> = { photorealistic: "photorealistic, 8K resolution, detailed texture, realistic lighting, sharp focus, professional photography", digital_art: "digital art, vibrant colors, detailed, trending on ArtStation, fantasy art, concept art", oil_painting: "oil painting, textured brush strokes, canvas texture, rich colors, traditional art, masterpiece", watercolor: "watercolor painting, soft edges, translucent layers, paper texture, artistic, delicate", anime: "anime style, cel shading, vibrant colors, detailed background, manga illustration, Japanese animation", cyberpunk: "cyberpunk, neon lighting, futuristic, dystopian, rainy night, cinematic, Blade Runner aesthetic", fantasy: "fantasy art, epic, magical, detailed environment, ethereal, concept art, mystical", minimalist: "minimalist, simple composition, clean lines, negative space, modern art, elegant", impressionism: "impressionism, visible brush strokes, emphasis on light, dreamy, artistic, painted", surrealism: "surrealism, dreamlike, impossible scenes, symbolic, artistic, imaginative" };
// Mostrar notificação const showNotification = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'success') => { setNotification({ message, type }); setTimeout(() => setNotification(null), 3000); };
// Manipular arrastar e soltar const handleDragOver = (e: DragEvent) => { e.preventDefault(); e.currentTarget.classList.add('dragover'); };
const handleDragLeave = (e: DragEvent) => { e.preventDefault(); e.currentTarget.classList.remove('dragover'); };
const handleDrop = (e: DragEvent) => { e.preventDefault(); e.currentTarget.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files).filter(file =>
file.type.startsWith('image/')
);
if (files.length === 0) {
showNotification('Por favor, solte apenas arquivos de imagem.', 'error');
return;
}
files.forEach(processImageFile);
};
// Processar arquivo de imagem
const processImageFile = (file: File) => {
// Verificar tamanho (5MB máximo)
if (file.size > 5 * 1024 * 1024) {
showNotification(Imagem "${file.name}" excede 5MB e não foi carregada., 'error');
return;
}
// Verificar se a imagem já foi carregada
if (loadedImages.some(img => img.name === file.name && img.size === file.size)) {
showNotification(`Imagem "${file.name}" já foi carregada.`, 'info');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const imgData: ImageData = {
id: Date.now() + Math.random().toString(),
name: file.name,
size: file.size,
type: file.type,
dataUrl: e.target?.result as string,
file: file,
analyzed: false
};
setLoadedImages(prev => [...prev, imgData]);
showNotification(`Imagem "${file.name}" carregada com sucesso!`, 'success');
// Analisar automaticamente após um breve delay
setTimeout(() => analyzeImage(imgData), 500);
};
reader.onerror = () => {
showNotification(`Erro ao carregar "${file.name}".`, 'error');
};
reader.readAsDataURL(file);
};
// Selecionar arquivos const handleFileSelect = (e: ChangeEvent) => { const files = Array.from(e.target.files || []);
files.forEach(processImageFile);
// Resetar input para permitir selecionar o mesmo arquivo novamente
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
// Remover imagem const removeImage = (id: string) => { const imageToRemove = loadedImages.find(img => img.id === id); setLoadedImages(prev => prev.filter(img => img.id !== id));
if (imageToRemove) {
showNotification(`Imagem "${imageToRemove.name}" removida.`, 'info');
}
};
// Limpar todas as imagens const clearAllImages = () => { setLoadedImages([]); setCurrentAnalysis(null); setPromptOutput(''); showNotification('Todas as imagens foram removidas.', 'info'); };
// Analisar imagem com Canvas API const analyzeImage = async (imgData: ImageData) => { setIsAnalyzing(true);
try {
// Criar uma imagem para análise
const img = new Image();
img.src = imgData.dataUrl;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
// Criar canvas para análise
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Não foi possível obter contexto do canvas');
}
// Limitar tamanho para performance
const maxSize = 400;
let width = img.width;
let height = img.height;
if (width > height && width > maxSize) {
height = Math.round(height * maxSize / width);
width = maxSize;
} else if (height > maxSize) {
width = Math.round(width * maxSize / height);
height = maxSize;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// Obter dados da imagem
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// Análises
const colorAnalysis = analyzeColors(data, width * height);
const brightnessAnalysis = analyzeBrightness(data);
const saturationAnalysis = analyzeSaturation(data);
const contrastAnalysis = analyzeContrast(data);
const compositionAnalysis = analyzeComposition(img.width, img.height);
// Criar objeto de análise
const analysis: ImageAnalysis = {
colors: colorAnalysis,
brightness: brightnessAnalysis,
saturation: saturationAnalysis,
contrast: contrastAnalysis,
composition: compositionAnalysis,
width: img.width,
height: img.height,
aspectRatio: parseFloat((img.width / img.height).toFixed(2))
};
setCurrentAnalysis(analysis);
// Marcar imagem como analisada
setLoadedImages(prev => prev.map(img =>
img.id === imgData.id ? { ...img, analyzed: true } : img
));
showNotification(`Imagem "${imgData.name}" analisada com sucesso!`, 'success');
} catch (error) {
console.error('Erro na análise da imagem:', error);
showNotification(`Erro ao analisar imagem "${imgData.name}".`, 'error');
} finally {
setIsAnalyzing(false);
}
};
// Funções auxiliares de análise const analyzeColors = (data: Uint8ClampedArray, pixelCount: number): ColorAnalysis => { const colorMap: Record<string, number> = {}; const sampleStep = Math.max(1, Math.floor(pixelCount / 10000));
for (let i = 0; i < data.length; i += 4 * sampleStep) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const hex = rgbToHex(r, g, b);
const groupedHex = groupColor(hex);
colorMap[groupedHex] = (colorMap[groupedHex] || 0) + 1;
}
// Ordenar cores por frequência
const sortedColors = Object.entries(colorMap)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(entry => entry[0]);
// Determinar paleta de cores
const paletteType = determinePaletteType(sortedColors);
return { dominantColors: sortedColors, paletteType };
};
const groupColor = (hex: string): string => { const r = parseInt(hex.substr(1, 2), 16); const g = parseInt(hex.substr(3, 2), 16); const b = parseInt(hex.substr(5, 2), 16);
// Agrupar em intervalos de 32
const groupedR = Math.floor(r / 32) * 32;
const groupedG = Math.floor(g / 32) * 32;
const groupedB = Math.floor(b / 32) * 32;
return rgbToHex(groupedR, groupedG, groupedB);
};
const determinePaletteType = (colors: string[]): string => { let warmCount = 0; let coolCount = 0; let saturatedCount = 0;
colors.forEach(hex => {
const r = parseInt(hex.substr(1, 2), 16);
const g = parseInt(hex.substr(3, 2), 16);
const b = parseInt(hex.substr(5, 2), 16);
// Determinar se é cor quente ou fria
if (r > g && r > b) warmCount++;
if (b > r && b > g) coolCount++;
// Determinar saturação
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const saturation = max === 0 ? 0 : (max - min) / max;
if (saturation > 0.5) saturatedCount++;
});
if (warmCount > coolCount && warmCount > 2) return "quente";
if (coolCount > warmCount && coolCount > 2) return "fria";
if (saturatedCount > 2) return "vibrante";
if (colors.length > 0 && colors[0].substr(1, 6) === "000000") return "escura";
return "equilibrada";
};
const analyzeBrightness = (data: Uint8ClampedArray): string => { let totalBrightness = 0; const sampleStep = Math.max(1, Math.floor(data.length / 40000)); let sampleCount = 0;
for (let i = 0; i < data.length; i += 4 * sampleStep) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Fórmula de luminosidade perceptiva
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
totalBrightness += brightness;
sampleCount++;
}
const avgBrightness = totalBrightness / sampleCount;
const normalized = avgBrightness / 255;
if (normalized > 0.7) return "alta";
if (normalized > 0.4) return "média";
return "baixa";
};
const analyzeSaturation = (data: Uint8ClampedArray): string => { let totalSaturation = 0; const sampleStep = Math.max(1, Math.floor(data.length / 40000)); let sampleCount = 0;
for (let i = 0; i < data.length; i += 4 * sampleStep) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const saturation = max === 0 ? 0 : (max - min) / max;
totalSaturation += saturation;
sampleCount++;
}
const avgSaturation = totalSaturation / sampleCount;
if (avgSaturation > 0.6) return "alta";
if (avgSaturation > 0.3) return "média";
return "baixa";
};
const analyzeContrast = (data: Uint8ClampedArray): string => { let minBrightness = 255; let maxBrightness = 0; const sampleStep = Math.max(1, Math.floor(data.length / 40000));
for (let i = 0; i < data.length; i += 4 * sampleStep) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
minBrightness = Math.min(minBrightness, brightness);
maxBrightness = Math.max(maxBrightness, brightness);
}
const contrast = (maxBrightness - minBrightness) / 255;
if (contrast > 0.7) return "alto";
if (contrast > 0.4) return "médio";
return "baixo";
};
const analyzeComposition = (width: number, height: number): string => { const ratio = width / height;
if (ratio > 1.5) return "paisagem";
if (ratio < 0.7) return "retrato";
if (ratio >= 0.9 && ratio <= 1.1) return "quadrada";
return "padrão";
};
const rgbToHex = (r: number, g: number, b: number): string => { return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase(); };
// Gerar prompt baseado na análise const generatePrompt = () => { if (!currentAnalysis || loadedImages.length === 0) { showNotification('Por favor, analise uma imagem primeiro.', 'error'); return; }
// Base do prompt baseado na análise
let prompt = "";
// Adicionar tipo de composição
if (composition === 'auto') {
if (currentAnalysis.composition === 'paisagem') {
prompt += "A landscape scene";
} else if (currentAnalysis.composition === 'retrato') {
prompt += "A portrait";
} else {
prompt += "A detailed scene";
}
} else {
prompt += `A ${composition.replace('_', ' ')}`;
}
// Adicionar características de cores
const paletteType = currentAnalysis.colors.paletteType;
if (paletteType === 'quente') {
prompt += " with warm color palette, golden tones";
} else if (paletteType === 'fria') {
prompt += " with cool color palette, blue and purple tones";
} else if (paletteType === 'vibrante') {
prompt += " with vibrant saturated colors, high contrast";
} else if (paletteType === 'escura') {
prompt += " with dark atmospheric tones, low-key lighting";
}
// Adicionar luminosidade
if (currentAnalysis.brightness === 'alta') {
prompt += ", bright illumination, well-lit";
} else if (currentAnalysis.brightness === 'baixa') {
prompt += ", low-light atmosphere, moody lighting";
}
// Adicionar saturação
if (currentAnalysis.saturation === 'alta') {
prompt += ", highly saturated colors";
} else if (currentAnalysis.saturation === 'baixa') {
prompt += ", desaturated colors, muted tones";
}
// Adicionar estilo artístico
prompt += `, ${styleTemplates[style]}`;
// Adicionar iluminação
if (lighting !== 'auto') {
prompt += `, ${lighting.replace('_', ' ')} lighting`;
} else {
// Sugerir iluminação baseada na análise
if (currentAnalysis.brightness === 'alta' && currentAnalysis.contrast === 'alto') {
prompt += ", dramatic lighting with strong shadows";
} else if (currentAnalysis.brightness === 'média' && currentAnalysis.contrast === 'médio') {
prompt += ", balanced natural lighting";
} else if (currentAnalysis.brightness === 'baixa') {
prompt += ", moody atmospheric lighting";
}
}
// Adicionar detalhes baseados no nível
if (detailLevel >= 9) {
prompt += ", ultra detailed, intricate, 8K resolution, masterpiece quality";
} else if (detailLevel >= 7) {
prompt += ", highly detailed, sharp focus, high resolution";
} else if (detailLevel >= 5) {
prompt += ", detailed, clear, good quality";
} else {
prompt += ", good quality";
}
// Adicionar extras
if (extraParams.trim() !== '') {
prompt += ` ${extraParams.trim()}`;
} else {
// Adicionar parâmetros padrão
prompt += ", trending on ArtStation";
// Adicionar aspect ratio baseado na composição
if (currentAnalysis.composition === 'paisagem') {
prompt += " --ar 16:9";
} else if (currentAnalysis.composition === 'retrato') {
prompt += " --ar 2:3";
} else if (currentAnalysis.composition === 'quadrada') {
prompt += " --ar 1:1";
}
// Adicionar qualidade baseada no nível de detalhe
if (detailLevel >= 8) {
prompt += " --quality 2";
}
}
setPromptOutput(prompt);
showNotification('Prompt gerado com base na análise visual!', 'success');
};
// Copiar prompt para área de transferência const copyPromptToClipboard = async () => { if (!promptOutput || promptOutput.includes('O prompt gerado aparecerá aqui')) { showNotification('Nenhum prompt para copiar. Gere um prompt primeiro.', 'error'); return; }
try {
await navigator.clipboard.writeText(promptOutput);
setCopySuccess(true);
setTimeout(() => setCopySuccess(false), 2000);
showNotification('Prompt copiado para a área de transferência!', 'success');
} catch (err) {
console.error('Erro ao copiar: ', err);
// Fallback para navegadores mais antigos
const textArea = document.createElement('textarea');
textArea.value = promptOutput;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
setCopySuccess(true);
setTimeout(() => setCopySuccess(false), 2000);
showNotification('Prompt copiado para a área de transferência!', 'success');
} else {
throw new Error('Falha no comando de cópia');
}
} catch (fallbackErr) {
console.error('Erro no fallback: ', fallbackErr);
showNotification('Erro ao copiar o prompt. Tente selecionar e copiar manualmente (Ctrl+C).', 'error');
}
}
};
// Editar prompt const [isEditing, setIsEditing] = useState(false); const [editedPrompt, setEditedPrompt] = useState('');
const startEditing = () => { if (!promptOutput || promptOutput.includes('O prompt gerado aparecerá aqui')) { showNotification('Nenhum prompt para editar. Gere um prompt primeiro.', 'error'); return; }
setIsEditing(true);
setEditedPrompt(promptOutput);
};
const saveEditedPrompt = () => { setPromptOutput(editedPrompt); setIsEditing(false); showNotification('Prompt editado com sucesso!', 'success'); };
// Carregar exemplo const loadExample = () => { // Limpar tudo primeiro clearAllImages();
// Criar uma imagem de exemplo com canvas
const canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 400;
const ctx = canvas.getContext('2d');
if (ctx) {
// Criar um gradiente colorido
const gradient = ctx.createLinearGradient(0, 0, 600, 400);
gradient.addColorStop(0, '#FF6B6B');
gradient.addColorStop(0.3, '#4ECDC4');
gradient.addColorStop(0.6, '#FFE66D');
gradient.addColorStop(1, '#6B48FF');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 600, 400);
// Adicionar formas
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.arc(300, 200, 80, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(100, 150, 100, 200);
ctx.fillRect(400, 150, 100, 200);
// Converter para blob e criar File
canvas.toBlob((blob) => {
if (blob) {
const file = new File([blob], 'example-colorful-scene.jpg', { type: 'image/jpeg' });
processImageFile(file);
showNotification('Imagem de exemplo carregada!', 'info');
}
});
}
};
// Efeito para mostrar notificação useEffect(() => { if (notification && notificationRef.current) { const timer = setTimeout(() => { setNotification(null); }, 3000);
return () => clearTimeout(timer);
}
}, [notification]);
// Estilos CSS como objeto const styles: { [key: string]: React.CSSProperties } = { container: { background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)', color: '#f1f5f9', minHeight: '100vh', padding: '20px' }, mainContainer: { maxWidth: '1400px', margin: '0 auto' }, header: { textAlign: 'center' as const, marginBottom: '30px', padding: '20px' }, title: { fontSize: '2.8rem', marginBottom: '10px', background: 'linear-gradient(to right, #60a5fa, #a78bfa, #f472b6)', WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent' }, subtitle: { fontSize: '1.2rem', color: '#94a3b8', maxWidth: '800px', margin: '0 auto 20px' }, platformTags: { display: 'flex', justifyContent: 'center', gap: '15px', flexWrap: 'wrap' as const, marginTop: '20px' }, platformTag: { background: 'rgba(96, 165, 250, 0.1)', border: '1px solid #60a5fa', borderRadius: '20px', padding: '8px 16px', fontSize: '0.9rem', display: 'flex', alignItems: 'center', gap: '8px' }, visionBadge: { background: 'linear-gradient(135deg, #8b5cf6, #3b82f6)', border: 'none', color: 'white', fontWeight: 'bold' }, mainContent: { display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: '30px' }, '@media (max-width: 1100px)': { mainContent: { gridTemplateColumns: '1fr' } }, uploadSection: { background: 'rgba(30, 41, 59, 0.9)', borderRadius: '15px', padding: '25px', boxShadow: '0 10px 30px rgba(0, 0, 0, 0.3)', border: '1px solid #334155' }, sectionTitle: { fontSize: '1.5rem', marginBottom: '20px', color: '#60a5fa', display: 'flex', alignItems: 'center', gap: '10px' }, dropZone: { border: '3px dashed #475569', borderRadius: '10px', padding: '40px 20px', textAlign: 'center' as const, cursor: 'pointer', transition: 'all 0.3s', marginBottom: '25px' }, dropZoneHover: { borderColor: '#60a5fa', background: 'rgba(96, 165, 250, 0.05)' }, fileInputLabel: { display: 'inline-block', background: '#3b82f6', color: 'white', padding: '12px 24px', borderRadius: '8px', cursor: 'pointer', fontWeight: '600', transition: 'background 0.3s' }, imagePreviews: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: '15px', marginTop: '20px' }, imagePreview: { position: 'relative' as const, borderRadius: '8px', overflow: 'hidden' as const, height: '120px', boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)' }, removeImage: { position: 'absolute' as const, top: '5px', right: '5px', background: 'rgba(239, 68, 68, 0.9)', color: 'white', border: 'none', width: '25px', height: '25px', borderRadius: '50%', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '0.8rem' }, analysisBadge: { position: 'absolute' as const, bottom: '5px', left: '5px', background: 'rgba(16, 185, 129, 0.9)', color: 'white', border: 'none', padding: '3px 8px', borderRadius: '4px', fontSize: '0.7rem', display: 'flex', alignItems: 'center', gap: '4px' }, promptControls: { background: 'rgba(15, 23, 42, 0.5)', borderRadius: '10px', padding: '20px', marginBottom: '25px' }, controlGroup: { marginBottom: '20px' }, controlLabel: { display: 'block', marginBottom: '8px', color: '#cbd5e1', fontWeight: '600' }, select: { width: '100%', padding: '12px 15px', borderRadius: '8px', background: '#1e293b', border: '1px solid #475569', color: '#f1f5f9', fontSize: '1rem' }, sliderContainer: { display: 'flex', alignItems: 'center', gap: '15px' }, sliderValue: { minWidth: '40px', textAlign: 'center' as const, fontWeight: '600', color: '#60a5fa' }, slider: { flex: '1', height: '8px', borderRadius: '4px', background: '#475569', outline: 'none', WebkitAppearance: 'none' as const }, controls: { display: 'flex', gap: '15px', marginTop: '25px', flexWrap: 'wrap' as const }, btn: { padding: '12px 24px', border: 'none', borderRadius: '8px', fontWeight: '600', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', transition: 'all 0.3s' }, btnPrimary: { background: '#3b82f6', color: 'white' }, btnSecondary: { background: '#475569', color: 'white' }, btnVision: { background: 'linear-gradient(135deg, #8b5cf6, #3b82f6)', color: 'white' }, promptOutput: { background: '#1e293b', borderRadius: '8px', padding: '20px', minHeight: '350px', border: '1px solid #334155', marginBottom: '25px', fontFamily: "'Courier New', monospace", whiteSpace: 'pre-wrap' as const, overflowWrap: 'break-word' as const, overflowY: 'auto' as const, maxHeight: '450px' }, promptPlaceholder: { color: '#64748b', fontStyle: 'italic', textAlign: 'center' as const, padding: '40px 20px' }, visionAnalysis: { background: 'rgba(139, 92, 246, 0.1)', borderRadius: '8px', padding: '15px', marginTop: '15px', borderLeft: '4px solid #8b5cf6' }, analysisTitle: { color: '#8b5cf6', fontWeight: '600', marginBottom: '10px', display: 'flex', alignItems: 'center', gap: '8px' }, analysisContent: { color: '#cbd5e1', fontSize: '0.9rem' }, analysisGrid: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px', marginTop: '10px' }, analysisItem: { background: 'rgba(15, 23, 42, 0.5)', padding: '8px', borderRadius: '6px' }, analysisLabel: { fontSize: '0.8rem', color: '#94a3b8' }, analysisValue: { fontSize: '0.9rem', color: '#e2e8f0', fontWeight: '500' }, colorPalette: { display: 'flex', gap: '5px', marginTop: '5px' }, colorBox: { width: '20px', height: '20px', borderRadius: '4px', border: '1px solid #475569' }, actions: { display: 'flex', justifyContent: 'space-between', gap: '15px' }, copySuccess: { color: '#10b981', fontSize: '0.8rem', marginLeft: '10px', opacity: copySuccess ? 1 : 0, transition: 'opacity 0.3s' }, notification: { position: 'fixed' as const, top: '20px', right: '20px', background: '#10b981', color: 'white', padding: '15px 25px', borderRadius: '8px', boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)', transform: 'translateX(150%)', transition: 'transform 0.5s', zIndex: 1000, display: 'flex', alignItems: 'center', gap: '10px' }, notificationShow: { transform: 'translateX(0)' }, notificationError: { background: '#ef4444' }, notificationWarning: { background: '#f59e0b' }, notificationInfo: { background: '#3b82f6' } };
return (
Gerador de Prompt com Análise Visual
Análise visual real das imagens usando Canvas API para extrair cores, composição e características visuais para gerar prompts precisos.
<div style={styles.platformTags}>
<span style={{...styles.platformTag, ...styles.visionBadge}}>
<FaEye /> Análise Visual Real
</span>
<span style={styles.platformTag}>
<FaPalette /> Midjourney
</span>
<span style={styles.platformTag}>
<FaRobot /> DALL-E
</span>
<span style={styles.platformTag}>
<FaCode /> Stable Diffusion
</span>
<span style={styles.platformTag}>
<FaSun /> Detecção de Cores
</span>
</div>
</header>
{/* Conteúdo Principal */}
<div style={styles.mainContent}>
{/* Seção de Upload */}
<section style={styles.uploadSection}>
<h2 style={styles.sectionTitle}>
<FaCloudUploadAlt /> Upload de Imagens
</h2>
<div
style={styles.dropZone}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
<FaImages style={{ fontSize: '3rem', color: '#60a5fa', marginBottom: '15px' }} />
<h3>Arraste e solte suas imagens aqui</h3>
<p>Suporta JPEG, PNG, WebP | Máx. 5MB cada | Análise visual automática</p>
<div style={styles.fileInputLabel}>
<FaFolderOpen /> Selecionar Imagens
</div>
<input
type="file"
id="file-input"
ref={fileInputRef}
accept="image/*"
multiple
onChange={handleFileSelect}
style={{ display: 'none' }}
/>
</div>
{/* Preview das Imagens */}
<div style={styles.imagePreviews}>
{loadedImages.map(img => (
<div key={img.id} style={styles.imagePreview}>
<img
src={img.dataUrl}
alt={img.name}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
<button
style={styles.removeImage}
onClick={() => removeImage(img.id)}
>
<FaTimes />
</button>
{img.analyzed && (
<div style={styles.analysisBadge}>
<FaCheck /> Analisada
</div>
)}
</div>
))}
</div>
{/* Controles de Prompt */}
<div style={styles.promptControls}>
<div style={styles.controlGroup}>
<label style={styles.controlLabel}>Estilo Artístico:</label>
<select
style={styles.select}
value={style}
onChange={(e) => setStyle(e.target.value)}
>
<option value="photorealistic">Fotorrealista</option>
<option value="digital_art">Arte Digital</option>
<option value="oil_painting">Pintura a Óleo</option>
<option value="watercolor">Aquarela</option>
<option value="anime">Anime</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="fantasy">Fantasia</option>
<option value="minimalist">Minimalista</option>
<option value="impressionism">Impressionsimo</option>
<option value="surrealism">Surrealismo</option>
</select>
</div>
<div style={styles.controlGroup}>
<label style={styles.controlLabel}>
Nível de Detalhe: <span style={styles.sliderValue}>{detailLevel}</span>
</label>
<div style={styles.sliderContainer}>
<span>Básico</span>
<input
type="range"
min="1"
max="10"
value={detailLevel}
onChange={(e) => setDetailLevel(parseInt(e.target.value))}
style={styles.slider}
/>
<span>Detalhado</span>
</div>
</div>
{/* Controles Avançados */}
<button
style={{
background: 'none',
border: 'none',
color: '#60a5fa',
cursor: 'pointer',
fontSize: '0.9rem',
display: 'flex',
alignItems: 'center',
gap: '5px',
marginTop: '10px'
}}
onClick={() => setShowAdvanced(!showAdvanced)}
>
<FaCog /> {showAdvanced ? 'Ocultar Controles Avançados' : 'Controles Avançados'}
</button>
{showAdvanced && (
<div style={{ marginTop: '20px', paddingTop: '20px', borderTop: '1px solid #334155' }}>
<div style={styles.controlGroup}>
<label style={styles.controlLabel}>Iluminação:</label>
<select
style={styles.select}
value={lighting}
onChange={(e) => setLighting(e.target.value)}
>
<option value="auto">Automático (baseado na imagem)</option>
<option value="studio_lighting">Iluminação de Estúdio</option>
<option value="natural_light">Luz Natural</option>
<option value="golden_hour">Hora Dourada</option>
<option value="dramatic">Dramática</option>
<option value="soft">Suave</option>
</select>
</div>
<div style={styles.controlGroup}>
<label style={styles.controlLabel}>Composição:</label>
<select
style={styles.select}
value={composition}
onChange={(e) => setComposition(e.target.value)}
>
<option value="auto">Automático (baseado na imagem)</option>
<option value="portrait">Retrato</option>
<option value="landscape">Paisagem</option>
<option value="close_up">Close-up</option>
<option value="wide_angle">Grande Angular</option>
<option value="macro">Macro</option>
</select>
</div>
<div style={styles.controlGroup}>
<label style={styles.controlLabel}>Parâmetros Extras:</label>
<input
type="text"
style={styles.select}
value={extraParams}
onChange={(e) => setExtraParams(e.target.value)}
placeholder="--ar 16:9 --chaos 50 --stylize 750"
/>
</div>
</div>
)}
</div>
{/* Botões de Controle */}
<div style={styles.controls}>
<button
style={{...styles.btn, ...styles.btnVision}}
onClick={() => loadedImages.length > 0 && analyzeImage(loadedImages[0])}
disabled={isAnalyzing || loadedImages.length === 0}
>
{isAnalyzing ? (
<>
<div style={{
width: '16px',
height: '16px',
border: '2px solid rgba(255, 255, 255, 0.3)',
borderRadius: '50%',
borderTopColor: 'white',
animation: 'spin 1s linear infinite'
}} />
Analisando...
</>
) : (
<>
<FaEye /> Analisar Imagem Detalhadamente
</>
)}
</button>
<button
style={{...styles.btn, ...styles.btnPrimary}}
onClick={generatePrompt}
disabled={!currentAnalysis}
>
<FaBolt /> Gerar Prompt
</button>
<button
style={{...styles.btn, ...styles.btnSecondary}}
onClick={clearAllImages}
>
<FaTrash /> Limpar Tudo
</button>
<button
style={{...styles.btn, ...styles.btnSecondary}}
onClick={loadExample}
>
<FaStar /> Carregar Exemplo
</button>
</div>
</section>
{/* Seção de Prompt */}
<section style={styles.uploadSection}>
<h2 style={styles.sectionTitle}>
<FaTerminal /> Prompt Gerado (Inglês)
</h2>
{/* Output do Prompt */}
<div style={styles.promptOutput}>
{isEditing ? (
<>
<textarea
value={editedPrompt}
onChange={(e) => setEditedPrompt(e.target.value)}
style={{
width: '100%',
height: '300px',
padding: '15px',
borderRadius: '8px',
background: '#0f172a',
color: '#f1f5f9',
border: '1px solid #334155',
fontFamily: "'Courier New', monospace",
fontSize: '0.95rem',
resize: 'vertical'
}}
/>
<button
style={{...styles.btn, ...styles.btnPrimary, marginTop: '15px'}}
onClick={saveEditedPrompt}
>
<FaCheck /> Salvar Edição
</button>
</>
) : promptOutput ? (
promptOutput
) : (
<div style={styles.promptPlaceholder}>
<FaTerminal style={{ fontSize: '2rem', marginBottom: '15px' }} />
<p>O prompt gerado aparecerá aqui com base na análise visual da imagem.</p>
<p>Use o botão "Analisar Imagem Detalhadamente" para começar.</p>
</div>
)}
</div>
{/* Análise Visual */}
{currentAnalysis && (
<div style={styles.visionAnalysis}>
<div style={styles.analysisTitle}>
<FaEye /> Análise Visual da Imagem:
</div>
<div style={styles.analysisContent}>
<div><strong>Tipo de Composição:</strong> {
currentAnalysis.composition === 'paisagem' ? 'Paisagem (horizontal)' :
currentAnalysis.composition === 'retrato' ? 'Retrato (vertical)' :
currentAnalysis.composition === 'quadrada' ? 'Quadrada' : 'Padrão'
}</div>
<div style={styles.analysisGrid}>
<div style={styles.analysisItem}>
<div style={styles.analysisLabel}>Paleta de Cores:</div>
<div style={styles.analysisValue}>{currentAnalysis.colors.paletteType}</div>
<div style={styles.colorPalette}>
{currentAnalysis.colors.dominantColors.slice(0, 5).map((color, index) => (
<div
key={index}
style={{...styles.colorBox, background: color}}
/>
))}
</div>
</div>
<div style={styles.analysisItem}>
<div style={styles.analysisLabel}>Características:</div>
<div style={styles.analysisValue}>{currentAnalysis.brightness} luminosidade</div>
<div style={styles.analysisValue}>{currentAnalysis.saturation} saturação</div>
<div style={styles.analysisValue}>{currentAnalysis.contrast} contraste</div>
</div>
</div>
<div style={{ marginTop: '10px', fontSize: '0.85rem', color: '#94a3b8' }}>
<FaLightbulb /> Estas características serão usadas para gerar um prompt preciso.
</div>
</div>
</div>
)}
{/* Botões de Ação */}
<div style={styles.actions}>
<button
style={{...styles.btn, ...styles.btnPrimary, background: copySuccess ? '#10b981' : '#3b82f6'}}
onClick={copyPromptToClipboard}
>
<FaCopy /> Copiar Prompt
<span style={styles.copySuccess}>✓ Copiado!</span>
</button>
<button
style={{...styles.btn, ...styles.btnSecondary}}
onClick={startEditing}
disabled={!promptOutput}
>
<FaEdit /> Editar Prompt
</button>
<button
style={{...styles.btn, ...styles.btnSecondary}}
onClick={generatePrompt}
disabled={!currentAnalysis}
>
<FaRedo /> Regenerar
</button>
</div>
</section>
</div>
{/* Notificação */}
{notification && (
<div
ref={notificationRef}
style={{
...styles.notification,
...styles.notificationShow,
...(notification.type === 'error' ? styles.notificationError :
notification.type === 'warning' ? styles.notificationWarning :
notification.type === 'info' ? styles.notificationInfo : {})
}}
>
{notification.type === 'error' ? <FaExclamationCircle /> :
notification.type === 'warning' ? <FaExclamationTriangle /> :
notification.type === 'info' ? <FaInfoCircle /> : <FaCheckCircle />}
<span>{notification.message}</span>
</div>
)}
{/* Estilos CSS dinâmicos */}
<style>
{`
@keyframes spin {
to { transform: rotate(360deg); }
}
.dragover {
border-color: #60a5fa !important;
background: rgba(96, 165, 250, 0.05) !important;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
@media (max-width: 1100px) {
.main-content {
grid-template-columns: 1fr !important;
}
}
`}
</style>
</div>
</div>
); };
export default PromptGenerator;