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 (

{/* Cabeçalho */}

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;

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support