|
import React, { useState, useRef, useEffect } from 'react'; |
|
import { |
|
Code, Terminal, FileCode, Play, CheckCircle, XCircle, AlertTriangle, |
|
Lightbulb, Settings, Copy, Download, Upload, Sparkles, Sun, Moon, |
|
FileText, Zap, GitBranch, Bug, Wrench, ArrowRight, Eye, EyeOff, |
|
Share2, RefreshCw, Wand2, Languages, Send, ClipboardCheck, Loader2 |
|
} from 'lucide-react'; |
|
|
|
const languages = { |
|
python: { name: 'Python', ext: '.py', icon: '🐍', example: 'def hello_world():\n print("¡Hola mundo!")\n return True' }, |
|
javascript: { name: 'JavaScript', ext: '.js', icon: '📜', example: 'function helloWorld() {\n console.log("¡Hola mundo!");\n return true;\n}' }, |
|
java: { name: 'Java', ext: '.java', icon: '☕', example: 'public class HelloWorld {\n public static void main(String[] args) {\n System.out.println("¡Hola mundo!");\n }\n}' }, |
|
cpp: { name: 'C++', ext: '.cpp', icon: '⚡', example: '#include <iostream>\nint main() {\n std::cout << "¡Hola mundo!" << std::endl;\n return 0;\n}' }, |
|
html: { name: 'HTML', ext: '.html', icon: '🌐', example: '<!DOCTYPE html>\n<html>\n<head>\n <title>Mi página</title>\n</head>\n<body>\n <h1>¡Hola mundo!</h1>\n</body>\n</html>' }, |
|
css: { name: 'CSS', ext: '.css', icon: '🎨', example: 'body {\n font-family: Arial, sans-serif;\n background-color: #f0f0f0;\n margin: 0;\n padding: 20px;\n}' }, |
|
react: { name: 'React JSX', ext: '.jsx', icon: '⚛️', example: 'import React from "react";\n\nfunction HelloWorld() {\n return <h1>¡Hola mundo!</h1>;\n}\n\nexport default HelloWorld;' } |
|
}; |
|
|
|
const CodeAssistant = () => { |
|
const [input, setInput] = useState(''); |
|
const [output, setOutput] = useState(''); |
|
const [analysis, setAnalysis] = useState(null); |
|
const [isProcessing, setIsProcessing] = useState(false); |
|
const [isDarkMode, setIsDarkMode] = useState(true); |
|
const [selectedLanguage, setSelectedLanguage] = useState('python'); |
|
const [activeTab, setActiveTab] = useState('input'); |
|
const [showPreview, setShowPreview] = useState(false); |
|
const [copySuccess, setCopySuccess] = useState(''); |
|
const [conversionMode, setConversionMode] = useState('improve'); // improve, convert, analyze |
|
|
|
const fileInputRef = useRef(null); |
|
|
|
// Detectar automáticamente el lenguaje del código |
|
const detectLanguage = (code) => { |
|
const patterns = { |
|
python: [/def\s+\w+/, /import\s+\w+/, /print\s*\(/, /if\s+__name__/, /:\s*$/m], |
|
javascript: [/function\s+\w+/, /console\.log/, /const\s+|let\s+|var\s+/, /=>\s*/, /document\./], |
|
java: [/public\s+class/, /System\.out/, /import\s+java/, /public\s+static\s+void\s+main/], |
|
cpp: [/#include/, /using\s+namespace/, /std::/, /cout\s*<</, /int\s+main/], |
|
html: [/<!DOCTYPE/, /<html/, /<head/, /<body/, /<div/, /<p>/], |
|
css: [/\{[^}]*\}/, /\.\w+\s*\{/, /#\w+\s*\{/, /@media/, /:\s*[^;]+;/], |
|
react: [/import\s+React/, /export\s+default/, /return\s*\(/, /className=/, /<\w+[^>]*>/] |
|
}; |
|
|
|
for (const [lang, regexes] of Object.entries(patterns)) { |
|
const matches = regexes.filter(regex => regex.test(code)).length; |
|
if (matches >= 2) return lang; |
|
} |
|
|
|
return 'python'; // default |
|
}; |
|
|
|
// Función principal para procesar código |
|
const processCode = async () => { |
|
if (!input.trim()) return; |
|
|
|
setIsProcessing(true); |
|
setAnalysis(null); |
|
|
|
try { |
|
const detectedLang = detectLanguage(input); |
|
|
|
let prompt = ''; |
|
|
|
switch (conversionMode) { |
|
case 'improve': |
|
prompt = `Analiza y mejora el siguiente código. Si es texto plano, conviértelo a ${languages[selectedLanguage].name}. Si ya es código, mejóralo corrigiendo errores, optimizando y aplicando mejores prácticas. |
|
|
|
Entrada: |
|
\`\`\` |
|
${input} |
|
\`\`\` |
|
|
|
Responde en formato JSON con esta estructura: |
|
{ |
|
"codigo_mejorado": "código corregido y mejorado", |
|
"lenguaje_detectado": "${detectedLang}", |
|
"errores_encontrados": ["lista de errores encontrados"], |
|
"mejoras_aplicadas": ["lista de mejoras aplicadas"], |
|
"explicacion": "breve explicación de los cambios", |
|
"es_funcional": true/false, |
|
"puntuacion_calidad": 0-100 |
|
} |
|
|
|
IMPORTANTE: Responde SOLO con JSON válido, sin markdown ni explicaciones adicionales.`; |
|
break; |
|
|
|
case 'convert': |
|
prompt = `Convierte el siguiente código/texto a ${languages[selectedLanguage].name}, manteniendo la funcionalidad y aplicando las mejores prácticas del lenguaje destino. |
|
|
|
Entrada: |
|
\`\`\` |
|
${input} |
|
\`\`\` |
|
|
|
Responde en formato JSON: |
|
{ |
|
"codigo_convertido": "código convertido a ${languages[selectedLanguage].name}", |
|
"lenguaje_origen": "${detectedLang}", |
|
"lenguaje_destino": "${selectedLanguage}", |
|
"cambios_realizados": ["lista de adaptaciones realizadas"], |
|
"notas_conversion": "explicación de la conversión" |
|
} |
|
|
|
IMPORTANTE: Responde SOLO con JSON válido.`; |
|
break; |
|
|
|
case 'analyze': |
|
prompt = `Analiza el siguiente código en detalle, identificando errores, problemas de rendimiento, seguridad y mejores prácticas. |
|
|
|
Código: |
|
\`\`\` |
|
${input} |
|
\`\`\` |
|
|
|
Responde en formato JSON: |
|
{ |
|
"analisis_detallado": "análisis completo del código", |
|
"errores_sintaxis": ["errores de sintaxis encontrados"], |
|
"errores_logicos": ["posibles errores lógicos"], |
|
"mejoras_rendimiento": ["sugerencias de rendimiento"], |
|
"mejoras_seguridad": ["mejoras de seguridad"], |
|
"puntuacion": 0-100, |
|
"recomendaciones": ["recomendaciones generales"] |
|
} |
|
|
|
IMPORTANTE: Responde SOLO con JSON válido.`; |
|
break; |
|
} |
|
|
|
const response = await fetch("https://api.anthropic.com/v1/messages", { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json", |
|
}, |
|
body: JSON.stringify({ |
|
model: "claude-sonnet-4-20250514", |
|
max_tokens: 4000, |
|
messages: [{ role: "user", content: prompt }] |
|
}) |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error(`Error ${response.status}: ${response.statusText}`); |
|
} |
|
|
|
const data = await response.json(); |
|
let responseText = data.content[0].text; |
|
|
|
// Limpiar respuesta para obtener JSON válido |
|
responseText = responseText.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim(); |
|
|
|
// Buscar el JSON en la respuesta |
|
const jsonStart = responseText.indexOf('{'); |
|
const jsonEnd = responseText.lastIndexOf('}'); |
|
if (jsonStart !== -1 && jsonEnd !== -1) { |
|
responseText = responseText.substring(jsonStart, jsonEnd + 1); |
|
} |
|
|
|
const result = JSON.parse(responseText); |
|
|
|
if (conversionMode === 'improve') { |
|
setOutput(result.codigo_mejorado || result.codigo_convertido || ''); |
|
setAnalysis({ |
|
type: 'improvement', |
|
language: result.lenguaje_detectado, |
|
errors: result.errores_encontrados || [], |
|
improvements: result.mejoras_aplicadas || [], |
|
explanation: result.explicacion || '', |
|
functional: result.es_funcional, |
|
score: result.puntuacion_calidad || 0 |
|
}); |
|
} else if (conversionMode === 'convert') { |
|
setOutput(result.codigo_convertido || ''); |
|
setAnalysis({ |
|
type: 'conversion', |
|
sourceLanguage: result.lenguaje_origen, |
|
targetLanguage: result.lenguaje_destino, |
|
changes: result.cambios_realizados || [], |
|
notes: result.notas_conversion || '' |
|
}); |
|
} else if (conversionMode === 'analyze') { |
|
setAnalysis({ |
|
type: 'analysis', |
|
detailed: result.analisis_detallado, |
|
syntaxErrors: result.errores_sintaxis || [], |
|
logicErrors: result.errores_logicos || [], |
|
performance: result.mejoras_rendimiento || [], |
|
security: result.mejoras_seguridad || [], |
|
score: result.puntuacion || 0, |
|
recommendations: result.recomendaciones || [] |
|
}); |
|
} |
|
|
|
setActiveTab('output'); |
|
|
|
} catch (error) { |
|
console.error('Error processing code:', error); |
|
setAnalysis({ |
|
type: 'error', |
|
message: `Error al procesar: ${error.message}` |
|
}); |
|
} finally { |
|
setIsProcessing(false); |
|
} |
|
}; |
|
|
|
// Copiar al portapapeles |
|
const copyToClipboard = async (text) => { |
|
try { |
|
await navigator.clipboard.writeText(text); |
|
setCopySuccess('¡Copiado!'); |
|
setTimeout(() => setCopySuccess(''), 2000); |
|
} catch (err) { |
|
console.error('Error al copiar:', err); |
|
setCopySuccess('Error al copiar'); |
|
} |
|
}; |
|
|
|
// Cargar archivo |
|
const handleFileUpload = (event) => { |
|
const file = event.target.files[0]; |
|
if (file) { |
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
setInput(e.target.result); |
|
// Auto-detectar lenguaje por extensión |
|
const ext = '.' + file.name.split('.').pop().toLowerCase(); |
|
const detectedLang = Object.entries(languages).find(([key, lang]) => lang.ext === ext); |
|
if (detectedLang) { |
|
setSelectedLanguage(detectedLang[0]); |
|
} |
|
}; |
|
reader.readAsText(file); |
|
} |
|
}; |
|
|
|
// Descargar código |
|
const downloadCode = () => { |
|
const element = document.createElement('a'); |
|
const file = new Blob([output || input], { type: 'text/plain' }); |
|
element.href = URL.createObjectURL(file); |
|
element.download = `codigo${languages[selectedLanguage]?.ext || '.txt'}`; |
|
document.body.appendChild(element); |
|
element.click(); |
|
document.body.removeChild(element); |
|
}; |
|
|
|
// Cargar ejemplo |
|
const loadExample = () => { |
|
const example = languages[selectedLanguage]?.example || languages.python.example; |
|
setInput(example); |
|
}; |
|
|
|
// Limpiar todo |
|
const clearAll = () => { |
|
setInput(''); |
|
setOutput(''); |
|
setAnalysis(null); |
|
setActiveTab('input'); |
|
}; |
|
|
|
// Renderizar preview del código (básico) |
|
const renderPreview = () => { |
|
const code = output || input; |
|
|
|
if (selectedLanguage === 'html' && code.includes('<html')) { |
|
return ( |
|
<div className="w-full h-96 border rounded-lg overflow-hidden"> |
|
<iframe |
|
srcDoc={code} |
|
className="w-full h-full" |
|
title="HTML Preview" |
|
sandbox="allow-scripts allow-same-origin" |
|
/> |
|
</div> |
|
); |
|
} |
|
|
|
return ( |
|
<div className={`p-4 rounded-lg ${isDarkMode ? 'bg-gray-900' : 'bg-gray-100'}`}> |
|
<p className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-2`}> |
|
Preview no disponible para {languages[selectedLanguage]?.name} |
|
</p> |
|
<pre className={`text-xs overflow-auto ${isDarkMode ? 'text-green-400' : 'text-gray-800'}`}> |
|
{code} |
|
</pre> |
|
</div> |
|
); |
|
}; |
|
|
|
return ( |
|
<div className={`min-h-screen transition-all duration-300 ${isDarkMode ? 'bg-gray-900' : 'bg-gray-50'}`}> |
|
{/* Header */} |
|
<div className={`border-b ${isDarkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}> |
|
<div className="max-w-7xl mx-auto px-4 py-4"> |
|
<div className="flex justify-between items-center"> |
|
<div className="flex items-center gap-3"> |
|
<div className="flex items-center justify-center w-12 h-12 rounded-xl bg-gradient-to-r from-purple-500 via-blue-500 to-teal-500"> |
|
<Wand2 className="w-7 h-7 text-white" /> |
|
</div> |
|
<div> |
|
<h1 className={`text-3xl font-bold bg-gradient-to-r from-purple-400 via-blue-500 to-teal-400 bg-clip-text text-transparent`}> |
|
Asistente Universal de Código IA |
|
</h1> |
|
<p className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}> |
|
Mejora, convierte y analiza cualquier código o texto automáticamente |
|
</p> |
|
</div> |
|
</div> |
|
|
|
<div className="flex items-center gap-2"> |
|
<button |
|
onClick={() => setIsDarkMode(!isDarkMode)} |
|
className={`p-2 rounded-lg transition-colors ${isDarkMode ? 'hover:bg-gray-700 text-yellow-400' : 'hover:bg-gray-100 text-gray-700'}`} |
|
> |
|
{isDarkMode ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />} |
|
</button> |
|
</div> |
|
</div> |
|
|
|
{/* Controls */} |
|
<div className="mt-4 flex flex-wrap gap-4 items-center"> |
|
<div className="flex items-center gap-2"> |
|
<label className={`text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}> |
|
Modo: |
|
</label> |
|
<select |
|
value={conversionMode} |
|
onChange={(e) => setConversionMode(e.target.value)} |
|
className={`px-3 py-1 rounded-md text-sm border ${ |
|
isDarkMode |
|
? 'bg-gray-700 text-white border-gray-600' |
|
: 'bg-white text-gray-900 border-gray-300' |
|
}`} |
|
> |
|
<option value="improve">🔧 Mejorar código</option> |
|
<option value="convert">🔄 Convertir lenguaje</option> |
|
<option value="analyze">🔍 Analizar código</option> |
|
</select> |
|
</div> |
|
|
|
<div className="flex items-center gap-2"> |
|
<label className={`text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}> |
|
Lenguaje destino: |
|
</label> |
|
<select |
|
value={selectedLanguage} |
|
onChange={(e) => setSelectedLanguage(e.target.value)} |
|
className={`px-3 py-1 rounded-md text-sm border ${ |
|
isDarkMode |
|
? 'bg-gray-700 text-white border-gray-600' |
|
: 'bg-white text-gray-900 border-gray-300' |
|
}`} |
|
> |
|
{Object.entries(languages).map(([key, lang]) => ( |
|
<option key={key} value={key}> |
|
{lang.icon} {lang.name} |
|
</option> |
|
))} |
|
</select> |
|
</div> |
|
|
|
<div className="flex gap-2"> |
|
<button |
|
onClick={loadExample} |
|
className={`flex items-center gap-2 px-3 py-1 text-sm rounded-md ${ |
|
isDarkMode ? 'bg-gray-700 hover:bg-gray-600 text-gray-300' : 'bg-gray-200 hover:bg-gray-300 text-gray-700' |
|
}`} |
|
> |
|
<FileText className="w-4 h-4" /> |
|
Ejemplo |
|
</button> |
|
|
|
<input |
|
ref={fileInputRef} |
|
type="file" |
|
onChange={handleFileUpload} |
|
className="hidden" |
|
accept=".py,.js,.java,.cpp,.c,.html,.css,.jsx,.tsx,.txt,.md" |
|
/> |
|
<button |
|
onClick={() => fileInputRef.current?.click()} |
|
className={`flex items-center gap-2 px-3 py-1 text-sm rounded-md ${ |
|
isDarkMode ? 'bg-gray-700 hover:bg-gray-600 text-gray-300' : 'bg-gray-200 hover:bg-gray-300 text-gray-700' |
|
}`} |
|
> |
|
<Upload className="w-4 h-4" /> |
|
Archivo |
|
</button> |
|
|
|
<button |
|
onClick={clearAll} |
|
className={`flex items-center gap-2 px-3 py-1 text-sm rounded-md ${ |
|
isDarkMode ? 'bg-red-600 hover:bg-red-700 text-white' : 'bg-red-500 hover:bg-red-600 text-white' |
|
}`} |
|
> |
|
<RefreshCw className="w-4 h-4" /> |
|
Limpiar |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div className="max-w-7xl mx-auto p-6"> |
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6"> |
|
{/* Panel de entrada */} |
|
<div className={`rounded-xl shadow-lg ${isDarkMode ? 'bg-gray-800' : 'bg-white'}`}> |
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700"> |
|
<div className="flex justify-between items-center"> |
|
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDarkMode ? 'text-white' : 'text-gray-900'}`}> |
|
<Terminal className="w-5 h-5" /> |
|
Entrada de código / texto |
|
</h2> |
|
<div className="flex gap-1"> |
|
<button |
|
onClick={() => copyToClipboard(input)} |
|
className={`p-2 rounded-md ${isDarkMode ? 'hover:bg-gray-700 text-gray-400' : 'hover:bg-gray-100 text-gray-600'}`} |
|
title="Copiar" |
|
> |
|
<Copy className="w-4 h-4" /> |
|
</button> |
|
<button |
|
onClick={() => setInput('')} |
|
className={`p-2 rounded-md ${isDarkMode ? 'hover:bg-gray-700 text-gray-400' : 'hover:bg-gray-100 text-gray-600'}`} |
|
title="Limpiar" |
|
> |
|
<RefreshCw className="w-4 h-4" /> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div className="p-4"> |
|
<textarea |
|
value={input} |
|
onChange={(e) => setInput(e.target.value)} |
|
placeholder="Pega tu código aquí, escribe texto para convertir a código, o ingresa código para mejorar..." |
|
className={`w-full h-96 p-4 rounded-lg border font-mono text-sm resize-none ${ |
|
isDarkMode |
|
? 'bg-gray-900 border-gray-700 text-green-400 placeholder-gray-500' |
|
: 'bg-gray-50 border-gray-300 text-gray-900 placeholder-gray-400' |
|
} focus:ring-2 focus:ring-purple-500 focus:border-transparent`} |
|
spellCheck={false} |
|
/> |
|
|
|
<div className="flex justify-between items-center mt-4"> |
|
<span className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}> |
|
{input.length} caracteres | {input.split('\n').length} líneas |
|
{input.trim() && ( |
|
<span className="ml-2 px-2 py-1 bg-purple-500 text-white text-xs rounded-full"> |
|
{detectLanguage(input)} |
|
</span> |
|
)} |
|
</span> |
|
|
|
<button |
|
onClick={processCode} |
|
disabled={isProcessing || !input.trim()} |
|
className={`flex items-center gap-2 px-6 py-3 rounded-lg font-medium transition-all ${ |
|
isProcessing || !input.trim() |
|
? 'bg-gray-500 text-gray-300 cursor-not-allowed' |
|
: 'bg-gradient-to-r from-purple-500 via-blue-500 to-teal-500 text-white hover:from-purple-600 hover:via-blue-600 hover:to-teal-600 shadow-lg transform hover:scale-105' |
|
}`} |
|
> |
|
{isProcessing ? ( |
|
<> |
|
<Loader2 className="w-5 h-5 animate-spin" /> |
|
Procesando... |
|
</> |
|
) : ( |
|
<> |
|
<Sparkles className="w-5 h-5" /> |
|
{conversionMode === 'improve' ? 'Mejorar código' : |
|
conversionMode === 'convert' ? 'Convertir código' : 'Analizar código'} |
|
</> |
|
)} |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
{/* Panel de salida */} |
|
<div className={`rounded-xl shadow-lg ${isDarkMode ? 'bg-gray-800' : 'bg-white'}`}> |
|
{/* Tabs */} |
|
<div className="flex border-b border-gray-200 dark:border-gray-700"> |
|
<button |
|
onClick={() => setActiveTab('output')} |
|
className={`flex-1 px-4 py-3 text-sm font-medium ${ |
|
activeTab === 'output' |
|
|