✅ Nuevo Tipo de Pregunta: Asistente IA (Chat) 🤖💬
Frontend (v1.0.88) QuestionTypeEditor.jsx ✅ Nuevo tipo: ai_assistant con icono 💬 ✅ Configuración completa: assistant_prompt: Define rol y comportamiento del asistente context_questions: IDs de preguntas anteriores cuyas fotos usar (o todas) assistant_instructions: Reglas específicas de diagnóstico max_messages: Límite de mensajes en el chat response_length: Corta/Media/Larga QuestionAnswerInput.jsx ✅ Mensaje informativo para tipo ai_assistant ✅ Indica que el chat se abre con botón separado App.jsx - Modal de Chat IA ✅ Modal full-screen responsive con: Header con gradiente purple/blue Área de mensajes con scroll Input de texto + botón enviar Soporte Enter para enviar Indicador de "pensando..." Timestamps en mensajes Confianza de la IA Límite de mensajes ✅ Botón "💬 Consultar Asistente IA" al lado de "Cargar Documentos" ✅ Contador de mensajes en el botón si ya hay historial ✅ Historial guardado en answers[questionId].chatHistory ✅ Auto-marca como completada cuando se abre el chat Backend (v1.0.85) Endpoint /api/ai/chat-assistant ✅ Recibe: Mensaje del usuario Historial del chat Fotos de preguntas anteriores con sus análisis Configuración del asistente Info del vehículo ✅ Contexto inteligente: Información del vehículo (marca, modelo, km, placa) Análisis previos de fotos (hasta 10 fotos más relevantes) Instrucciones personalizadas por pregunta Historial conversacional (últimos 10 mensajes) ✅ Soporte multi-proveedor: OpenAI (GPT-4, GPT-3.5) Google Gemini ✅ Respuestas adaptables: Longitud configurable (200/400/800 tokens) Temperature 0.7 para balance creatividad/precisión Cómo Funciona Configuración (Admin): - Crear pregunta tipo "Asistente IA (Chat)" - Definir prompt: "Eres experto en sistemas de frenos..." - Seleccionar preguntas anteriores: "5,8,12" (fotos de frenos) - Instrucciones: "Si pastillas < 3mm → cambio inmediato" - Longitud: Media - Max mensajes: 20 2 Durante Inspección (Mecánico): - Llega a pregunta tipo AI Assistant - Ve botón "💬 Consultar Asistente IA" - Clic → Abre chat full-screen - La IA YA vio las fotos de preguntas 5, 8, 12 - Mecánico pregunta: "¿Las pastillas están bien?" - IA responde basado en fotos previas + contexto vehículo - Conversación natural con historial - Al cerrar → se guarda automáticamente 3 Ejemplo de Conversación: Mecánico: "Vi desgaste en las pastillas, ¿qué recomiendas?" IA: "Basándome en las fotos que analicé: Pastilla delantera izquierda: ~2.5mm de espesor Pastilla delantera derecha: ~2.8mm ⚠️ CRÍTICO: Ambas pastillas están por debajo del mínimo de 3mm. Con 45,000 km, es normal este desgaste. RECOMENDACIÓN: - Cambio INMEDIATO de pastillas delanteras - Verificar estado de discos (posible rectificado) - Revisar líquido de frenos (última foto muestra nivel bajo) ¿Necesitas el código de pieza para este Toyota Corolla 2019?" Casos de Uso Diagnóstico de Frenos assistant_prompt: "Eres especialista en sistemas de frenos. Analiza desgaste, fugas, vibraciones." context_questions: "10,11,12,13" // Fotos de pastillas, discos, líquido assistant_prompt: "Experto en motores. Detecta fugas, ruidos anormales, consumo excesivo." context_questions: "5,6,7,8,9" // Motor, aceite, correa, filtros assistant_prompt: "Especialista en sistemas eléctricos y electrónicos." context_questions: "20,21,22" // Batería, luces, tablero instructions: "Siempre pedir código OBD2 si hay check engine" Ventajas ✅ Contextual: La IA ve fotos previas, no pregunta "¿puedes mostrarme?" ✅ Especializado: Un asistente POR tema (frenos, motor, eléctrico) ✅ Conversacional: El mecánico puede hacer follow-up questions ✅ Guiado: Instrucciones específicas por tipo de inspección ✅ Historial: No repite info, mantiene contexto de la conversación ✅ Móvil-friendly: Modal responsive, fácil de usar en celular
This commit is contained in:
@@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db):
|
||||
# No lanzamos excepción para no interrumpir el flujo normal
|
||||
|
||||
|
||||
BACKEND_VERSION = "1.0.84"
|
||||
BACKEND_VERSION = "1.0.85"
|
||||
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
|
||||
|
||||
# S3/MinIO configuration
|
||||
@@ -2849,6 +2849,175 @@ Responde en formato JSON:
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/ai/chat-assistant")
|
||||
async def chat_with_ai_assistant(
|
||||
request: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: models.User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Chat conversacional con IA usando contexto de fotos anteriores
|
||||
El asistente tiene acceso a fotos de preguntas previas para dar mejor contexto
|
||||
"""
|
||||
print("\n" + "="*80)
|
||||
print("🤖 AI CHAT ASSISTANT")
|
||||
print("="*80)
|
||||
|
||||
question_id = request.get('question_id')
|
||||
inspection_id = request.get('inspection_id')
|
||||
user_message = request.get('user_message')
|
||||
chat_history = request.get('chat_history', [])
|
||||
context_photos = request.get('context_photos', [])
|
||||
assistant_prompt = request.get('assistant_prompt', '')
|
||||
assistant_instructions = request.get('assistant_instructions', '')
|
||||
response_length = request.get('response_length', 'medium')
|
||||
vehicle_info = request.get('vehicle_info', {})
|
||||
|
||||
print(f"📋 Question ID: {question_id}")
|
||||
print(f"🚗 Inspection ID: {inspection_id}")
|
||||
print(f"💬 User message: {user_message}")
|
||||
print(f"📸 Context photos: {len(context_photos)} fotos")
|
||||
print(f"💭 Chat history: {len(chat_history)} mensajes previos")
|
||||
|
||||
# Obtener configuración de IA
|
||||
ai_config = db.query(models.AIConfiguration).filter(
|
||||
models.AIConfiguration.is_active == True
|
||||
).first()
|
||||
|
||||
if not ai_config:
|
||||
return {
|
||||
"success": False,
|
||||
"response": "No hay configuración de IA activa. Por favor configura en Settings.",
|
||||
"confidence": 0
|
||||
}
|
||||
|
||||
try:
|
||||
# Construir el contexto del vehículo
|
||||
vehicle_context = f"""
|
||||
INFORMACIÓN DEL VEHÍCULO:
|
||||
- Marca: {vehicle_info.get('brand', 'N/A')}
|
||||
- Modelo: {vehicle_info.get('model', 'N/A')}
|
||||
- Placa: {vehicle_info.get('plate', 'N/A')}
|
||||
- Kilometraje: {vehicle_info.get('km', 'N/A')} km
|
||||
"""
|
||||
|
||||
# Construir el contexto de las fotos anteriores
|
||||
photos_context = ""
|
||||
if context_photos:
|
||||
photos_context = f"\n\nFOTOS ANALIZADAS PREVIAMENTE ({len(context_photos)} imágenes):\n"
|
||||
for idx, photo in enumerate(context_photos[:10], 1): # Limitar a 10 fotos
|
||||
ai_analysis = photo.get('aiAnalysis', [])
|
||||
if ai_analysis and len(ai_analysis) > 0:
|
||||
analysis_text = ai_analysis[0].get('analysis', {})
|
||||
obs = analysis_text.get('observations', 'Sin análisis')
|
||||
status = analysis_text.get('status', 'unknown')
|
||||
photos_context += f"\n{idx}. Pregunta ID {photo.get('questionId')}: Status={status}\n Observaciones: {obs[:200]}...\n"
|
||||
|
||||
# Definir la longitud de respuesta
|
||||
max_tokens_map = {
|
||||
'short': 200,
|
||||
'medium': 400,
|
||||
'long': 800
|
||||
}
|
||||
max_tokens = max_tokens_map.get(response_length, 400)
|
||||
|
||||
# Construir el system prompt
|
||||
base_prompt = assistant_prompt or "Eres un experto mecánico automotriz que ayuda a diagnosticar problemas."
|
||||
|
||||
system_prompt = f"""{base_prompt}
|
||||
|
||||
{vehicle_context}
|
||||
|
||||
{photos_context}
|
||||
|
||||
INSTRUCCIONES ADICIONALES:
|
||||
{assistant_instructions if assistant_instructions else "Sé técnico, claro y directo en tus respuestas."}
|
||||
|
||||
FORMATO DE RESPUESTA:
|
||||
- Sé {response_length} en tus respuestas
|
||||
- Usa lenguaje técnico pero comprensible
|
||||
- Si ves algo preocupante en las fotos analizadas, menciónalo
|
||||
- Proporciona recomendaciones específicas cuando sea relevante
|
||||
- Si no tienes suficiente información, pide más detalles
|
||||
"""
|
||||
|
||||
# Construir el historial de mensajes para la IA
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
|
||||
# Agregar historial previo (últimos 10 mensajes para no saturar)
|
||||
for msg in chat_history[-10:]:
|
||||
messages.append({
|
||||
"role": msg.get('role'),
|
||||
"content": msg.get('content')
|
||||
})
|
||||
|
||||
# Agregar el mensaje actual del usuario
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": user_message
|
||||
})
|
||||
|
||||
print(f"🔧 Enviando a {ai_config.provider} con {len(messages)} mensajes")
|
||||
|
||||
# Llamar a la IA según el proveedor
|
||||
if ai_config.provider == 'openai':
|
||||
from openai import OpenAI
|
||||
client = OpenAI(api_key=ai_config.api_key)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=ai_config.model or "gpt-4",
|
||||
messages=messages,
|
||||
max_tokens=max_tokens,
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
ai_response = response.choices[0].message.content
|
||||
confidence = 0.85 # OpenAI no devuelve confidence directo
|
||||
|
||||
elif ai_config.provider == 'gemini':
|
||||
import google.generativeai as genai
|
||||
genai.configure(api_key=ai_config.api_key)
|
||||
|
||||
model = genai.GenerativeModel(ai_config.model or 'gemini-pro')
|
||||
|
||||
# Gemini maneja el chat diferente
|
||||
# Convertir mensajes al formato de Gemini
|
||||
chat_content = ""
|
||||
for msg in messages[1:]: # Skip system message
|
||||
role_label = "Usuario" if msg['role'] == 'user' else "Asistente"
|
||||
chat_content += f"\n{role_label}: {msg['content']}\n"
|
||||
|
||||
full_prompt = f"{system_prompt}\n\nCONVERSACIÓN:\n{chat_content}\n\nAsistente:"
|
||||
|
||||
response = model.generate_content(full_prompt)
|
||||
ai_response = response.text
|
||||
confidence = 0.80
|
||||
|
||||
else:
|
||||
raise ValueError(f"Proveedor no soportado: {ai_config.provider}")
|
||||
|
||||
print(f"✅ Respuesta generada: {len(ai_response)} caracteres")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"response": ai_response,
|
||||
"confidence": confidence,
|
||||
"provider": ai_config.provider,
|
||||
"model": ai_config.model
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error en chat IA: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"response": f"Error al comunicarse con el asistente: {str(e)}",
|
||||
"confidence": 0
|
||||
}
|
||||
|
||||
|
||||
# ============= REPORTS =============
|
||||
@app.get("/api/reports/dashboard", response_model=schemas.DashboardData)
|
||||
def get_dashboard_data(
|
||||
|
||||
Reference in New Issue
Block a user