ai greets first with full context

This commit is contained in:
2026-04-16 00:49:58 -06:00
parent 4d6166a882
commit eaa519dd69
4 changed files with 65 additions and 15 deletions

View File

@@ -1,9 +1,6 @@
package com.petshop.backend.dto.chat; package com.petshop.backend.dto.chat;
import jakarta.validation.constraints.NotBlank;
public class ConversationRequest { public class ConversationRequest {
@NotBlank(message = "Initial message is required")
private String message; private String message;
public ConversationRequest() { public ConversationRequest() {

View File

@@ -59,14 +59,20 @@ public class ChatService {
conversation.setMode(Conversation.ConversationMode.AUTOMATED); conversation.setMode(Conversation.ConversationMode.AUTOMATED);
conversation = conversationRepository.save(conversation); conversation = conversationRepository.save(conversation);
Message message = new Message(); User botUser = getBotUser();
message.setConversationId(conversation.getId()); String firstName = user.getFirstName();
message.setSenderId(userId); String greeting = (firstName != null && !firstName.isBlank())
message.setContent(request.getMessage()); ? "Hi " + firstName + "! I'm Leon's Pet Assistant. Ask me anything about pet care, adoption advice, or your pets."
message.setIsRead(false); : "Hi! I'm Leon's Pet Assistant. Ask me anything about pet care, adoption advice, or your pets.";
messageRepository.save(message);
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<ConversationResponse> getConversations(Long userId, User.Role role, boolean mine) { public List<ConversationResponse> getConversations(Long userId, User.Role role, boolean mine) {

View File

@@ -5,8 +5,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.petshop.backend.entity.Conversation; import com.petshop.backend.entity.Conversation;
import com.petshop.backend.entity.Message; import com.petshop.backend.entity.Message;
import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.User; import com.petshop.backend.entity.User;
import com.petshop.backend.repository.MessageRepository; import com.petshop.backend.repository.MessageRepository;
import com.petshop.backend.repository.PetRepository;
import com.petshop.backend.repository.UserRepository; import com.petshop.backend.repository.UserRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -39,6 +41,7 @@ public class OpenRouterAiService {
private final ChatRealtimeService chatRealtimeService; private final ChatRealtimeService chatRealtimeService;
private final MessageRepository messageRepository; private final MessageRepository messageRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final PetRepository petRepository;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final HttpClient httpClient; private final HttpClient httpClient;
@@ -46,12 +49,14 @@ public class OpenRouterAiService {
ChatService chatService, ChatService chatService,
ChatRealtimeService chatRealtimeService, ChatRealtimeService chatRealtimeService,
MessageRepository messageRepository, MessageRepository messageRepository,
UserRepository userRepository UserRepository userRepository,
PetRepository petRepository
) { ) {
this.chatService = chatService; this.chatService = chatService;
this.chatRealtimeService = chatRealtimeService; this.chatRealtimeService = chatRealtimeService;
this.messageRepository = messageRepository; this.messageRepository = messageRepository;
this.userRepository = userRepository; this.userRepository = userRepository;
this.petRepository = petRepository;
this.objectMapper = JsonMapper.builder().findAndAddModules().build(); this.objectMapper = JsonMapper.builder().findAndAddModules().build();
this.httpClient = HttpClient.newBuilder() this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10)) .connectTimeout(Duration.ofSeconds(10))
@@ -117,10 +122,15 @@ public class OpenRouterAiService {
return; return;
} }
User customer = userRepository.findById(conversation.getCustomerId()).orElse(null);
List<Pet> customerPets = customer != null
? petRepository.findAllByOwner_IdOrderByPetNameAsc(customer.getId())
: List.of();
List<Map<String, String>> messages = new ArrayList<>(); List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of( messages.add(Map.of(
"role", "system", "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) { for (Message message : history) {
@@ -177,6 +187,43 @@ public class OpenRouterAiService {
} }
} }
private String buildSystemPrompt(User customer, List<Pet> 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) { private String resolveRole(Message message, Long botUserId) {
if (message.getSenderId() != null && message.getSenderId().equals(botUserId)) { if (message.getSenderId() != null && message.getSenderId().equals(botUserId)) {
return "assistant"; return "assistant";

View File

@@ -246,7 +246,7 @@ function AiChatPage() {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
body: JSON.stringify({ message: "Hello! I'd like to chat with the AI assistant." }), body: JSON.stringify({}),
}); });
if (res.ok) { if (res.ok) {
const conv = await res.json(); const conv = await res.json();
@@ -406,7 +406,7 @@ function AiChatPage() {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
body: JSON.stringify({ message: "Hello! I'd like to chat with the AI assistant." }), body: JSON.stringify({}),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json().catch(() => null); const data = await res.json().catch(() => null);
@@ -583,7 +583,7 @@ function AiChatPage() {
Chat with a Real Person Chat with a Real Person
</button> </button>
)} )}
{isEscalated && !isClosed && ( {!isClosed && (
<button style={s.closeConvBtn} onClick={handleCloseConversation} title="Close this conversation"> <button style={s.closeConvBtn} onClick={handleCloseConversation} title="Close this conversation">
Close Chat Close Chat
</button> </button>