diff --git a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java index bb488b9..11b8986 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java +++ b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java @@ -1,10 +1,5 @@ package com.pablotj.ia.chat.boot.adapter.controller; -import com.pablotj.ia.chat.boot.application.session.ChatSessionManager; -import com.pablotj.ia.chat.boot.application.usecase.ChatUseCase; -import com.pablotj.ia.chat.boot.domain.model.ChatMessage; -import jakarta.servlet.http.HttpSession; -import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Controller; @@ -17,26 +12,10 @@ import org.springframework.web.bind.annotation.RequestMapping; public class ChatPageController { private static final Logger LOGGER = LogManager.getLogger(ChatPageController.class); - private final ChatSessionManager chatSessionManager; - private final ChatUseCase chatUseCase; - - public ChatPageController(ChatSessionManager chatSessionManager, ChatUseCase chatUseCase) { - this.chatSessionManager = chatSessionManager; - this.chatUseCase = chatUseCase; - } @GetMapping - public String showChat(Model model, HttpSession session) { + public String showChat(Model model) { LOGGER.debug("Accessing to chat"); - List messages = chatSessionManager.getMessages(session); - if (messages != null && messages.isEmpty()) { - try { - messages.add(chatUseCase.processUserPrompt("", session)); - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } - } - model.addAttribute("messages", messages); return "chat"; } } \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java index 4a12324..dade381 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java +++ b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java @@ -1,37 +1,66 @@ package com.pablotj.ia.chat.boot.adapter.controller; +import com.pablotj.ia.chat.boot.application.usecase.ChatHistoryUseCase; import com.pablotj.ia.chat.boot.application.usecase.ChatUseCase; +import com.pablotj.ia.chat.boot.domain.model.ChatIdentity; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; -import jakarta.servlet.http.HttpSession; import java.util.Date; +import java.util.List; +import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.ResponseEntity; +import org.springframework.util.ObjectUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/chat") +@RequestMapping("/api/v1/chats") public class ChatRestController { private static final Logger LOGGER = LogManager.getLogger(ChatRestController.class); private final ChatUseCase chatUseCase; + private final ChatHistoryUseCase chatHistoryUseCase; - public ChatRestController(ChatUseCase chatUseCase) { + public ChatRestController(ChatUseCase chatUseCase, ChatHistoryUseCase chatHistoryUseCase) { this.chatUseCase = chatUseCase; + this.chatHistoryUseCase = chatHistoryUseCase; } - @PostMapping(consumes = "application/x-www-form-urlencoded", produces = "application/json") - public ResponseEntity handleChat(@RequestParam("prompt") String prompt, HttpSession session) { + @GetMapping + public ResponseEntity> getChatHistory() { + LOGGER.debug("Accessing to chat"); + return ResponseEntity.ok(chatHistoryUseCase.chats()); + } + + @GetMapping("{chatId}") + public ResponseEntity> getChatMessages(@PathVariable("chatId") String chatId) { + LOGGER.debug("Accessing to chat messages"); + return ResponseEntity.ok(chatUseCase.getMessages(chatId)); + } + + @PostMapping(produces = "application/json") + public ResponseEntity newChat() { + return ResponseEntity.ok(UUID.randomUUID().toString()); + } + + @PutMapping(value = "{chatId}", consumes = "application/x-www-form-urlencoded", produces = "application/json") + public ResponseEntity handleChat(@PathVariable("chatId") String chatId, @RequestParam("prompt") String prompt) { + if (ObjectUtils.isEmpty(chatId)) { + throw new IllegalArgumentException("Chat id cannot be empty"); + } ChatMessage reply; try { - reply = chatUseCase.processUserPrompt(prompt, session); + reply = chatUseCase.processUserPrompt(prompt, chatId); } catch (Exception e) { LOGGER.error(e.getMessage(), e); - reply = new ChatMessage("bot", e.getMessage(), new Date()); + reply = new ChatMessage(chatId, "bot", e.getMessage(), new Date()); } return ResponseEntity.ok(reply); } diff --git a/src/main/java/com/pablotj/ia/chat/boot/application/session/ChatSessionManager.java b/src/main/java/com/pablotj/ia/chat/boot/application/session/ChatSessionManager.java index 2940748..aea765c 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/application/session/ChatSessionManager.java +++ b/src/main/java/com/pablotj/ia/chat/boot/application/session/ChatSessionManager.java @@ -2,13 +2,11 @@ package com.pablotj.ia.chat.boot.application.session; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; import com.pablotj.ia.chat.boot.domain.port.ChatMessageStore; -import jakarta.servlet.http.HttpSession; import java.util.ArrayList; +import java.util.List; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; -import java.util.List; - @Component public class ChatSessionManager { @@ -18,9 +16,8 @@ public class ChatSessionManager { this.chatMessageStore = chatMessageStore; } - public List getMessages(HttpSession session) { - String sessionId = session.getId(); - List messages = chatMessageStore.getMessages(sessionId); + public List getMessages(String chatId) { + List messages = chatMessageStore.getMessages(chatId); List filteredMessages = new ArrayList<>(messages); if (ObjectUtils.isEmpty(filteredMessages)) { @@ -31,8 +28,8 @@ public class ChatSessionManager { return filteredMessages; } - public void setMessages(HttpSession session, List messages) { + public void setMessages(String chatId, List messages) { messages.removeIf(m -> ObjectUtils.isEmpty(m.text())); - chatMessageStore.saveMessages(session.getId(), messages); + chatMessageStore.saveMessages(chatId, messages); } } \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatHistoryUseCase.java b/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatHistoryUseCase.java new file mode 100644 index 0000000..bde04e4 --- /dev/null +++ b/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatHistoryUseCase.java @@ -0,0 +1,20 @@ +package com.pablotj.ia.chat.boot.application.usecase; + +import com.pablotj.ia.chat.boot.domain.model.ChatIdentity; +import com.pablotj.ia.chat.boot.domain.port.ChatMessageStore; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class ChatHistoryUseCase { + + private final ChatMessageStore chatMessageStore; + + public ChatHistoryUseCase(ChatMessageStore chatMessageStore) { + this.chatMessageStore = chatMessageStore; + } + + public List chats() { + return chatMessageStore.getChats(); + } +} diff --git a/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java b/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java index bb33b62..15f9fcf 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java +++ b/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java @@ -5,7 +5,6 @@ import com.pablotj.ia.chat.boot.application.prompt.PromptTemplates; import com.pablotj.ia.chat.boot.application.session.ChatSessionManager; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; import com.pablotj.ia.chat.boot.infraestructure.llm.LlmModelClient; -import jakarta.servlet.http.HttpSession; import java.util.Date; import java.util.List; import org.springframework.stereotype.Component; @@ -25,9 +24,13 @@ public class ChatUseCase { this.sessionManager = sessionManager; } - public ChatMessage processUserPrompt(String prompt, HttpSession session) { - List messages = sessionManager.getMessages(session); - messages.add(new ChatMessage(ATTR_ROLE_USER, prompt, new Date())); + public List getMessages(String chatId) { + return sessionManager.getMessages(chatId); + } + + public ChatMessage processUserPrompt(String prompt, String chatId) { + List messages = sessionManager.getMessages(chatId); + messages.add(new ChatMessage(chatId, ATTR_ROLE_USER, prompt, new Date())); PromptBuilder builder = new PromptBuilder(PromptTemplates.getDefault()); @@ -40,9 +43,9 @@ public class ChatUseCase { } String result = llmModelClient.generate(builder.build()); - ChatMessage reply = new ChatMessage(ATTR_ROLE_BOT, result, new Date()); + ChatMessage reply = new ChatMessage(chatId, ATTR_ROLE_BOT, result, new Date()); messages.add(reply); - sessionManager.setMessages(session, messages); + sessionManager.setMessages(chatId, messages); return reply; } } diff --git a/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatIdentity.java b/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatIdentity.java new file mode 100644 index 0000000..6a4d512 --- /dev/null +++ b/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatIdentity.java @@ -0,0 +1,6 @@ +package com.pablotj.ia.chat.boot.domain.model; + +import java.io.Serializable; + +public record ChatIdentity(String id, String name) implements Serializable { +} \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatMessage.java b/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatMessage.java index e1889b8..584c36a 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatMessage.java +++ b/src/main/java/com/pablotj/ia/chat/boot/domain/model/ChatMessage.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import java.io.Serializable; import java.util.Date; -public record ChatMessage(String role, String text, Date date) implements Serializable { +public record ChatMessage(String chatId, String role, String text, Date date) implements Serializable { @Override @JsonFormat(pattern = "dd/MM/yyyy HH:mm", timezone = "Europe/Madrid") diff --git a/src/main/java/com/pablotj/ia/chat/boot/domain/port/ChatMessageStore.java b/src/main/java/com/pablotj/ia/chat/boot/domain/port/ChatMessageStore.java index 8b95e28..7553b2c 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/domain/port/ChatMessageStore.java +++ b/src/main/java/com/pablotj/ia/chat/boot/domain/port/ChatMessageStore.java @@ -1,9 +1,12 @@ package com.pablotj.ia.chat.boot.domain.port; +import com.pablotj.ia.chat.boot.domain.model.ChatIdentity; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; import java.util.List; public interface ChatMessageStore { - List getMessages(String sessionId); + List getChats(); + + List getMessages(String chatId); void saveMessages(String sessionId, List messages); } diff --git a/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageJpaRepository.java b/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageJpaRepository.java index 39d86af..2f88ef9 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageJpaRepository.java +++ b/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageJpaRepository.java @@ -1,10 +1,24 @@ package com.pablotj.ia.chat.boot.persistence; -import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface ChatMessageJpaRepository extends JpaRepository { - List findBySessionIdOrderByIdAsc(String sessionId); - void deleteBySessionId(String sessionId); + + @Query(value = """ + SELECT * + FROM ( + SELECT *, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY id ASC) AS rn + FROM chat_messages + ) sub + WHERE rn = 1 + ORDER BY id desc + """, nativeQuery = true) + List findFirstMessagePerSession(); + + List findBySessionIdOrderByIdAsc(String chatId); + + void deleteBySessionId(String chatId); } \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageMapper.java b/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageMapper.java index 3300a18..ede6726 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageMapper.java +++ b/src/main/java/com/pablotj/ia/chat/boot/persistence/ChatMessageMapper.java @@ -1,12 +1,17 @@ package com.pablotj.ia.chat.boot.persistence; +import com.pablotj.ia.chat.boot.domain.model.ChatIdentity; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; public class ChatMessageMapper { - public static ChatMessageEntity toEntity(String sessionId, ChatMessage message) { + private ChatMessageMapper() throws IllegalAccessException { + throw new IllegalAccessException("Private access to ChatMessageMapper"); + } + + public static ChatMessageEntity toEntity(String chatId, ChatMessage message) { ChatMessageEntity entity = new ChatMessageEntity(); - entity.setSessionId(sessionId); + entity.setSessionId(chatId); entity.setRole(message.role()); entity.setText(message.text()); entity.setDate(message.date()); @@ -14,6 +19,10 @@ public class ChatMessageMapper { } public static ChatMessage toDomain(ChatMessageEntity entity) { - return new ChatMessage(entity.getRole(), entity.getText(), entity.getDate()); + return new ChatMessage(entity.getSessionId(), entity.getRole(), entity.getText(), entity.getDate()); + } + + public static ChatIdentity toEntityId(ChatMessageEntity m) { + return new ChatIdentity(m.getSessionId(), m.getText().length() > 35 ? m.getText().substring(0, 35).concat("...") : m.getText()); } } \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/persistence/SqliteChatMessageStore.java b/src/main/java/com/pablotj/ia/chat/boot/persistence/SqliteChatMessageStore.java index 36a4cbc..6c6af7c 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/persistence/SqliteChatMessageStore.java +++ b/src/main/java/com/pablotj/ia/chat/boot/persistence/SqliteChatMessageStore.java @@ -1,9 +1,10 @@ package com.pablotj.ia.chat.boot.persistence; +import com.pablotj.ia.chat.boot.domain.model.ChatIdentity; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; import com.pablotj.ia.chat.boot.domain.port.ChatMessageStore; -import org.springframework.stereotype.Repository; import java.util.List; +import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @Repository @@ -16,8 +17,15 @@ public class SqliteChatMessageStore implements ChatMessageStore { } @Override - public List getMessages(String sessionId) { - return repository.findBySessionIdOrderByIdAsc(sessionId) + public List getChats() { + return repository.findFirstMessagePerSession().stream() + .map(ChatMessageMapper::toEntityId) + .toList(); + } + + @Override + public List getMessages(String chatId) { + return repository.findBySessionIdOrderByIdAsc(chatId) .stream() .map(ChatMessageMapper::toDomain) .toList(); @@ -25,10 +33,10 @@ public class SqliteChatMessageStore implements ChatMessageStore { @Override @Transactional - public void saveMessages(String sessionId, List messages) { - repository.deleteBySessionId(sessionId); + public void saveMessages(String chatId, List messages) { + repository.deleteBySessionId(chatId); List entities = messages.stream() - .map(m -> ChatMessageMapper.toEntity(sessionId, m)) + .map(m -> ChatMessageMapper.toEntity(chatId, m)) .toList(); repository.saveAll(entities); } diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 03da5c7..b7cbbe2 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -11,14 +11,74 @@ html, body { } body { + margin: 0; + padding: 0; + height: 100vh; + background-color: #121217; +} + +/* Layout general */ +.layout-container { + display: flex; + height: 100vh; + width: 100vw; + overflow: hidden; +} + +/* Menú lateral izquierdo */ +#sidebar { + width: 25%; + min-width: 200px; + background-color: #1b1d23; + color: #ffffff; + padding: 1.5rem 1rem; + box-sizing: border-box; + border-right: 1px solid #2c2e34; display: flex; flex-direction: column; - max-width: 600px; - margin: 0 auto; - height: 100vh; - padding: 2rem 1.25rem 1rem; +} + +#sidebar h2 { + font-size: 1.25rem; + margin-bottom: 1rem; + color: #5a90ff; +} + +#chat-list { + list-style: none; + padding: 0; + margin: 0; + overflow-y: auto; + flex-grow: 1; +} + +#chat-list li { + padding: 0.5rem 0.75rem; + cursor: pointer; + border-radius: 8px; + transition: background-color 0.2s ease; + font-size: 0.95rem; +} + +#chat-list li:hover { + background-color: #2c2f3a; +} + +#chat-list li.active { + background-color: #3451d1; + color: white; + font-weight: bold; +} + +/* Área principal del chat */ +#main-content { + width: 75%; box-sizing: border-box; + display: flex; + flex-direction: column; + padding: 2rem 1.25rem 1rem; background-color: #121217; + overflow: hidden; } /* Título */ @@ -50,6 +110,7 @@ h1 { #chat-log::-webkit-scrollbar { width: 6px; } + #chat-log::-webkit-scrollbar-thumb { background-color: #5a90ff55; border-radius: 3px; @@ -70,7 +131,6 @@ h1 { animation: slideFadeIn 0.3s forwards; } -/* Animación de entrada */ @keyframes slideFadeIn { to { opacity: 1; @@ -138,7 +198,7 @@ textarea:focus { } /* Botón */ -button { +button#send-btn { background-color: #5a90ff; border: none; border-radius: 14px; @@ -151,16 +211,16 @@ button { white-space: nowrap; } -button:hover:not(:disabled) { +button#send-btn:hover:not(:disabled) { background-color: #4076e0; } -button:active:not(:disabled) { +button#send-btn:active:not(:disabled) { transform: translateY(1px); background-color: #305dc0; } -button:disabled { +button#send-btn:disabled { opacity: 0.5; cursor: not-allowed; background-color: #3a4a6a; @@ -205,30 +265,33 @@ button:disabled { user-select: none; } +#new-chat-btn { + margin-bottom: 10px; + padding: 8px; + font-size: 14px; + cursor: pointer; + background-color: #f0f0f0; + border: 1px solid #ccc; + border-radius: 4px; +} + +#new-chat-btn:hover { + background-color: #e0e0e0; +} + /* Responsive */ -@media (max-width: 480px) { - body { - max-width: 100%; - padding: 1.5rem 1rem 1rem; +@media (max-width: 768px) { + .layout-container { + flex-direction: column; } - #chat-log { - padding: 1rem 1.25rem; - border-radius: 12px; + #sidebar { + width: 100%; + border-right: none; + border-bottom: 1px solid #2c2e34; } - form { - gap: 8px; - } - - textarea { - min-height: 3.5rem; - font-size: 0.95rem; - padding: 0.75rem 1rem; - } - - button { - padding: 0 1.25rem; - font-size: 0.95rem; + #main-content { + width: 100%; } } \ No newline at end of file diff --git a/src/main/resources/static/js/main.js b/src/main/resources/static/js/main.js index ccb3bfd..f39358d 100644 --- a/src/main/resources/static/js/main.js +++ b/src/main/resources/static/js/main.js @@ -1,4 +1,8 @@ -document.addEventListener("DOMContentLoaded", () => { +let chatId = ''; + +document.addEventListener("DOMContentLoaded", async () => { + const newChatBtn = document.getElementById("new-chat-btn"); + const chatList = document.getElementById("chat-list"); const form = document.getElementById('chat-form'); const promptInput = document.getElementById('prompt'); const chatLog = document.getElementById('chat-log'); @@ -6,21 +10,116 @@ document.addEventListener("DOMContentLoaded", () => { const spinner = document.getElementById('spinner'); const thinkingText = document.getElementById('thinking-text'); + await loadHistory(); + + async function loadHistory() { + try { + const response = await fetch("/api/v1/chats", { method: "GET" }); + const data = await response.json(); + + chatList.innerHTML = ""; + + data.forEach(chat => { + const li = document.createElement("li"); + li.textContent = chat.name; + li.setAttribute("data-chat-id", chat.id); + if (chat.id === chatId) { + li.classList.add("active"); + } + + li.addEventListener("click", async () => { + const selectedId = chat.id; + if (selectedId !== chatId) { + chatId = selectedId; + highlightActiveChat(li); + await loadMessages(chatId); + } + }); + + chatList.appendChild(li); + }); + + // Autoabrir primer chat si no hay chatId activo + if (!chatId && data.length > 0) { + chatId = data[0].id; + const firstLi = chatList.querySelector('li'); + if (firstLi) { + firstLi.classList.add("active"); + await loadMessages(chatId); + } + } + + } catch (error) { + console.error("Error cargando historial:", error); + } + } + + async function loadMessages(chatId) { + try { + const response = await fetch(`/api/v1/chats/${chatId}`); + const messages = await response.json(); + + chatLog.innerHTML = ""; + + messages.forEach(msg => { + appendMessage(msg.role, msg.text, msg.date); + }); + + } catch (error) { + appendMessage("bot", "❌ Error cargando mensajes del chat."); + console.error("Error al cargar mensajes:", error); + } + } + + function highlightActiveChat(selectedLi) { + const lis = chatList.querySelectorAll("li"); + lis.forEach(li => li.classList.remove("active")); + selectedLi.classList.add("active"); + } + function appendMessage(role, text, date) { const bubble = document.createElement('div'); bubble.className = `bubble ${role}`; bubble.textContent = text; chatLog.appendChild(bubble); + const timestamp = document.createElement('em'); timestamp.className = 'timestamp'; timestamp.textContent = date; bubble.appendChild(timestamp); + chatLog.scrollTop = chatLog.scrollHeight; } + async function createNewChat() { + try { + const response = await fetch("/api/v1/chats", { + method: "POST" + }); + chatId = await response.text(); + + await loadHistory(); + await loadMessages(chatId); + + const li = chatList.querySelector(`li[data-chat-id="${chatId}"]`); + if (li) highlightActiveChat(li); + + } catch (error) { + console.error("Error al crear nuevo chat:", error); + appendMessage("bot", "❌ Error creando nuevo chat."); + } + } + newChatBtn.addEventListener("click", async () => { + await createNewChat() + }); + form.addEventListener('submit', async function (e) { e.preventDefault(); + if (chatId === undefined || chatId === null || chatId === '') { + await createNewChat(); + } + const prompt = promptInput.value.trim(); if (!prompt) return; @@ -31,8 +130,8 @@ document.addEventListener("DOMContentLoaded", () => { thinkingText.style.display = 'block'; try { - const response = await fetch("/chat", { - method: "POST", + const response = await fetch(`/api/v1/chats/${chatId}`, { + method: "PUT", headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, @@ -40,7 +139,12 @@ document.addEventListener("DOMContentLoaded", () => { }); const data = await response.json(); + chatId = data.chatId; + appendMessage("bot", data.text, data.date); + + await loadHistory(); + } catch (error) { appendMessage("bot", "❌ Error procesando la respuesta."); console.error(error); @@ -53,7 +157,7 @@ document.addEventListener("DOMContentLoaded", () => { function formatDate(date) { const day = date.getDate().toString().padStart(2, '0'); - const month = (date.getMonth() + 1).toString().padStart(2, '0'); // Enero = 0 + const month = (date.getMonth() + 1).toString().padStart(2, '0'); const year = date.getFullYear(); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); diff --git a/src/main/resources/templates/chat.html b/src/main/resources/templates/chat.html index 4bf90c7..8124509 100644 --- a/src/main/resources/templates/chat.html +++ b/src/main/resources/templates/chat.html @@ -6,23 +6,43 @@ -

🤖 Chat IA Offline

+
+ + + + + +
+

🤖 Chat IA Offline

+ +
+
+ + +
+
+
+
Pensando...
+ +
+ + +
+
-
-
- - -
-
-
Pensando...
- -
- - -