Răsfoiți Sursa

chat IRC version alpha

Erwin Jacimino 8 luni în urmă
părinte
comite
b370a5d86e

+ 4 - 1
main.py

@@ -7,7 +7,7 @@ from app import create_app, setup_routes
 from logging import getLogger
 from logging import getLogger
 
 
 from services.data_service import initialize_db
 from services.data_service import initialize_db
-
+import redis
 logger = getLogger("main")
 logger = getLogger("main")
 
 
 def main():
 def main():
@@ -26,6 +26,9 @@ def main():
         setup_routes(app)
         setup_routes(app)
         # Initialize database
         # Initialize database
         logger.info("Initializing database")
         logger.info("Initializing database")
+        redis_client = redis.Redis(host='localhost', port=6379, db=0)
+        # eliminar la lista de usuarios conectados al iniciar
+        redis_client.delete("connected_users")
         initialize_db()
         initialize_db()
         # Display startup information
         # Display startup information
         logger.info(f"Server starting on http://localhost:{PORT}")
         logger.info(f"Server starting on http://localhost:{PORT}")

+ 11 - 7
public/main/index.html

@@ -121,7 +121,7 @@
             <!-- Mensajes de ejemplo estilo IRC -->
             <!-- Mensajes de ejemplo estilo IRC -->
             <div class="text-gray-500 text-xs">*** Bienvenido al chat de Biergarten Klein ***</div>
             <div class="text-gray-500 text-xs">*** Bienvenido al chat de Biergarten Klein ***</div>
             <template id="chatMessageTemplate">
             <template id="chatMessageTemplate">
-              <div><span class="text-gray-400 chat-message-time">[00:36]</span> <span class="font-bold chat-message-user">&lt;JuanP_mesa5&gt;</span> <span class="chat-message-text">Yo también quiero!</span></div>
+              <div class="chat-message"><span class="text-gray-400 chat-message-time">[00:36]</span> <span class="font-bold chat-message-user">&lt;JuanP_mesa5&gt;</span> <span class="chat-message-text">Yo también quiero!</span></div>
             </template>
             </template>
             <template id="systemMessageTemplate">
             <template id="systemMessageTemplate">
               <div class="system-container"><span class="text-gray-400 chat-message-time">[14:24]</span> <span class="font-bold chat-message-text">*** Maria_mesa2 se ha unido al chat</span></div>
               <div class="system-container"><span class="text-gray-400 chat-message-time">[14:24]</span> <span class="font-bold chat-message-text">*** Maria_mesa2 se ha unido al chat</span></div>
@@ -130,9 +130,7 @@
           <!-- Input y botón -->
           <!-- Input y botón -->
           <form id="chatForm" class="flex flex-col relative gap-2 border-t border-gray-200 bg-white px-3 py-2" autocomplete="off">
           <form id="chatForm" class="flex flex-col relative gap-2 border-t border-gray-200 bg-white px-3 py-2" autocomplete="off">
               <ul id="userList" class="hidden absolute bottom-full w-3/4 rounded-lg border bg-gray-100 flex flex-col gap-2 py-2 px-2">
               <ul id="userList" class="hidden absolute bottom-full w-3/4 rounded-lg border bg-gray-100 flex flex-col gap-2 py-2 px-2">
-                <template id="listUserName">
-                  <li class="list-user-name">Maria_mesa2</li>
-                </template>
+            
               </ul>
               </ul>
             <div class="flex gap-2 w-full">
             <div class="flex gap-2 w-full">
               <input id="chatInput" type="text" placeholder="Escribe un mensaje..." class="flex-grow flex-1 px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#101419] text-sm bg-gray-50" autocomplete="off" maxlength="300" />
               <input id="chatInput" type="text" placeholder="Escribe un mensaje..." class="flex-grow flex-1 px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#101419] text-sm bg-gray-50" autocomplete="off" maxlength="300" />
@@ -227,9 +225,12 @@
         </div>
         </div>
         <span class="text-xs font-medium">Carrito</span>
         <span class="text-xs font-medium">Carrito</span>
       </button>
       </button>
