diff --git a/ChatbotApi/Services/ChatService.cs b/ChatbotApi/Services/ChatService.cs
index 7563815..c371a96 100644
--- a/ChatbotApi/Services/ChatService.cs
+++ b/ChatbotApi/Services/ChatService.cs
@@ -48,7 +48,7 @@ namespace ChatbotApi.Services
_apiUrl = $"{baseUrl}{apiKey}";
}
- // Response model for structured JSON from Gemini
+ // Modelo de respuesta para JSON estructurado de Gemini
private class GeminiStructuredResponse
{
public string intent { get; set; } = "NOTICIAS_PORTADA";
@@ -75,7 +75,7 @@ namespace ChatbotApi.Services
try
{
- // Load article if URL provided
+ // Cargar artículo si se proporciona URL
if (!string.IsNullOrEmpty(request.ContextUrl) && await UrlSecurity.IsSafeUrlAsync(request.ContextUrl))
{
articleTask = GetArticleContentAsync(request.ContextUrl);
@@ -83,7 +83,7 @@ namespace ChatbotApi.Services
if (articleTask != null) articleContext = await articleTask;
- // Build context based on heuristics
+ // Construir contexto basado en heurísticas
if (!string.IsNullOrEmpty(articleContext))
{
context = articleContext;
@@ -102,7 +102,16 @@ namespace ChatbotApi.Services
if (bestMatch == null)
{
- bestMatch = await FindBestMatchingArticleAIAsync(safeUserMessage, articles, request.ConversationSummary);
+ // Optimización: Solo llamar AI matching si el mensaje parece específico
+ // Evita llamadas innecesarias para saludos y mensajes genéricos
+ if (RequiresAIMatching(safeUserMessage))
+ {
+ bestMatch = await FindBestMatchingArticleAIAsync(safeUserMessage, articles, request.ConversationSummary);
+ }
+ else
+ {
+ _logger.LogInformation("Mensaje genérico detectado: '{Message}'. Skipping AI matching.", safeUserMessage);
+ }
}
if (bestMatch != null && await UrlSecurity.IsSafeUrlAsync(bestMatch.Url))
@@ -122,7 +131,7 @@ namespace ChatbotApi.Services
}
}
- // Add knowledge base if available
+ // Agregar base de conocimiento si está disponible
var knowledgeItems = await GetKnowledgeItemsAsync();
if (knowledgeItems.Any())
{
@@ -146,7 +155,7 @@ namespace ChatbotApi.Services
yield break;
}
- // ========== UNIFIED API CALL ==========
+ // ========== LLAMADA API UNIFICADA ==========
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(45);
@@ -158,7 +167,7 @@ namespace ChatbotApi.Services
? request.SystemPromptOverride
: await systemPromptsTask;
- // Build unified meta-prompt
+ // Construir meta-prompt unificado
var promptBuilder = new StringBuilder();
promptBuilder.AppendLine("");
@@ -197,7 +206,7 @@ namespace ChatbotApi.Services
promptBuilder.AppendLine("");
promptBuilder.AppendLine();
- // Conversation history
+ // Historial de conversación
if (!string.IsNullOrWhiteSpace(request.ConversationSummary))
{
promptBuilder.AppendLine("");
@@ -206,13 +215,13 @@ namespace ChatbotApi.Services
promptBuilder.AppendLine();
}
- // Context
+ // Contexto
promptBuilder.AppendLine("");
promptBuilder.AppendLine(context);
promptBuilder.AppendLine("");
promptBuilder.AppendLine();
- // User question
+ // Pregunta del usuario
promptBuilder.AppendLine("");
promptBuilder.AppendLine(safeUserMessage);
promptBuilder.AppendLine("");
@@ -227,7 +236,7 @@ namespace ChatbotApi.Services
SafetySettings = GetDefaultSafetySettings()
};
- // Use non-streaming endpoint
+ // Usar endpoint sin streaming
var nonStreamingApiUrl = _apiUrl.Replace(":streamGenerateContent?alt=sse&", ":generateContent?");
var response = await httpClient.PostAsJsonAsync(nonStreamingApiUrl, requestData, cancellationToken);
@@ -259,11 +268,11 @@ namespace ChatbotApi.Services
yield break;
}
- // Parse JSON response (outside try-catch to allow yield)
+ // Parsear respuesta JSON (fuera del try-catch para permitir yield)
GeminiStructuredResponse? apiResponse = null;
try
{
- // Extract JSON from markdown code blocks if present
+ // Extraer JSON de bloques de código markdown si están presentes
var jsonContent = jsonText!;
if (jsonText!.Contains("```json"))
{
@@ -291,7 +300,7 @@ namespace ChatbotApi.Services
}
catch (JsonException ex)
{
- _logger.LogError(ex, "Failed to parse Gemini JSON. Raw response: {JsonText}", jsonText);
+ _logger.LogError(ex, "Error al parsear JSON de Gemini. Respuesta raw: {JsonText}", jsonText);
}
if (apiResponse == null || string.IsNullOrEmpty(apiResponse.reply))
@@ -300,10 +309,10 @@ namespace ChatbotApi.Services
yield break;
}
- // Send intent metadata
+ // Enviar metadata de intención
yield return $"INTENT::{apiResponse.intent}";
- // Simulate streaming by chunking the reply
+ // Simular streaming dividiendo la respuesta en fragmentos
string fullReply = apiResponse.reply;
var words = fullReply.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var chunkBuilder = new StringBuilder();
@@ -312,7 +321,7 @@ namespace ChatbotApi.Services
{
chunkBuilder.Append(word + " ");
- // Send chunk every ~20 characters for smooth streaming
+ // Enviar fragmento cada ~20 caracteres para streaming fluido
if (chunkBuilder.Length >= 20)
{
yield return chunkBuilder.ToString();
@@ -321,13 +330,13 @@ namespace ChatbotApi.Services
}
}
- // Send any remaining text
+ // Enviar cualquier texto restante
if (chunkBuilder.Length > 0)
{
yield return chunkBuilder.ToString();
}
- // Log conversation (fire-and-forget)
+ // Registrar conversación (fire-and-forget)
_ = Task.Run(async () =>
{
using (var scope = _serviceProvider.CreateScope())
@@ -346,16 +355,16 @@ namespace ChatbotApi.Services
catch(Exception ex)
{
var logger = scope.ServiceProvider.GetRequiredService>();
- logger.LogError(ex, "Error in background logging");
+ logger.LogError(ex, "Error en registro en segundo plano");
}
}
});
- // Send summary
+ // Enviar resumen
yield return $"SUMMARY::{apiResponse.summary}";
}
- // --- PRIVATE METHODS ---
+ // --- MÉTODOS PRIVADOS ---
private string SanitizeInput(string? input)
{
@@ -390,8 +399,99 @@ namespace ChatbotApi.Services
};
}
- // NOTE: UpdateConversationSummaryAsync and GetIntentAsync have been REMOVED
- // Their functionality is now in the unified StreamMessageAsync call
+ ///
+ /// Determina si un mensaje requiere búsqueda AI de artículos.
+ /// Usa enfoque híbrido: heurísticas (longitud, estructura) + patrones comunes.
+ /// Retorna false para mensajes genéricos (saludos, respuestas cortas, confirmaciones)
+ /// para evitar llamadas innecesarias a la API y reducir latencia.
+ ///
+ private bool RequiresAIMatching(string userMessage)
+ {
+ // Normalizar: lowercase, trim, quitar puntuación final
+ var normalized = userMessage.Trim().ToLowerInvariant()
+ .TrimEnd('.', '!', '?', ',', ';');
+
+ // Contar palabras (excluyendo puntuación)
+ var wordCount = normalized
+ .Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
+ .Length;
+
+ // ========== REGLA 1: Mensajes ultra-cortos (1-2 palabras) ==========
+ // Probablemente sean saludos o respuestas cortas, SALVO que contengan keywords específicas
+ if (wordCount <= 2)
+ {
+ // Excepciones: keywords de temas que SÍ requieren búsqueda de artículos
+ var specificKeywords = new[] {
+ "economía", "economia", "inflación", "inflacion", "dólar", "dolar",
+ "política", "politica", "elecciones", "gobierno",
+ "clima", "deporte", "fútbol", "futbol", "boca", "river"
+ };
+
+ // Si NO contiene ningún keyword específico, skip AI
+ if (!specificKeywords.Any(k => normalized.Contains(k)))
+ {
+ return false; // Skip AI - probablemente saludo/respuesta corta
+ }
+ }
+
+ // ========== REGLA 2: Preguntas casuales cortas ==========
+ // Si tiene signos de pregunta y es corto (≤4 palabras)
+ if (userMessage.Contains('?') && wordCount <= 4)
+ {
+ var casualQuestions = new[] {
+ "qué tal", "que tal", "cómo va", "como va",
+ "cómo estás", "como estas", "cómo andás", "como andas",
+ "todo bien", "qué onda", "que onda"
+ };
+
+ if (casualQuestions.Any(q => normalized.Contains(q)))
+ {
+ return false; // Skip AI - pregunta casual
+ }
+ }
+
+ // ========== REGLA 3: Lista expandida de patrones comunes ==========
+ // Mensajes cortos (≤3 palabras) que claramente son genéricos
+ if (wordCount <= 3)
+ {
+ var genericPatterns = new[]
+ {
+ // Saludos (incluyendo variantes argentinas)
+ "hola", "buenas", "buen día", "buenos días", "buenas tardes", "buenas noches",
+ "buen dia", "buenos dias", "hi", "hello", "hey",
+
+ // Confirmaciones/Aceptación (argentinismos incluidos)
+ "ok", "perfecto", "genial", "bárbaro", "barbaro", "dale", "dale dale",
+ "está bien", "esta bien", "de acuerdo", "si", "sí", "vale", "listo",
+ "joya", "buenísimo", "buenisimo", "excelente",
+
+ // Agradecimientos
+ "gracias", "muchas gracias", "mil gracias", "thank you", "thanks",
+
+ // Despedidas
+ "chau", "chao", "adiós", "adios", "hasta luego", "nos vemos", "bye",
+
+ // Ayuda genérica
+ "ayuda", "help", "ayúdame", "ayudame",
+
+ // Negaciones simples
+ "no", "nada", "ninguna", "ninguno"
+ };
+
+ if (genericPatterns.Contains(normalized))
+ {
+ return false; // Skip AI - patrón genérico detectado
+ }
+ }
+
+ // ========== Por defecto: usar AI matching ==========
+ // Cualquier mensaje que no caiga en las reglas anteriores
+ // (más de 4 palabras, o contiene keywords específicas, o no está en patrones)
+ return true;
+ }
+
+ // NOTA: UpdateConversationSummaryAsync y GetIntentAsync han sido REMOVIDOS
+ // Su funcionalidad ahora está en la llamada unificada StreamMessageAsync
private async Task SaveConversationLogAsync(string userMessage, string botReply)
{
diff --git a/chatbot-admin/src/components/SourceManager.tsx b/chatbot-admin/src/components/SourceManager.tsx
index 8aecb51..bf87a4d 100644
--- a/chatbot-admin/src/components/SourceManager.tsx
+++ b/chatbot-admin/src/components/SourceManager.tsx
@@ -1,4 +1,4 @@
-// EN: src/components/SourceManager.tsx
+// ChatBot\chatbot-admin\src\components\SourceManager.tsx
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid';
@@ -143,7 +143,7 @@ const SourceManager: React.FC = ({ onAuthError }) => {
icon={}
label="Eliminar"
onClick={() => handleDeleteClick(params.id as number)}
- color="inherit" // Keeping it generic to avoid type errors
+ color="inherit" // Mantener genérico para evitar errores de tipo
/>,
],
},
diff --git a/chatbot-admin/src/components/SystemPromptManager.tsx b/chatbot-admin/src/components/SystemPromptManager.tsx
index cfff351..35ab36e 100644
--- a/chatbot-admin/src/components/SystemPromptManager.tsx
+++ b/chatbot-admin/src/components/SystemPromptManager.tsx
@@ -7,7 +7,7 @@ import {
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
-import PlayArrowIcon from '@mui/icons-material/PlayArrow'; // Iconos para "Test"
+import PlayArrowIcon from '@mui/icons-material/PlayArrow'; // Íconos para "Test"
import AddIcon from '@mui/icons-material/Add';
import SendIcon from '@mui/icons-material/Send';
import apiClient from '../api/apiClient';