|
|
|
|
@@ -4270,7 +4270,8 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
|
|
|
|
status: status,
|
|
|
|
|
comment: answer.observations || null,
|
|
|
|
|
ai_analysis: answer.aiAnalysis || null,
|
|
|
|
|
is_flagged: status === 'critical'
|
|
|
|
|
is_flagged: status === 'critical',
|
|
|
|
|
chat_history: answer.chatHistory || null // Agregar historial de chat
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${API_URL}/api/answers`, {
|
|
|
|
|
@@ -5265,7 +5266,9 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
|
|
|
|
// Componente Modal de Chat IA Asistente
|
|
|
|
|
function AIAssistantChatModal({ question, inspection, allAnswers, messages, setMessages, loading, setLoading, onClose }) {
|
|
|
|
|
const [inputMessage, setInputMessage] = useState('')
|
|
|
|
|
const [attachedFiles, setAttachedFiles] = useState([])
|
|
|
|
|
const chatEndRef = useRef(null)
|
|
|
|
|
const fileInputRef = useRef(null)
|
|
|
|
|
const config = question.options || {}
|
|
|
|
|
|
|
|
|
|
// Auto-scroll al final
|
|
|
|
|
@@ -5273,19 +5276,66 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
|
|
|
|
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
|
|
|
}, [messages])
|
|
|
|
|
|
|
|
|
|
// Manejar adjuntos de archivos
|
|
|
|
|
const handleFileAttach = (e) => {
|
|
|
|
|
const files = Array.from(e.target.files)
|
|
|
|
|
const validFiles = files.filter(file => {
|
|
|
|
|
const isImage = file.type.startsWith('image/')
|
|
|
|
|
const isPDF = file.type === 'application/pdf'
|
|
|
|
|
const isValid = isImage || isPDF
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
alert(`⚠️ ${file.name}: Solo se permiten imágenes y PDFs`)
|
|
|
|
|
}
|
|
|
|
|
return isValid
|
|
|
|
|
})
|
|
|
|
|
setAttachedFiles(prev => [...prev, ...validFiles])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const removeAttachedFile = (index) => {
|
|
|
|
|
setAttachedFiles(prev => prev.filter((_, i) => i !== index))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enviar mensaje al asistente
|
|
|
|
|
const sendMessage = async () => {
|
|
|
|
|
if (!inputMessage.trim() || loading) return
|
|
|
|
|
if ((!inputMessage.trim() && attachedFiles.length === 0) || loading) return
|
|
|
|
|
|
|
|
|
|
const userMessage = { role: 'user', content: inputMessage, timestamp: new Date().toISOString() }
|
|
|
|
|
const userMessage = {
|
|
|
|
|
role: 'user',
|
|
|
|
|
content: inputMessage || '📎 Archivos adjuntos',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
files: attachedFiles.map(f => ({ name: f.name, type: f.type, size: f.size }))
|
|
|
|
|
}
|
|
|
|
|
setMessages(prev => [...prev, userMessage])
|
|
|
|
|
|
|
|
|
|
const currentFiles = attachedFiles
|
|
|
|
|
setInputMessage('')
|
|
|
|
|
setAttachedFiles([])
|
|
|
|
|
setLoading(true)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const token = localStorage.getItem('token')
|
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL || ''
|
|
|
|
|
|
|
|
|
|
// Preparar FormData para enviar archivos
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
formData.append('question_id', question.id)
|
|
|
|
|
formData.append('inspection_id', inspection.id)
|
|
|
|
|
formData.append('user_message', inputMessage)
|
|
|
|
|
formData.append('chat_history', JSON.stringify(messages))
|
|
|
|
|
formData.append('assistant_prompt', config.assistant_prompt || '')
|
|
|
|
|
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
|
|
|
|
|
currentFiles.forEach((file, index) => {
|
|
|
|
|
formData.append('files', file)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Recopilar fotos de preguntas anteriores según configuración
|
|
|
|
|
const contextPhotos = []
|
|
|
|
|
const contextQuestionIds = config.context_questions
|
|
|
|
|
@@ -5308,33 +5358,23 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Preparar el payload
|
|
|
|
|
const payload = {
|
|
|
|
|
question_id: question.id,
|
|
|
|
|
inspection_id: inspection.id,
|
|
|
|
|
user_message: inputMessage,
|
|
|
|
|
chat_history: messages,
|
|
|
|
|
context_photos: contextPhotos,
|
|
|
|
|
assistant_prompt: config.assistant_prompt || '',
|
|
|
|
|
assistant_instructions: config.assistant_instructions || '',
|
|
|
|
|
response_length: config.response_length || 'medium',
|
|
|
|
|
vehicle_info: {
|
|
|
|
|
brand: inspection.vehicle_brand,
|
|
|
|
|
model: inspection.vehicle_model,
|
|
|
|
|
plate: inspection.vehicle_plate,
|
|
|
|
|
km: inspection.vehicle_km
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
formData.append('context_photos', JSON.stringify(contextPhotos))
|
|
|
|
|
formData.append('vehicle_info', JSON.stringify({
|
|
|
|
|
brand: inspection.vehicle_brand,
|
|
|
|
|
model: inspection.vehicle_model,
|
|
|
|
|
plate: inspection.vehicle_plate,
|
|
|
|
|
km: inspection.vehicle_km
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
console.log('📤 Enviando a chat IA:', payload)
|
|
|
|
|
console.log('📤 Enviando a chat IA con archivos:', currentFiles.length)
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${API_URL}/api/ai/chat-assistant`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Authorization': `Bearer ${token}`,
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
'Authorization': `Bearer ${token}`
|
|
|
|
|
// No incluir Content-Type, fetch lo establece automáticamente con FormData
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(payload)
|
|
|
|
|
body: formData
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
@@ -5430,6 +5470,18 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
|
|
|
|
<div className="text-sm sm:text-base whitespace-pre-wrap break-words">
|
|
|
|
|
{msg.content}
|
|
|
|
|
</div>
|
|
|
|
|
{/* Mostrar archivos adjuntos si existen */}
|
|
|
|
|
{msg.files && msg.files.length > 0 && (
|
|
|
|
|
<div className="mt-2 space-y-1">
|
|
|
|
|
{msg.files.map((file, fIdx) => (
|
|
|
|
|
<div key={fIdx} className={`text-xs flex items-center gap-1 ${msg.role === 'user' ? 'text-blue-100' : 'text-gray-600'}`}>
|
|
|
|
|
<span>{file.type.startsWith('image/') ? '🖼️' : '📄'}</span>
|
|
|
|
|
<span className="truncate">{file.name}</span>
|
|
|
|
|
<span>({(file.size / 1024).toFixed(1)} KB)</span>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div
|
|
|
|
|
className={`text-xs mt-2 ${
|
|
|
|
|
msg.role === 'user' ? 'text-blue-100' : 'text-gray-400'
|
|
|
|
|
@@ -5463,7 +5515,43 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
|
|
|
|
|
|
|
|
|
{/* Input */}
|
|
|
|
|
<div className="border-t p-3 sm:p-4 bg-white rounded-b-xl">
|
|
|
|
|
{/* Preview de archivos adjuntos */}
|
|
|
|
|
{attachedFiles.length > 0 && (
|
|
|
|
|
<div className="mb-3 flex flex-wrap gap-2">
|
|
|
|
|
{attachedFiles.map((file, idx) => (
|
|
|
|
|
<div key={idx} className="flex items-center gap-2 bg-gray-100 px-3 py-2 rounded-lg text-sm">
|
|
|
|
|
<span>{file.type.startsWith('image/') ? '🖼️' : '📄'}</span>
|
|
|
|
|
<span className="max-w-[150px] truncate">{file.name}</span>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => removeAttachedFile(idx)}
|
|
|
|
|
className="text-red-600 hover:text-red-800 font-bold"
|
|
|
|
|
type="button"
|
|
|
|
|
>
|
|
|
|
|
✕
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
onChange={handleFileAttach}
|
|
|
|
|
accept="image/*,application/pdf"
|
|
|
|
|
multiple
|
|
|
|
|
className="hidden"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => fileInputRef.current?.click()}
|
|
|
|
|
disabled={loading}
|
|
|
|
|
className="px-3 py-2 sm:py-3 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
title="Adjuntar archivos (imágenes o PDFs)"
|
|
|
|
|
type="button"
|
|
|
|
|
>
|
|
|
|
|
📎
|
|
|
|
|
</button>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={inputMessage}
|
|
|
|
|
@@ -5475,7 +5563,7 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
onClick={sendMessage}
|
|
|
|
|
disabled={!inputMessage.trim() || loading}
|
|
|
|
|
disabled={(!inputMessage.trim() && attachedFiles.length === 0) || loading}
|
|
|
|
|
className="px-4 sm:px-6 py-2 sm:py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white rounded-lg hover:from-purple-700 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm sm:text-base font-semibold"
|
|
|
|
|
>
|
|
|
|
|
Enviar
|
|
|
|
|
|