diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java b/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java index a6ef1db1..8677865a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java @@ -1,9 +1,6 @@ package com.petshop.backend.dto.chat; -import jakarta.validation.constraints.NotBlank; - public class ConversationRequest { - @NotBlank(message = "Initial message is required") private String message; public ConversationRequest() { diff --git a/backend/src/main/java/com/petshop/backend/service/ChatService.java b/backend/src/main/java/com/petshop/backend/service/ChatService.java index 138b5006..620f6130 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -59,14 +59,20 @@ public class ChatService { conversation.setMode(Conversation.ConversationMode.AUTOMATED); conversation = conversationRepository.save(conversation); - Message message = new Message(); - message.setConversationId(conversation.getId()); - message.setSenderId(userId); - message.setContent(request.getMessage()); - message.setIsRead(false); - messageRepository.save(message); + User botUser = getBotUser(); + String firstName = user.getFirstName(); + String greeting = (firstName != null && !firstName.isBlank()) + ? "Hi " + firstName + "! I'm Leon's Pet Assistant. Ask me anything about pet care, adoption advice, or your pets." + : "Hi! I'm Leon's Pet Assistant. Ask me anything about pet care, adoption advice, or your pets."; - return ConversationResponse.fromEntity(conversation, request.getMessage(), userId); + Message greetingMsg = new Message(); + greetingMsg.setConversationId(conversation.getId()); + greetingMsg.setSenderId(botUser.getId()); + greetingMsg.setContent(greeting); + greetingMsg.setIsRead(false); + messageRepository.save(greetingMsg); + + return ConversationResponse.fromEntity(conversation, greeting, botUser.getId()); } public List getConversations(Long userId, User.Role role, boolean mine) { diff --git a/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java b/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java index 149bd4dc..d2e007dc 100644 --- a/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java +++ b/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java @@ -5,8 +5,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.petshop.backend.entity.Conversation; import com.petshop.backend.entity.Message; +import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.User; import com.petshop.backend.repository.MessageRepository; +import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.UserRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +41,7 @@ public class OpenRouterAiService { private final ChatRealtimeService chatRealtimeService; private final MessageRepository messageRepository; private final UserRepository userRepository; + private final PetRepository petRepository; private final ObjectMapper objectMapper; private final HttpClient httpClient; @@ -46,12 +49,14 @@ public class OpenRouterAiService { ChatService chatService, ChatRealtimeService chatRealtimeService, MessageRepository messageRepository, - UserRepository userRepository + UserRepository userRepository, + PetRepository petRepository ) { this.chatService = chatService; this.chatRealtimeService = chatRealtimeService; this.messageRepository = messageRepository; this.userRepository = userRepository; + this.petRepository = petRepository; this.objectMapper = JsonMapper.builder().findAndAddModules().build(); this.httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) @@ -117,10 +122,15 @@ public class OpenRouterAiService { return; } + User customer = userRepository.findById(conversation.getCustomerId()).orElse(null); + List customerPets = customer != null + ? petRepository.findAllByOwner_IdOrderByPetNameAsc(customer.getId()) + : List.of(); + List> messages = new ArrayList<>(); messages.add(Map.of( "role", "system", - "content", "You are a helpful pet shop assistant. Provide concise and friendly answers. Do not output markdown, just plain text." + "content", buildSystemPrompt(customer, customerPets) )); for (Message message : history) { @@ -177,6 +187,43 @@ public class OpenRouterAiService { } } + private String buildSystemPrompt(User customer, List pets) { + StringBuilder sb = new StringBuilder(); + sb.append("You are Leon's Pet Assistant, a helpful AI for Leon's Pet Store. "); + sb.append("Be concise, friendly, and focused on pet care, adoption, products, and appointments. "); + sb.append("Do not output markdown, just plain text.\n\n"); + + if (customer != null) { + sb.append("Customer profile:\n"); + sb.append("- Name: ").append(customer.getFirstName()).append(" ").append(customer.getLastName()).append("\n"); + if (customer.getLoyaltyPoints() != null) { + sb.append("- Loyalty points: ").append(customer.getLoyaltyPoints()).append("\n"); + } + if (customer.getPrimaryStore() != null && customer.getPrimaryStore().getStoreName() != null) { + sb.append("- Preferred store: ").append(customer.getPrimaryStore().getStoreName()).append("\n"); + } + sb.append("\n"); + } + + if (pets != null && !pets.isEmpty()) { + sb.append("Their registered pets:\n"); + for (Pet pet : pets) { + sb.append("- ").append(pet.getPetName()).append(" (").append(pet.getPetSpecies()); + if (pet.getPetBreed() != null && !pet.getPetBreed().isBlank()) { + sb.append(", ").append(pet.getPetBreed()); + } + if (pet.getPetAge() != null) { + sb.append(", ").append(pet.getPetAge()).append(" yr"); + } + sb.append(")\n"); + } + } else { + sb.append("They have no pets registered yet.\n"); + } + + return sb.toString(); + } + private String resolveRole(Message message, Long botUserId) { if (message.getSenderId() != null && message.getSenderId().equals(botUserId)) { return "assistant"; diff --git a/web/app/ai-chat/page.js b/web/app/ai-chat/page.js index 3b43abf5..2f35f51a 100644 --- a/web/app/ai-chat/page.js +++ b/web/app/ai-chat/page.js @@ -246,7 +246,7 @@ function AiChatPage() { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - body: JSON.stringify({ message: "Hello! I'd like to chat with the AI assistant." }), + body: JSON.stringify({}), }); if (res.ok) { const conv = await res.json(); @@ -406,7 +406,7 @@ function AiChatPage() { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - body: JSON.stringify({ message: "Hello! I'd like to chat with the AI assistant." }), + body: JSON.stringify({}), }); if (!res.ok) { const data = await res.json().catch(() => null); @@ -583,7 +583,7 @@ function AiChatPage() { Chat with a Real Person )} - {isEscalated && !isClosed && ( + {!isClosed && (