-      <button data-target="chatTab" data-title="KleinBot" class="tab-btn flex-1 flex flex-col items-center text-[#58728d]">
-        <svg  xmlns="http://www.w3.org/2000/svg"  width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="h-8"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" /></svg>
-        <span class="text-xs font-medium">Chat IA</span>
+      <button data-target="chatTab" data-title="RetroChat" class="tab-btn flex-1 flex flex-col items-center text-[#58728d]">
+        <div id="chatIcon">
+          <span class="hidden"></span>
+          <svg  xmlns="http://www.w3.org/2000/svg"  width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="h-8"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" /></svg>
+          
+        </div><span class="text-xs font-medium">Chat IA</span>
       </button>
       </button>
             
             
     </nav>
     </nav>
@@ -499,6 +500,9 @@
     buttons.forEach(btn => {
     buttons.forEach(btn => {
       btn.addEventListener('click', () => {
       btn.addEventListener('click', () => {
         const target = btn.dataset.target;
         const target = btn.dataset.target;
+        if (target === "chatTab") {
+          window.hideMentioned();
+        }
         const active = document.querySelector(':not(.hidden)[data-tab]');
         const active = document.querySelector(':not(.hidden)[data-tab]');
         const activeIndex = active.dataset.index;
         const activeIndex = active.dataset.index;
         const to = document.querySelector(`#${target}[data-tab]`);
         const to = document.querySelector(`#${target}[data-tab]`);

+ 125 - 82
public/main/js/app.js

@@ -1,5 +1,5 @@
 //--- Imports ---
 //--- Imports ---
-import { sendMessage as serviceSendMessage } from './service/chat.js';
+import { getUserList } from './service/chat.js';
 import { getProducts, sendOrder } from './service/product.js';
 import { getProducts, sendOrder } from './service/product.js';
 import { login } from './service/auth.js';
 import { login } from './service/auth.js';
 import { createGlobalLoader, showGlobalLoader, hideGlobalLoader } from './utils/loader.js';
 import { createGlobalLoader, showGlobalLoader, hideGlobalLoader } from './utils/loader.js';
@@ -191,6 +191,7 @@ function initializeWebSocket() {
         }
         }
         chatWebsocket.onmessage = (event) => {
         chatWebsocket.onmessage = (event) => {
             const data = JSON.parse(event.data);
             const data = JSON.parse(event.data);
+            console.log("Event:", data);
             if (data.type === "message") {
             if (data.type === "message") {
                 displayChatMessage(data.username, data.message);
                 displayChatMessage(data.username, data.message);
             }
             }
@@ -200,6 +201,10 @@ function initializeWebSocket() {
             else if (data.type === "leave") {
             else if (data.type === "leave") {
                 userLeftChat(data.username);
                 userLeftChat(data.username);
             }
             }
+            else if (data.type === "mentioned") {
+                if (data.username && data.username !== chatUserName) return; // not for me
+                showMentioned();
+            }
             else if (data.type === "ping") {
             else if (data.type === "ping") {
                 chatWebsocket.send(JSON.stringify({
                 chatWebsocket.send(JSON.stringify({
                     type: "pong"
                     type: "pong"
@@ -209,8 +214,42 @@ function initializeWebSocket() {
     }
     }
 }
 }
 
 
-
-
+async function sendMessage(message) {
+    if (chatWebsocket) {
+        if (chatWebsocket.readyState !== WebSocket.OPEN) {
+            console.error("WebSocket is not open, reopening...");
+            chatWebsocket = null;
+            initializeWebSocket();
+        }
+        await chatWebsocket.send(JSON.stringify({
+            type: "message",
+            username: chatUserName,
+            message: message
+        }));
+        await setTimeout(() => {
+        const mentionRegex = /@([a-zA-Z0-9_]+_mesa\d+|IAKlein)/g;
+        const mentions = message.match(mentionRegex);
+        if (mentions) {
+            mentions.forEach(mention => {
+                const username = mention.slice(1);
+                if (username === "IAKlein"){
+                    sendMessageToAI(message);
+                }else {
+                    userMentioned(username);
+                }
+            });
+        }
+        }, 1000); // small delay to ensure message order
+        // regex  text_mesa111 or IAKlein
+        
+    }
+}
+function userMentioned(username) {
+    chatWebsocket.send(JSON.stringify({
+        type: "mention",
+        username: username
+    }));
+}
 /**
 /**
  * Initialize chat functionality
  * Initialize chat functionality
  */
  */
@@ -221,21 +260,35 @@ function initializeChat() {
         if (chatInputElement.value.trim() === "") return;
         if (chatInputElement.value.trim() === "") return;
 
 
         // sendMessageToAI();
         // sendMessageToAI();
-        if (chatWebsocket) {
-            chatWebsocket.send(JSON.stringify({
-                type: "message",
-                username: chatUserName,
-                message: chatInputElement.value
-            }));
-            chatInputElement.value = "";
-        }
-        // displayChatMessage(chatUserName, chatInputElement.value)
+        sendMessage(chatInputElement.value.trim());
+        chatInputElement.value = "";
     });
     });
-    chatInputElement.addEventListener("input", () => {
+    chatInputElement.addEventListener("input", async () => {
       const lastWord = chatInputElement.value.split(" ").at(-1)
       const lastWord = chatInputElement.value.split(" ").at(-1)
       if (lastWord.trim().startsWith("@")) {
       if (lastWord.trim().startsWith("@")) {
+        console.log("Detectado @ en el input, buscando usuarios...");
         userList.classList.remove("hidden")
         userList.classList.remove("hidden")
-
+        console.log("Buscando usuarios para:", lastWord.trim().substring(1));
+        let users = await getUserList(lastWord.trim().substring(1), userToken)
+        users = users.filter(u => u !== chatUserName)
+        users.push("IAKlein")
+        userList.innerHTML = ""
+        users.forEach(user => {
+           const userItem = document.createElement("button");
+           userItem.type = "button"
+           userItem.onclick = () => {
+               chatInputElement.value = chatInputElement.value.replace(lastWord, `@${user}`);
+               userList.classList.add("hidden");
+           };
+           console.log(user);
+           const color = getUserColor(user);
+           userItem.classList.add("list-user-name");
+            userItem.classList.add("cursor-pointer");
+            userItem.classList.add(color);
+
+          userItem.textContent = user;
+          userList.appendChild(userItem);
+        });
       }else{
       }else{
         userList.classList.add("hidden")
         userList.classList.add("hidden")
       }
       }
@@ -750,14 +803,46 @@ async function processOrder() {
  * Display chat message with proper styling
  * Display chat message with proper styling
  */
  */
 
 
+function showMentioned(){
+    const chatButton = document.querySelector("#chatIcon span");
+
+    if (chatButton) {
+        //get after element
+        console.log("Mostrando mención en el botón de chat");
+        chatButton.classList.remove("hidden");
+        chatButton.animate(
+    [
+        { transform: 'scale(1)' },
+        { transform: 'scale(1.5)' },
+        { transform: 'scale(1)' }
+    ],
+    {
+        duration: 300,
+        iterations: 3,
+        easing: 'ease-in-out'
+    }
+);
+
+    }
+}
+window.hideMentioned = function(){
+    const chatButton = document.querySelector("#chatIcon span");
+    if (chatButton) {
+        chatButton.classList.add("hidden");
+    }
+}
 function getUserColor(username) {
 function getUserColor(username) {
   const hash = [...username].reduce((acc, char) => acc + char.charCodeAt(0), 0);
   const hash = [...username].reduce((acc, char) => acc + char.charCodeAt(0), 0);
-  return userColors[hash % userColors.length];
+  console.log(userColors.length)
+  const color = userColors[hash % userColors.length];
+  console.log(`User: ${username}, Hash: ${hash}, Color: ${color}`);
+  
+  return color;
 }
 }
 
 
 function displayChatMessage(sender, message, time = undefined) {
 function displayChatMessage(sender, message, time = undefined) {
     console.log(`[${sender.toUpperCase()}] ${message}`);
     console.log(`[${sender.toUpperCase()}] ${message}`);
-    let realtime = `[${time ? time : new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}] `;
+    let realtime = `[${time ? time : new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `;
     let messageTemplate = chatMessagesElement.querySelector("#chatMessageTemplate");
     let messageTemplate = chatMessagesElement.querySelector("#chatMessageTemplate");
     if (!messageTemplate) return;
     if (!messageTemplate) return;
 
 
@@ -774,8 +859,11 @@ function newUserInChat(userName) {
     let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate");
     let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate");
     if (!userTemplate) return;
     if (!userTemplate) return;
 
 
+    const realtime = `[${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `;
+
     let userClone = userTemplate.content.cloneNode(true).firstElementChild;
     let userClone = userTemplate.content.cloneNode(true).firstElementChild;
     userClone.classList.add("text-green-600");
     userClone.classList.add("text-green-600");
+    userClone.querySelector(".chat-message-time").innerHTML = realtime;
     userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha unido al chat`;
     userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha unido al chat`;
     chatMessagesElement.appendChild(userClone);
     chatMessagesElement.appendChild(userClone);
 }
 }
@@ -784,54 +872,33 @@ function userLeftChat(userName) {
     let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate");
     let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate");
     if (!userTemplate) return;
     if (!userTemplate) return;
 
 
+    const realtime = `[${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `;
+
     let userClone = userTemplate.content.cloneNode(true).firstElementChild;
     let userClone = userTemplate.content.cloneNode(true).firstElementChild;
     userClone.classList.add("text-red-600");
     userClone.classList.add("text-red-600");
+    userClone.querySelector(".chat-message-time").innerHTML = realtime;
     userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha ido del chat`;
     userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha ido del chat`;
     chatMessagesElement.appendChild(userClone);
     chatMessagesElement.appendChild(userClone);
 }
 }
 
 
-/**
- * Send message to AI service
- */
-async function sendMessageToAI() {
-    if (!chatInputElement || !aiLoadingIndicator) return;
-    
-    const userInput = chatInputElement.value.trim();
-    if (!userInput) return;
+function sendMessageToAI() {
+    const userMessage = chatInputElement.value.trim();
+    if (userMessage === "") return;
+    const messages = Array.from(chatMessagesElement.querySelectorAll(".chat-message")).slice(-10);
+    let formattedMessages = messages.map(msg => {
+        const sender = msg.querySelector(".chat-message-user").textContent;
+        const content = msg.querySelector(".chat-message-text").innerHTML;
+        return { user: sender, content: content };
+    });
 
 
-    displayChatMessage("user", userInput);
-    chatSuggestionsElement.classList.add("hidden");
-    chatInputElement.value = '';
-    aiLoadingIndicator.classList.remove("hidden");
 
 
-    try {
-        const response = await serviceSendMessage(userInput, chatHistory, userName, userToken);
-        
-        if (!response) {
-            displayChatMessage("ai", "Hubo un problema al conectar con el Chef IA.");
-        } else if (response === "not_init") {
-            if (await initializeService()) {
-                const retryResponse = await serviceSendMessage(userInput, chatHistory, userName, userToken);
-                if (retryResponse) {
-                    chatHistory = retryResponse.messageList;
-                    displayChatMessage("ai", retryResponse.assistantResponse);
-                } else {
-                    displayChatMessage("ai", "Hubo un problema al enviar el mensaje.");
-                }
-            } else {
-                displayChatMessage("ai", "Fallo la reconexión. Por favor, refresca la página.");
-            }
-        } else if (response.assistantResponse) {
-            chatHistory = response.messageList;
-            displayChatMessage("ai", response.assistantResponse);
-        }
-    } catch (error) {
-        console.error("Error enviando mensaje a IA:", error);
-        displayChatMessage("ai", `Error: ${error.message || "No se pudo conectar con el Chef IA."}`);
-    } finally {
-        aiLoadingIndicator.classList.add("hidden");
-        if (chatInputElement) chatInputElement.focus();
-    }
+    chatWebsocket.send(JSON.stringify({
+        type: "ai_message",
+        username: chatUserName,
+        messages: formattedMessages
+    }));
+    chatInputElement.value = "";
+
 }
 }
 //--- Sistema de Recompensas ---
 //--- Sistema de Recompensas ---
 
 
@@ -916,31 +983,7 @@ function closeModal() {
     claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
     claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
 }
 }
 
 
-/**
- * Show reward success notification
- */
-function showRewardSuccess(code) {
-    const successToast = document.createElement('div');
-    successToast.className = `
-        fixed top-4 left-1/2 transform -translate-x-1/2 
-        bg-green-500 text-white text-sm rounded-lg px-6 py-4 
-        shadow-lg z-50 max-w-sm text-center
-    `;
-    successToast.innerHTML = `
-        <div class="font-bold mb-1">🎉 ¡Premio Reclamado!</div>
-        <div class="text-xs opacity-90">Tu código: <strong>${code}</strong></div>
-        <div class="text-xs mt-1 opacity-80">Muéstralo al mesero</div>
-    `;
-
-    document.body.appendChild(successToast);
-
-    // Remove after 5 seconds
-    setTimeout(() => {
-        if (successToast.parentNode) {
-            successToast.remove();
-        }
-    }, 5000);
-}
+
 
 
 /**
 /**
  * Reset reward progress to 0%
  * Reset reward progress to 0%
@@ -972,7 +1015,7 @@ document.addEventListener("DOMContentLoaded", async () => {
     createGlobalLoader();
     createGlobalLoader();
     // Uncomment these lines when ready to initialize the full app:
     // Uncomment these lines when ready to initialize the full app:
     initializeLoginModal();
     initializeLoginModal();
-    hideGUI();
+    // hideGUI();
     // initializeApp();
     // initializeApp();
 });
 });
 
 

+ 7 - 17
public/main/js/service/chat.js

@@ -1,30 +1,20 @@
 
 
-async function sendMessage(message, messageList, userName, token) {
-  if (!token) { 
+async function getUserList(q, token) {
+  if (!token) {
     return "not_init";
     return "not_init";
   }
   }
-  messageList.push({ role: "user", content: message });
-  const cuerpo = { messages: messageList, user: userName }
-  const response = await fetch("/api/chat/completions", {
-    method: "POST",
+  const response = await fetch(`/api/chat/users?q=${encodeURIComponent(q)}`, {
+    method: "GET",
     headers: {
     headers: {
-      "Content-Type": "application/json",
       "Authorization": `Bearer ${token}`
       "Authorization": `Bearer ${token}`
-    },
-    body: JSON.stringify(cuerpo)
+    }
   });
   });
   if (!response.ok) {
   if (!response.ok) {
     const errorData = await response.json().catch(() => ({ message: "Respuesta no válida del servidor." }));
     const errorData = await response.json().catch(() => ({ message: "Respuesta no válida del servidor." }));
     throw new Error(errorData.message || `Error del servidor: ${response.status}`);
     throw new Error(errorData.message || `Error del servidor: ${response.status}`);
   }
   }
   const data = await response.json();
   const data = await response.json();
-  const assistantResponse = data.response;
-  if (assistantResponse) {
-    messageList.push({ role: "assistant", content: assistantResponse });
-    return { messageList, assistantResponse };
-  } else {
-    throw new Error("Respuesta vacía del asistente.");
-  }
+  return data.users || [];
 }
 }
 
 
-export { sendMessage };
+export { getUserList };

+ 12 - 1
public/main/styles.css

@@ -52,7 +52,18 @@ footer.bg-black {
     color: white;
     color: white;
 }
 }
 
 
-#userList {
+#chatIcon {
+    position: relative;
+    & span {
+        content: "";
+        position: absolute;
+        top: -5px;
+        right: -5px;
+        width: 10px;
+        height: 10px;
+        background: red;
+        border-radius: 50%;
+    }
 }
 }
 
 
 .list-user-name {
 .list-user-name {

+ 48 - 53
routes/chat.py

@@ -1,7 +1,7 @@
 import asyncio
 import asyncio
 import json
 import json
 import logging
 import logging
-from fastapi import Request, HTTPException, Depends, APIRouter, WebSocket, WebSocketDisconnect
+from fastapi import Query, Request, HTTPException, Depends, APIRouter, WebSocket, WebSocketDisconnect
 from fastapi.responses import JSONResponse
 from fastapi.responses import JSONResponse
 from fastapi.security import HTTPAuthorizationCredentials
 from fastapi.security import HTTPAuthorizationCredentials
 from pydantic import BaseModel, ConfigDict
 from pydantic import BaseModel, ConfigDict
@@ -12,19 +12,12 @@ from models.user import User
 from services.openai_service.openai_service import generate_completion
 from services.openai_service.openai_service import generate_completion
 from auth.security import get_current_user
 from auth.security import get_current_user
 from config.messages import SuccessResponse
 from config.messages import SuccessResponse
-from contextlib import asynccontextmanager
-from fastapi import FastAPI
+from redis import Redis
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 chat_router = APIRouter()
 chat_router = APIRouter()
 
 
-
-class ConnectedUser(BaseModel):
-    model_config = ConfigDict(arbitrary_types_allowed=True)
-    websocket: WebSocket
-    username: str
-
-connected_users: list[ConnectedUser] = []  # todavía útil para listar usuarios conectados
+redis_client = Redis(host='localhost', port=6379, db=0, decode_responses=True)
 
 
 
 
 
 
@@ -54,7 +47,6 @@ async def chat_irc_endpoint(websocket: WebSocket):
     logger.info(f"User {current_user.email} connected to WebSocket chat.")
     logger.info(f"User {current_user.email} connected to WebSocket chat.")
 
 
     username = None
     username = None
-    stop_event = asyncio.Event()
     async def reader():
     async def reader():
         """Lee mensajes desde Redis y los manda al WebSocket"""
         """Lee mensajes desde Redis y los manda al WebSocket"""
         logger.info("Iniciando lector de mensajes")
         logger.info("Iniciando lector de mensajes")
@@ -75,29 +67,51 @@ async def chat_irc_endpoint(websocket: WebSocket):
     try:
     try:
         while True:
         while True:
             try:
             try:
+                
                 data = await asyncio.wait_for(websocket.receive_text(), timeout=30.0)
                 data = await asyncio.wait_for(websocket.receive_text(), timeout=30.0)
                 payload = json.loads(data)
                 payload = json.loads(data)
                 event_type = payload.get("type")
                 event_type = payload.get("type")
-
+                logger.debug(f"Received event: {event_type} with payload: {payload}")
                 if event_type == "join":
                 if event_type == "join":
                     logger.info(f"User {payload['username']} joined the chat.")
                     logger.info(f"User {payload['username']} joined the chat.")
-                    username = payload["username"]
-                    connected_users.append(ConnectedUser(websocket=websocket, username=username))
-                    response = {"type": "join", "username": username}
+                    data = {
+                        "mail": current_user.email,
+                        "username": payload["username"]
+                    }
+                    redis_client.sadd("connected_users", json.dumps(data))
+                    response = {"type": "join", "username": payload["username"]}
 
 
                 elif event_type == "message":
                 elif event_type == "message":
                     logger.debug(f"Message from {payload['username']}: {payload['message']}")
                     logger.debug(f"Message from {payload['username']}: {payload['message']}")
-                    username = payload["username"]
+                    message_username = payload["username"]
                     message = payload["message"]
                     message = payload["message"]
-                    response = {"type": "message", "username": username, "message": message}
+                    response = {"type": "message", "username": message_username, "message": message}
 
 
                 elif event_type == "leave":
                 elif event_type == "leave":
                     logger.info(f"User {payload['username']} left the chat.")
                     logger.info(f"User {payload['username']} left the chat.")
-                    response = {"type": "leave", "username": payload["username"]}
-                    connected_users = [u for u in connected_users if u.websocket != websocket]
+                    message_username = payload["username"]
+                    
+                    users = redis_client.smembers("connected_users")
+                    for user in users:
+                        user_data = json.loads(user)
+                        if user_data["username"] == message_username:
+                            redis_client.srem("connected_users", user)
+                            break
+                    response = {"type": "leave", "username": message_username}
                     await websocket.close()
                     await websocket.close()
                     break
                     break
-
+                elif event_type == "ai_message":
+                    logger.debug(f"IA Message from {payload['username']}: {payload['messages']}")
+                    message_username = payload["username"]
+                    messages = payload["messages"]
+                    response_content = await generate_completion(messages, current_user)
+                    response = {"type": "message", "username": "IAKlein", "message": response_content}
+                elif event_type == "mention":
+                    logger.debug(f"Mention to {payload['username']}")
+                    mention_username = payload["username"]
+                    logger.debug(f"Mention username: {mention_username}, Current username: {username}")
+                    await broadcast.publish(channel="chat", message=json.dumps({"type": "mentioned", "username": mention_username}))
+                    continue
                 elif event_type == "pong":
                 elif event_type == "pong":
                     logger.debug(f"Received pong from user {current_user.email}")
                     logger.debug(f"Received pong from user {current_user.email}")
                     continue
                     continue
@@ -109,9 +123,15 @@ async def chat_irc_endpoint(websocket: WebSocket):
                 await websocket.send_text(json.dumps({"type": "ping"}))
                 await websocket.send_text(json.dumps({"type": "ping"}))
 
 
     except WebSocketDisconnect:
     except WebSocketDisconnect:
-        connected_users = [u for u in connected_users if u.websocket != websocket]
         logger.info(f"User {current_user.email} disconnected from WebSocket chat.")
         logger.info(f"User {current_user.email} disconnected from WebSocket chat.")
-        response = {"type": "leave", "username": username or "unknown"}
+        
+        users = redis_client.smembers("connected_users")
+        for user in users:
+            user_data = json.loads(user)
+            if user_data["mail"] == current_user.email:
+                redis_client.srem("connected_users", user)
+                break
+        response = {"type": "leave", "username": user_data["username"]}
         await broadcast.publish(channel="chat", message=json.dumps(response))
         await broadcast.publish(channel="chat", message=json.dumps(response))
 
 
     finally:
     finally:
@@ -120,36 +140,11 @@ async def chat_irc_endpoint(websocket: WebSocket):
 
 
 
 
 @chat_router.get("/users")
 @chat_router.get("/users")
-async def get_connected_users(_: User = Depends(get_current_user)):
+async def get_connected_users(q: str = Query(...), _: User = Depends(get_current_user)):
     """Get a list of connected users (solo local al worker)"""
     """Get a list of connected users (solo local al worker)"""
-    return {"users": [user.username for user in connected_users]}
+    # return {"users": [user.username for user in connected_users if q.lower() in user.username.lower()]}
+    all_users = redis_client.smembers("connected_users")
+    all_users = [json.loads(user)["username"] for user in all_users]
+    filtered_users = [user for user in all_users if q.lower() in user.lower()]
+    return {"users": filtered_users}
 
 
-
-@chat_router.post("/completions")
-async def chat_completions(
-    request_data: ChatCompletionRequest,
-    request: Request,
-    current_user: User = Depends(get_current_user)
-):
-    """Get chat completions from OpenAI"""
-    session_identifier = request.session.get("antiAbuseToken", "unknown_session")
-    logger.info(f"Chat completion request from user {current_user.email}")
-
-    try:
-        openai_response = await generate_completion(
-            request_data.messages,
-            session_identifier,
-            current_user.name,
-            current_user.email
-        )
-        logger.info(f"Chat completion successful for user {current_user.email}")
-        return JSONResponse({
-            "response": openai_response,
-            "message": SuccessResponse.CHAT_RESPONSE_SUCCESS
-        })
-    except HTTPException as e:
-        logger.error(f"HTTP error in chat completion for user {current_user.email}: {e.detail}")
-        raise
-    except Exception as e:
-        logger.error(f"Unexpected error: {e}")
-        raise HTTPException(status_code=500, detail="Error interno del servidor al procesar el chat.")

+ 11 - 6
services/openai_service/openai_service.py

@@ -4,21 +4,22 @@ from fastapi import HTTPException
 from openai import OpenAI
 from openai import OpenAI
 from config.settings import OPENAI_API_KEY
 from config.settings import OPENAI_API_KEY
 from models.chat import Message
 from models.chat import Message
+from models.user import User
 from services.data_service import data_bg_loaded
 from services.data_service import data_bg_loaded
 from logging import getLogger
 from logging import getLogger
 from services.openai_service.openai_tools import tools_list, tools
 from services.openai_service.openai_tools import tools_list, tools
+
 # Initialize OpenAI client
 # Initialize OpenAI client
 openai_client = OpenAI(api_key=OPENAI_API_KEY)
 openai_client = OpenAI(api_key=OPENAI_API_KEY)
 
 
 logger = getLogger(__name__)
 logger = getLogger(__name__)
 
 
-async def generate_completion(messages_array: List[Message], session_id: str, name: str, email: str) -> str:
+async def generate_completion(messages_array: List[dict], user: User) -> str:
     """Generate OpenAI chat completion"""
     """Generate OpenAI chat completion"""
     if not OPENAI_API_KEY:
     if not OPENAI_API_KEY:
         logger.error("Error: OpenAI API key is not configured.")
         logger.error("Error: OpenAI API key is not configured.")
         raise HTTPException(status_code=500, detail="OpenAI API key not configured on server.")
         raise HTTPException(status_code=500, detail="OpenAI API key not configured on server.")
 
 
-    logger.info(f"[OpenAI Service Python] Session/Token {session_id} sent: {[msg.model_dump() for msg in messages_array]}")
 
 
     data_for_prompt = [
     data_for_prompt = [
         f'{{"pregunta": "{item.get("q", "")}", "respuesta": "{item.get("ans", "")}"}}'
         f'{{"pregunta": "{item.get("q", "")}", "respuesta": "{item.get("ans", "")}"}}'
@@ -28,21 +29,25 @@ async def generate_completion(messages_array: List[Message], session_id: str, na
 
 
     preprompt = f"""
     preprompt = f"""
 Eres un asistente de el bar klein, tu nombre es camilo klein, usas emojis para responder.
 Eres un asistente de el bar klein, tu nombre es camilo klein, usas emojis para responder.
-y ser carismatico con el cliente.
+y ser carismatico con los cliente.
 tus responsabilidades son:
 tus responsabilidades son:
 - Responder preguntas sobre el menu de el bar klein
 - Responder preguntas sobre el menu de el bar klein
+- bromear con los usuarios sobre distintos temas pero siempre redirigir la conversacion al bar klein
 - Proporcionar información sobre el menú de el bar klein
 - Proporcionar información sobre el menú de el bar klein
 - Proporcionar recomendaciones sobre el menú de el bar klein
 - Proporcionar recomendaciones sobre el menú de el bar klein
 - Proporcionar información sobre la comida de el bar klein
 - Proporcionar información sobre la comida de el bar klein
 - No puedes tomar pedidos de clientes, solo informar
 - No puedes tomar pedidos de clientes, solo informar
 - puedes recibir feedback de los clientes, y usar la herramienta feedback para enviar el feedback
 - puedes recibir feedback de los clientes, y usar la herramienta feedback para enviar el feedback
-- Debes evadir cualquier pregunta que no sea relacionada con el bar klein
+- respuestas cortas estilo app de mensajeria
+- si el usuario te pregunta por tu nombre, di que eres camilo klein
 para esto usaras los siguientes datos:
 para esto usaras los siguientes datos:
 {data_string}
 {data_string}
     """
     """
 
 
     processed_messages: List[dict] = [{"role": "system", "content": preprompt}]
     processed_messages: List[dict] = [{"role": "system", "content": preprompt}]
-    processed_messages.extend([msg.model_dump() for msg in messages_array])
+    processed_messages.append(
+        {"role": "user", "content": json.dumps(messages_array)}
+    )
 
 
     try:
     try:
         completion = openai_client.chat.completions.create(
         completion = openai_client.chat.completions.create(
@@ -60,7 +65,7 @@ para esto usaras los siguientes datos:
                     tool_function = tools[call.function.name]
                     tool_function = tools[call.function.name]
                     tool_args = json.loads(call.function.arguments)
                     tool_args = json.loads(call.function.arguments)
                     logger.info(f"Calling tool: {call.function.name} with args: {tool_args}")
                     logger.info(f"Calling tool: {call.function.name} with args: {tool_args}")
-                    tool_response = tool_function(name=name, email=email, **tool_args)
+                    tool_response = tool_function(name=user.name, email=user.email, **tool_args)
                     logger.info(f"Tool response: {tool_response}")
                     logger.info(f"Tool response: {tool_response}")
                     completion.choices[0].message.content = tool_response
                     completion.choices[0].message.content = tool_response
                 else:
                 else: