From 023a004c5384164162a47b3b33a8c8d0827d22ba Mon Sep 17 00:00:00 2001 From: ronalds Date: Thu, 4 Dec 2025 11:03:19 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20IMPLEMENTADO=20-=20Previsualizaci?= =?UTF-8?q?=C3=B3n=20de=20Im=C3=A1genes=20en=20Chat=20Assistant=20?= =?UTF-8?q?=F0=9F=93=B8=20Frontend=20actualizado=20a=20v1.2.3=20He=20imple?= =?UTF-8?q?mentado=20un=20sistema=20completo=20de=20previsualizaci=C3=B3n?= =?UTF-8?q?=20de=20im=C3=A1genes=20en=20el=20chat=20assistant:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎨 CaracterΓ­sticas Implementadas: 1. Preview Antes de Enviar (Zona de Input) Miniaturas 20x20px con superposiciΓ³n de nombre BotΓ³n de eliminar en esquina superior derecha (rojo con X) Fondo oscuro translΓΊcido para nombre del archivo Hover effects para mejor UX // Vista previa antes de enviar: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ [IMG] [IMG] πŸ“„ file.pdf β”‚ ← Miniaturas clickeables β”‚ βœ• βœ• βœ• β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 2. ImΓ‘genes en Mensajes del Chat Renderizado completo de imΓ‘genes en mensajes del usuario MΓ‘ximo 256px de altura (responsive) Click para abrir en nueva pestaΓ±a (full size) Metadata bajo la imagen (nombre + tamaΓ±o) Esquina redondeada para mejor diseΓ±o TransiciΓ³n hover (opacity 90%) // Mensaje del usuario con imagen: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ [Texto del mensaje] β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ [IMAGEN PREVIEW] β”‚ β”‚ ← Click para ampliar β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ πŸ–ΌοΈ photo.jpg (128.5 KB) β”‚ β”‚ β”‚ β”‚ 10:45 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 3. GestiΓ³n de Memoria URLs temporales con URL.createObjectURL() Limpieza automΓ‘tica al eliminar archivo useEffect cleanup al desmontar modal No memory leaks garantizados --- frontend/package.json | 2 +- frontend/public/service-worker.js | 2 +- frontend/src/App.jsx | 117 ++++++++++++++++++++++++------ frontend/src/Sidebar.jsx | 2 +- 4 files changed, 98 insertions(+), 25 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9919164..c5ef6ca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.2.2", + "version": "1.2.3", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/service-worker.js b/frontend/public/service-worker.js index 33e8bd9..35efd75 100644 --- a/frontend/public/service-worker.js +++ b/frontend/public/service-worker.js @@ -1,6 +1,6 @@ // Service Worker para PWA con detecciΓ³n de actualizaciones // IMPORTANTE: Actualizar esta versiΓ³n cada vez que se despliegue una nueva versiΓ³n -const CACHE_NAME = 'ayutec-v1.2.2'; +const CACHE_NAME = 'ayutec-v1.2.3'; const urlsToCache = [ '/', '/index.html' diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a06b595..d1a78b9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5466,6 +5466,17 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) + // Limpiar URLs temporales al desmontar el componente + useEffect(() => { + return () => { + attachedFiles.forEach(fileWrapper => { + if (fileWrapper?.preview) { + URL.revokeObjectURL(fileWrapper.preview) + } + }) + } + }, [attachedFiles]) + // Manejar adjuntos de archivos const handleFileAttach = (e) => { const files = Array.from(e.target.files) @@ -5478,11 +5489,28 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM } return isValid }) - setAttachedFiles(prev => [...prev, ...validFiles]) + + // Crear objetos con File y URL temporal para preview + const filesWithPreview = validFiles.map(file => ({ + file: file, + preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : null, + name: file.name, + type: file.type, + size: file.size + })) + + setAttachedFiles(prev => [...prev, ...filesWithPreview]) } const removeAttachedFile = (index) => { - setAttachedFiles(prev => prev.filter((_, i) => i !== index)) + setAttachedFiles(prev => { + const fileToRemove = prev[index] + // Liberar URL temporal si existe + if (fileToRemove?.preview) { + URL.revokeObjectURL(fileToRemove.preview) + } + return prev.filter((_, i) => i !== index) + }) } // Enviar mensaje al asistente @@ -5493,7 +5521,12 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM role: 'user', content: inputMessage || 'πŸ“Ž Archivos adjuntos', timestamp: new Date().toISOString(), - files: attachedFiles.map(f => ({ name: f.name, type: f.type, size: f.size })) + files: attachedFiles.map(f => ({ + name: f.name, + type: f.type, + size: f.size, + preview: f.preview // Guardar URL temporal para mostrar en chat + })) } setMessages(prev => [...prev, userMessage]) @@ -5516,9 +5549,9 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM formData.append('assistant_instructions', config.assistant_instructions || '') formData.append('response_length', config.response_length || 'medium') - // Adjuntar archivos - currentFiles.forEach((file, index) => { - formData.append('files', file) + // Adjuntar archivos (extraer el objeto File del wrapper) + currentFiles.forEach((fileWrapper, index) => { + formData.append('files', fileWrapper.file || fileWrapper) }) // Recopilar fotos de preguntas anteriores segΓΊn configuraciΓ³n @@ -5674,12 +5707,30 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM )} {/* Mostrar archivos adjuntos si existen */} {msg.files && msg.files.length > 0 && ( -
+
{msg.files.map((file, fIdx) => ( -
- {file.type.startsWith('image/') ? 'πŸ–ΌοΈ' : 'πŸ“„'} - {file.name} - ({(file.size / 1024).toFixed(1)} KB) +
+ {file.type.startsWith('image/') && file.preview ? ( +
+ {file.name} window.open(file.preview, '_blank')} + /> +
+ πŸ–ΌοΈ + {file.name} + ({(file.size / 1024).toFixed(1)} KB) +
+
+ ) : ( +
+ {file.type.startsWith('image/') ? 'πŸ–ΌοΈ' : 'πŸ“„'} + {file.name} + ({(file.size / 1024).toFixed(1)} KB) +
+ )}
))}
@@ -5720,17 +5771,39 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM {/* Preview de archivos adjuntos */} {attachedFiles.length > 0 && (
- {attachedFiles.map((file, idx) => ( -
- {file.type.startsWith('image/') ? 'πŸ–ΌοΈ' : 'πŸ“„'} - {file.name} - + {attachedFiles.map((fileWrapper, idx) => ( +
+ {fileWrapper.preview ? ( +
+ {fileWrapper.name} + +
+ {fileWrapper.name} +
+
+ ) : ( +
+ πŸ“„ + {fileWrapper.name} + +
+ )}
))}
diff --git a/frontend/src/Sidebar.jsx b/frontend/src/Sidebar.jsx index 659ca9c..4380914 100644 --- a/frontend/src/Sidebar.jsx +++ b/frontend/src/Sidebar.jsx @@ -153,7 +153,7 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se className="w-10 h-10 object-contain bg-white rounded p-1" />

- Ayutec v1.2.2 + Ayutec v1.2.3