Versiones Actualizadas
Backend: 1.2.12 → 1.2.13
Frontend: 1.3.8 → 1.3.9
💡 Beneficios
Mejor UX: El mecánico no ve un chat vacío, sino orientación inmediata
Contextualizado: El asistente menciona lo que ya conoce de la inspección
Guía clara: Indica qué puede hacer el asistente según las instrucciones configuradas
Eficiente: Solo se genera una vez por sesión de chat
This commit is contained in:
@@ -278,7 +278,7 @@ def extract_pdf_text_smart(pdf_content: bytes, max_chars: int = None) -> dict:
|
||||
}
|
||||
|
||||
|
||||
BACKEND_VERSION = "1.2.12"
|
||||
BACKEND_VERSION = "1.2.13"
|
||||
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
|
||||
|
||||
# S3/MinIO configuration
|
||||
@@ -3453,6 +3453,11 @@ async def chat_with_ai_assistant(
|
||||
print(f"📝 Context answers: {len(context_answers_list)} respuestas previas")
|
||||
print(f"💭 Chat history: {len(chat_history_list)} mensajes previos")
|
||||
|
||||
# Detectar si es el mensaje inicial de bienvenida
|
||||
is_initial_greeting = (user_message == "__INITIAL_GREETING__")
|
||||
if is_initial_greeting:
|
||||
print("🎉 MENSAJE INICIAL DE BIENVENIDA")
|
||||
|
||||
# Procesar archivos adjuntos
|
||||
attached_files_data = []
|
||||
if files:
|
||||
@@ -3564,6 +3569,36 @@ INFORMACIÓN DEL VEHÍCULO:
|
||||
if observations:
|
||||
answers_context += f" Observaciones: {observations}\n"
|
||||
|
||||
# Si es mensaje inicial, generar saludo contextualizado
|
||||
if is_initial_greeting:
|
||||
greeting_parts = ["¡Hola! Soy tu Asistente Ayutec."]
|
||||
|
||||
# Mencionar instrucciones específicas si existen
|
||||
if assistant_instructions:
|
||||
greeting_parts.append(f"\n\n{assistant_instructions}")
|
||||
elif assistant_prompt:
|
||||
greeting_parts.append(f"\n\nEstoy aquí para ayudarte con: {assistant_prompt}")
|
||||
|
||||
# Mencionar contexto disponible
|
||||
context_info = []
|
||||
if context_answers_list:
|
||||
context_info.append(f"{len(context_answers_list)} respuestas anteriores")
|
||||
if context_photos_list:
|
||||
context_info.append(f"{len(context_photos_list)} fotografías")
|
||||
|
||||
if context_info:
|
||||
greeting_parts.append(f"\n\nHe analizado {' y '.join(context_info)} de esta inspección del vehículo {vehicle_info_dict.get('brand', '')} {vehicle_info_dict.get('model', '')} (Placa: {vehicle_info_dict.get('plate', '')}).")
|
||||
|
||||
# Pregunta específica o solicitud de información
|
||||
if assistant_prompt:
|
||||
greeting_parts.append("\n\n¿Qué información necesitas o deseas que analice?")
|
||||
else:
|
||||
greeting_parts.append("\n\n¿En qué puedo ayudarte con esta inspección?")
|
||||
|
||||
# Reemplazar el mensaje especial con el saludo generado
|
||||
user_message = "".join(greeting_parts)
|
||||
print(f"✅ Mensaje de bienvenida generado: {user_message[:100]}...")
|
||||
|
||||
# Definir la longitud de respuesta
|
||||
max_tokens_map = {
|
||||
'short': 200,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "checklist-frontend",
|
||||
"private": true,
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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.3.8';
|
||||
const CACHE_NAME = 'ayutec-v1.3.9';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/index.html'
|
||||
|
||||
@@ -4384,6 +4384,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
const [showAIChat, setShowAIChat] = useState(false)
|
||||
const [aiChatMessages, setAiChatMessages] = useState([])
|
||||
const [aiChatLoading, setAiChatLoading] = useState(false)
|
||||
const [initialMessageSent, setInitialMessageSent] = useState(false)
|
||||
|
||||
// Signature canvas
|
||||
const mechanicSigRef = useRef(null)
|
||||
@@ -5309,9 +5310,10 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const existingHistory = answers[currentQuestion.id]?.chatHistory || []
|
||||
setAiChatMessages(existingHistory)
|
||||
setInitialMessageSent(existingHistory.length > 0) // Solo enviar inicial si no hay historial
|
||||
setShowAIChat(true)
|
||||
// SIEMPRE inicializar - cargar historial guardado O array vacío para nueva sesión
|
||||
setAiChatMessages(answers[currentQuestion.id]?.chatHistory || [])
|
||||
}}
|
||||
className="w-full mt-3 px-4 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white rounded-lg hover:from-purple-700 hover:to-blue-700 transition flex items-center justify-center gap-2 font-semibold shadow-lg"
|
||||
>
|
||||
@@ -5647,12 +5649,14 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
{showAIChat && currentQuestion && (
|
||||
<AIAssistantChatModal
|
||||
question={currentQuestion}
|
||||
inspection={{ id: inspectionId, ...vehicleData }}
|
||||
inspection={{ id: inspectionId, ...vehicleData, checklist: checklist }}
|
||||
allAnswers={answers}
|
||||
messages={aiChatMessages}
|
||||
setMessages={setAiChatMessages}
|
||||
loading={aiChatLoading}
|
||||
setLoading={setAiChatLoading}
|
||||
initialMessageSent={initialMessageSent}
|
||||
setInitialMessageSent={setInitialMessageSent}
|
||||
onClose={() => {
|
||||
setShowAIChat(false)
|
||||
// Guardar historial del chat en la respuesta
|
||||
@@ -5673,7 +5677,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
}
|
||||
|
||||
// Componente Modal de Chat IA Asistente
|
||||
function AIAssistantChatModal({ question, inspection, allAnswers, messages, setMessages, loading, setLoading, onClose }) {
|
||||
function AIAssistantChatModal({ question, inspection, allAnswers, messages, setMessages, loading, setLoading, initialMessageSent, setInitialMessageSent, onClose }) {
|
||||
const [inputMessage, setInputMessage] = useState('')
|
||||
const [attachedFiles, setAttachedFiles] = useState([])
|
||||
const [selectedImage, setSelectedImage] = useState(null) // Para lightbox
|
||||
@@ -5686,6 +5690,14 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
||||
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [messages])
|
||||
|
||||
// Generar mensaje de bienvenida automático al abrir el chat
|
||||
useEffect(() => {
|
||||
if (!initialMessageSent && messages.length === 0) {
|
||||
sendInitialMessage()
|
||||
setInitialMessageSent(true)
|
||||
}
|
||||
}, [initialMessageSent])
|
||||
|
||||
// Limpiar URLs temporales al desmontar el componente
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -5697,6 +5709,122 @@ function AIAssistantChatModal({ question, inspection, allAnswers, messages, setM
|
||||
}
|
||||
}, [attachedFiles])
|
||||
|
||||
// Enviar mensaje inicial de bienvenida del asistente
|
||||
const sendInitialMessage = async () => {
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const API_URL = import.meta.env.VITE_API_URL || ''
|
||||
|
||||
// Preparar contexto para el mensaje inicial
|
||||
const contextPhotos = []
|
||||
const contextAnswers = []
|
||||
const config = question.options || {}
|
||||
|
||||
const contextQuestionIds = config.context_questions
|
||||
? config.context_questions.split(',').map(id => parseInt(id.trim()))
|
||||
: Object.keys(allAnswers).map(id => parseInt(id))
|
||||
|
||||
const previousQuestionIds = contextQuestionIds.filter(id => id < question.id)
|
||||
|
||||
previousQuestionIds.forEach(qId => {
|
||||
const answer = allAnswers[qId]
|
||||
|
||||
if (answer?.photos && answer.photos.length > 0) {
|
||||
answer.photos.forEach(photoUrl => {
|
||||
contextPhotos.push({
|
||||
questionId: qId,
|
||||
url: photoUrl,
|
||||
aiAnalysis: answer.aiAnalysis
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (answer?.value || answer?.observations) {
|
||||
const questionData = inspection?.checklist?.questions?.find(q => q.id === qId)
|
||||
|
||||
let formattedAnswer = answer.value || ''
|
||||
if (questionData?.options?.type) {
|
||||
const qType = questionData.options.type
|
||||
|
||||
if (qType === 'boolean') {
|
||||
formattedAnswer = answer.value === 'true' ? 'Sí' : answer.value === 'false' ? 'No' : formattedAnswer
|
||||
} else if (qType === 'single_choice' && questionData.options.choices) {
|
||||
const selectedChoice = questionData.options.choices.find(c => c.value === answer.value)
|
||||
formattedAnswer = selectedChoice?.label || answer.value
|
||||
} else if (qType === 'scale') {
|
||||
formattedAnswer = `${answer.value} (escala ${questionData.options.min || 1}-${questionData.options.max || 10})`
|
||||
}
|
||||
}
|
||||
|
||||
contextAnswers.push({
|
||||
questionId: qId,
|
||||
questionText: questionData?.text || `Pregunta ${qId}`,
|
||||
questionType: questionData?.options?.type || 'text',
|
||||
answer: formattedAnswer,
|
||||
observations: answer.observations || ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('question_id', question.id)
|
||||
formData.append('inspection_id', inspection.id)
|
||||
formData.append('user_message', '__INITIAL_GREETING__') // Mensaje especial para identificar inicio
|
||||
formData.append('chat_history', JSON.stringify([]))
|
||||
formData.append('assistant_prompt', config.assistant_prompt || '')
|
||||
formData.append('assistant_instructions', config.assistant_instructions || '')
|
||||
formData.append('response_length', config.response_length || 'medium')
|
||||
formData.append('context_photos', JSON.stringify(contextPhotos))
|
||||
formData.append('context_answers', JSON.stringify(contextAnswers))
|
||||
formData.append('vehicle_info', JSON.stringify({
|
||||
brand: inspection.vehicle_brand,
|
||||
model: inspection.vehicle_model,
|
||||
plate: inspection.vehicle_plate,
|
||||
km: inspection.vehicle_km
|
||||
}))
|
||||
|
||||
console.log('📤 Generando mensaje de bienvenida del asistente...')
|
||||
|
||||
const response = await fetch(`${API_URL}/api/ai/chat-assistant`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Error en respuesta del servidor')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// Crear solo el mensaje del asistente (sin mensaje del usuario)
|
||||
const assistantMessage = {
|
||||
role: 'assistant',
|
||||
content: data.response,
|
||||
timestamp: new Date().toISOString(),
|
||||
confidence: data.confidence
|
||||
}
|
||||
|
||||
setMessages([assistantMessage])
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error al generar mensaje inicial:', error)
|
||||
const errorMessage = {
|
||||
role: 'assistant',
|
||||
content: '❌ Error al inicializar el chat. Por favor intenta nuevamente.',
|
||||
timestamp: new Date().toISOString(),
|
||||
isError: true
|
||||
}
|
||||
setMessages([errorMessage])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Manejar adjuntos de archivos
|
||||
const handleFileAttach = (e) => {
|
||||
const files = Array.from(e.target.files)
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<p className="text-xs text-indigo-300 font-medium hover:text-indigo-200">
|
||||
Ayutec v1.3.8
|
||||
Ayutec v1.3.9
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user