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:
2025-11-30 23:23:43 -03:00
parent a692948a6f
commit 14a64778b8
6 changed files with 592 additions and 4 deletions

View File

@@ -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(