OpenRouter bot fixes
This commit is contained in:
@@ -12,6 +12,7 @@ import com.petshop.backend.repository.UserRepository;
|
|||||||
import com.petshop.backend.service.ChatAttachmentStorageService;
|
import com.petshop.backend.service.ChatAttachmentStorageService;
|
||||||
import com.petshop.backend.service.ChatRealtimeService;
|
import com.petshop.backend.service.ChatRealtimeService;
|
||||||
import com.petshop.backend.service.ChatService;
|
import com.petshop.backend.service.ChatService;
|
||||||
|
import com.petshop.backend.service.OpenRouterAiService;
|
||||||
import com.petshop.backend.util.AuthenticationHelper;
|
import com.petshop.backend.util.AuthenticationHelper;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@@ -32,15 +33,17 @@ public class ChatController {
|
|||||||
|
|
||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
private final ChatRealtimeService chatRealtimeService;
|
private final ChatRealtimeService chatRealtimeService;
|
||||||
|
private final OpenRouterAiService openRouterAiService;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final ChatAttachmentStorageService attachmentStorageService;
|
private final ChatAttachmentStorageService attachmentStorageService;
|
||||||
private final MessageRepository messageRepository;
|
private final MessageRepository messageRepository;
|
||||||
|
|
||||||
public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService,
|
public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService,
|
||||||
UserRepository userRepository, ChatAttachmentStorageService attachmentStorageService,
|
OpenRouterAiService openRouterAiService, UserRepository userRepository, ChatAttachmentStorageService attachmentStorageService,
|
||||||
MessageRepository messageRepository) {
|
MessageRepository messageRepository) {
|
||||||
this.chatService = chatService;
|
this.chatService = chatService;
|
||||||
this.chatRealtimeService = chatRealtimeService;
|
this.chatRealtimeService = chatRealtimeService;
|
||||||
|
this.openRouterAiService = openRouterAiService;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.attachmentStorageService = attachmentStorageService;
|
this.attachmentStorageService = attachmentStorageService;
|
||||||
this.messageRepository = messageRepository;
|
this.messageRepository = messageRepository;
|
||||||
@@ -88,6 +91,7 @@ public class ChatController {
|
|||||||
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
||||||
chatRealtimeService.publishMessage(id, message);
|
chatRealtimeService.publishMessage(id, message);
|
||||||
chatRealtimeService.publishConversationUpdate(id);
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
|
openRouterAiService.generateAndSendReply(id);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +105,7 @@ public class ChatController {
|
|||||||
MessageResponse message = chatService.sendMessageWithAttachment(id, user.getId(), user.getRole(), file, content);
|
MessageResponse message = chatService.sendMessageWithAttachment(id, user.getId(), user.getRole(), file, content);
|
||||||
chatRealtimeService.publishMessage(id, message);
|
chatRealtimeService.publishMessage(id, message);
|
||||||
chatRealtimeService.publishConversationUpdate(id);
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
|
openRouterAiService.generateAndSendReply(id);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.petshop.backend.security.JwtUtil;
|
|||||||
import com.petshop.backend.service.ChatRealtimeService;
|
import com.petshop.backend.service.ChatRealtimeService;
|
||||||
import com.petshop.backend.service.ChatService;
|
import com.petshop.backend.service.ChatService;
|
||||||
import com.petshop.backend.service.OpenRouterAiService;
|
import com.petshop.backend.service.OpenRouterAiService;
|
||||||
import com.petshop.backend.entity.Conversation;
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
||||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||||
@@ -58,13 +57,7 @@ public class ChatWebSocketController {
|
|||||||
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
||||||
chatRealtimeService.publishMessage(id, message);
|
chatRealtimeService.publishMessage(id, message);
|
||||||
chatRealtimeService.publishConversationUpdate(id);
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
|
openRouterAiService.generateAndSendReply(id);
|
||||||
if (user.getRole() == User.Role.CUSTOMER) {
|
|
||||||
Conversation conversation = chatService.getConversationEntity(id);
|
|
||||||
if (conversation.getMode() == Conversation.ConversationMode.AUTOMATED) {
|
|
||||||
openRouterAiService.generateAndSendReply(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageExceptionHandler({IllegalArgumentException.class, RuntimeException.class})
|
@MessageExceptionHandler({IllegalArgumentException.class, RuntimeException.class})
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public class MessageResponse {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private Long conversationId;
|
private Long conversationId;
|
||||||
private Long senderId;
|
private Long senderId;
|
||||||
|
private String senderRole;
|
||||||
|
private String senderDisplayName;
|
||||||
private String senderAvatarUrl;
|
private String senderAvatarUrl;
|
||||||
private String content;
|
private String content;
|
||||||
private LocalDateTime timestamp;
|
private LocalDateTime timestamp;
|
||||||
@@ -82,6 +84,22 @@ public class MessageResponse {
|
|||||||
this.senderId = senderId;
|
this.senderId = senderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSenderRole() {
|
||||||
|
return senderRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderRole(String senderRole) {
|
||||||
|
this.senderRole = senderRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSenderDisplayName() {
|
||||||
|
return senderDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderDisplayName(String senderDisplayName) {
|
||||||
|
this.senderDisplayName = senderDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getContent() {
|
public String getContent() {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class ChatService {
|
public class ChatService {
|
||||||
|
private static final String BOT_USERNAME = "ai.bot";
|
||||||
|
|
||||||
private final ConversationRepository conversationRepository;
|
private final ConversationRepository conversationRepository;
|
||||||
private final MessageRepository messageRepository;
|
private final MessageRepository messageRepository;
|
||||||
@@ -160,12 +161,13 @@ public class ChatService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public MessageResponse saveBotMessage(Long conversationId, String content) {
|
public MessageResponse saveBotMessage(Long conversationId, String content) {
|
||||||
Conversation conversation = conversationRepository.findById(conversationId)
|
conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
User botUser = getBotUser();
|
||||||
|
|
||||||
Message message = new Message();
|
Message message = new Message();
|
||||||
message.setConversationId(conversationId);
|
message.setConversationId(conversationId);
|
||||||
message.setSenderId(101L);
|
message.setSenderId(botUser.getId());
|
||||||
message.setContent(content);
|
message.setContent(content);
|
||||||
message.setIsRead(false);
|
message.setIsRead(false);
|
||||||
message = messageRepository.save(message);
|
message = messageRepository.save(message);
|
||||||
@@ -289,14 +291,60 @@ public class ChatService {
|
|||||||
|
|
||||||
private MessageResponse toMessageResponse(Message message) {
|
private MessageResponse toMessageResponse(Message message) {
|
||||||
MessageResponse response = MessageResponse.fromEntity(message);
|
MessageResponse response = MessageResponse.fromEntity(message);
|
||||||
userRepository.findById(message.getSenderId()).ifPresent(user -> {
|
userRepository.findById(message.getSenderId())
|
||||||
if (avatarStorageService.hasAvatar(user)) {
|
.ifPresentOrElse(
|
||||||
response.setSenderAvatarUrl(avatarStorageService.toOwnerAvatarUrl(user));
|
user -> populateSenderMetadata(response, user),
|
||||||
}
|
() -> {
|
||||||
});
|
response.setSenderRole("UNKNOWN");
|
||||||
|
response.setSenderDisplayName("Unknown");
|
||||||
|
}
|
||||||
|
);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void populateSenderMetadata(MessageResponse response, User user) {
|
||||||
|
response.setSenderRole(resolveSenderRole(user));
|
||||||
|
response.setSenderDisplayName(resolveSenderDisplayName(user));
|
||||||
|
if (avatarStorageService.hasAvatar(user)) {
|
||||||
|
response.setSenderAvatarUrl(avatarStorageService.toOwnerAvatarUrl(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSenderRole(User user) {
|
||||||
|
if (BOT_USERNAME.equals(user.getUsername())) {
|
||||||
|
return "BOT";
|
||||||
|
}
|
||||||
|
return user.getRole() != null ? user.getRole().name() : "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSenderDisplayName(User user) {
|
||||||
|
if (BOT_USERNAME.equals(user.getUsername())) {
|
||||||
|
return "AI Bot";
|
||||||
|
}
|
||||||
|
if (user.getFullName() != null && !user.getFullName().isBlank()) {
|
||||||
|
return user.getFullName();
|
||||||
|
}
|
||||||
|
StringBuilder displayName = new StringBuilder();
|
||||||
|
if (user.getFirstName() != null && !user.getFirstName().isBlank()) {
|
||||||
|
displayName.append(user.getFirstName().trim());
|
||||||
|
}
|
||||||
|
if (user.getLastName() != null && !user.getLastName().isBlank()) {
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
displayName.append(' ');
|
||||||
|
}
|
||||||
|
displayName.append(user.getLastName().trim());
|
||||||
|
}
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName.toString();
|
||||||
|
}
|
||||||
|
return user.getUsername() != null && !user.getUsername().isBlank() ? user.getUsername() : "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private User getBotUser() {
|
||||||
|
return userRepository.findByUsername(BOT_USERNAME)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Bot user not found"));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasConversationAccess(Long conversationId, Long userId, User.Role role) {
|
public boolean hasConversationAccess(Long conversationId, Long userId, User.Role role) {
|
||||||
Conversation conversation = conversationRepository.findById(conversationId)
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|||||||
@@ -1,90 +1,167 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.petshop.backend.entity.Conversation;
|
||||||
import com.petshop.backend.entity.Message;
|
import com.petshop.backend.entity.Message;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.repository.MessageRepository;
|
import com.petshop.backend.repository.MessageRepository;
|
||||||
import org.springframework.stereotype.Service;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class OpenRouterAiService {
|
public class OpenRouterAiService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(OpenRouterAiService.class);
|
||||||
|
private static final String BOT_USERNAME = "ai.bot";
|
||||||
|
|
||||||
@Value("${openrouter.api-key}")
|
@Value("${openrouter.api-key:}")
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
|
||||||
@Value("${openrouter.model}")
|
@Value("${openrouter.model:mistralai/mistral-7b-instruct:free}")
|
||||||
private String model;
|
private String model;
|
||||||
|
|
||||||
private final String openRouterUrl = "https://openrouter.ai/api/v1/chat/completions";
|
private final String openRouterUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||||
|
|
||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
private final ChatRealtimeService chatRealtimeService;
|
private final ChatRealtimeService chatRealtimeService;
|
||||||
private final MessageRepository messageRepository;
|
private final MessageRepository messageRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
public OpenRouterAiService(ChatService chatService, ChatRealtimeService chatRealtimeService, MessageRepository messageRepository, ObjectMapper objectMapper) {
|
public OpenRouterAiService(
|
||||||
|
ChatService chatService,
|
||||||
|
ChatRealtimeService chatRealtimeService,
|
||||||
|
MessageRepository messageRepository,
|
||||||
|
UserRepository userRepository,
|
||||||
|
ObjectMapper objectMapper
|
||||||
|
) {
|
||||||
this.chatService = chatService;
|
this.chatService = chatService;
|
||||||
this.chatRealtimeService = chatRealtimeService;
|
this.chatRealtimeService = chatRealtimeService;
|
||||||
this.messageRepository = messageRepository;
|
this.messageRepository = messageRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.httpClient = HttpClient.newHttpClient();
|
this.httpClient = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(10))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateAndSendReply(Long conversationId) {
|
public void generateAndSendReply(Long conversationId) {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> generateReply(conversationId));
|
||||||
try {
|
}
|
||||||
List<Message> history = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
|
||||||
|
|
||||||
List<Map<String, String>> messages = history.stream().map(msg -> {
|
|
||||||
String role = (msg.getSenderId() != null && msg.getSenderId() == 101L) ? "assistant" : "user";
|
|
||||||
return Map.of("role", role, "content", msg.getContent() != null ? msg.getContent() : "");
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
|
|
||||||
messages.add(0, Map.of("role", "system", "content", "You are a helpful pet shop assistant. Provide concise and friendly answers. Do not output markdown, just plain text."));
|
|
||||||
|
|
||||||
Map<String, Object> requestBody = Map.of(
|
private void generateReply(Long conversationId) {
|
||||||
"model", model,
|
try {
|
||||||
"messages", messages
|
if (apiKey == null || apiKey.isBlank()) {
|
||||||
);
|
log.debug("Skipping OpenRouter reply for conversation {} because no API key is configured", conversationId);
|
||||||
|
return;
|
||||||
String requestJson = objectMapper.writeValueAsString(requestBody);
|
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(openRouterUrl))
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Authorization", "Bearer " + apiKey)
|
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(requestJson))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
||||||
|
|
||||||
if (response.statusCode() == 200) {
|
|
||||||
Map<String, Object> responseMap = objectMapper.readValue(response.body(), Map.class);
|
|
||||||
List<Map<String, Object>> choices = (List<Map<String, Object>>) responseMap.get("choices");
|
|
||||||
if (choices != null && !choices.isEmpty()) {
|
|
||||||
Map<String, Object> choice = choices.get(0);
|
|
||||||
Map<String, String> message = (Map<String, String>) choice.get("message");
|
|
||||||
String replyContent = message.get("content");
|
|
||||||
|
|
||||||
var messageResponse = chatService.saveBotMessage(conversationId, replyContent);
|
|
||||||
chatRealtimeService.publishMessage(conversationId, messageResponse);
|
|
||||||
chatRealtimeService.publishConversationUpdate(conversationId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println("OpenRouter API Error: " + response.statusCode() + " " + response.body());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
});
|
if (model == null || model.isBlank()) {
|
||||||
|
log.warn("Skipping OpenRouter reply for conversation {} because no model is configured", conversationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Conversation conversation = chatService.getConversationEntity(conversationId);
|
||||||
|
if (conversation.getStatus() == Conversation.ConversationStatus.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (conversation.getHumanRequestedAt() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (conversation.getMode() != Conversation.ConversationMode.AUTOMATED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User botUser = userRepository.findByUsername(BOT_USERNAME)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Bot user not found"));
|
||||||
|
|
||||||
|
List<Message> history = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
||||||
|
if (history.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message lastMessage = history.get(history.size() - 1);
|
||||||
|
String lastContent = normalizeContent(lastMessage.getContent());
|
||||||
|
if (lastContent.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lastMessage.getSenderId() != null && lastMessage.getSenderId().equals(botUser.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User lastSender = userRepository.findById(lastMessage.getSenderId()).orElse(null);
|
||||||
|
if (lastSender == null || lastSender.getRole() != User.Role.CUSTOMER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, String>> 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."
|
||||||
|
));
|
||||||
|
|
||||||
|
for (Message message : history) {
|
||||||
|
messages.add(Map.of(
|
||||||
|
"role", resolveRole(message, botUser.getId()),
|
||||||
|
"content", normalizeContent(message.getContent())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> requestBody = Map.of(
|
||||||
|
"model", model,
|
||||||
|
"messages", messages
|
||||||
|
);
|
||||||
|
|
||||||
|
String requestJson = objectMapper.writeValueAsString(requestBody);
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(openRouterUrl))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.timeout(Duration.ofSeconds(30))
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(requestJson))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
log.warn("OpenRouter API returned {} for conversation {}", response.statusCode(), conversationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode responseNode = objectMapper.readTree(response.body());
|
||||||
|
String replyContent = responseNode.path("choices").path(0).path("message").path("content").asText("");
|
||||||
|
replyContent = normalizeContent(replyContent);
|
||||||
|
if (replyContent.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var messageResponse = chatService.saveBotMessage(conversationId, replyContent);
|
||||||
|
chatRealtimeService.publishMessage(conversationId, messageResponse);
|
||||||
|
chatRealtimeService.publishConversationUpdate(conversationId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Failed to generate OpenRouter reply for conversation {}", conversationId, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveRole(Message message, Long botUserId) {
|
||||||
|
if (message.getSenderId() != null && message.getSenderId().equals(botUserId)) {
|
||||||
|
return "assistant";
|
||||||
|
}
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeContent(String content) {
|
||||||
|
return content == null ? "" : content.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,5 +72,5 @@ logging:
|
|||||||
deserialization:
|
deserialization:
|
||||||
fail-on-unknown-properties: false
|
fail-on-unknown-properties: false
|
||||||
openrouter:
|
openrouter:
|
||||||
api-key: ${OPENROUTER_API_KEY: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}
|
api-key: ${OPENROUTER_API_KEY:}
|
||||||
model: ${OPENROUTER_MODEL:mistralai/mistral-7b-instruct:free}
|
model: ${OPENROUTER_MODEL:mistralai/mistral-7b-instruct:free}
|
||||||
|
|||||||
@@ -155,8 +155,7 @@ INSERT INTO users (id, username, password, email, firstName, lastName, fullName,
|
|||||||
(97, 'alex.murray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.murray@gmail.com', 'Alex', 'Murray', 'Alex Murray', '403-730-0097', 'https://images.petshop.local/users/097.webp', 'CUSTOMER', 'CUSTOMER', NULL, 4, 1, 0),
|
(97, 'alex.murray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.murray@gmail.com', 'Alex', 'Murray', 'Alex Murray', '403-730-0097', 'https://images.petshop.local/users/097.webp', 'CUSTOMER', 'CUSTOMER', NULL, 4, 1, 0),
|
||||||
(98, 'alex.freeman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.freeman@gmail.com', 'Alex', 'Freeman', 'Alex Freeman', '403-730-0098', 'https://images.petshop.local/users/098.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0),
|
(98, 'alex.freeman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.freeman@gmail.com', 'Alex', 'Freeman', 'Alex Freeman', '403-730-0098', 'https://images.petshop.local/users/098.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0),
|
||||||
(99, 'alex.wells', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wells@gmail.com', 'Alex', 'Wells', 'Alex Wells', '403-730-0099', 'https://images.petshop.local/users/099.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0),
|
(99, 'alex.wells', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wells@gmail.com', 'Alex', 'Wells', 'Alex Wells', '403-730-0099', 'https://images.petshop.local/users/099.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0),
|
||||||
(100, 'alex.webb', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.webb@gmail.com', 'Alex', 'Webb', 'Alex Webb', '403-730-0100', 'https://images.petshop.local/users/100.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0),
|
(100, 'alex.webb', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.webb@gmail.com', 'Alex', 'Webb', 'Alex Webb', '403-730-0100', 'https://images.petshop.local/users/100.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0);
|
||||||
(101, 'ai.bot', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'bot@petshop.com', 'AI', 'Bot', 'AI Bot', '000-000-0000', 'https://images.petshop.local/users/bot.webp', 'STAFF', 'CUSTOMER_SERVICE', NULL, 0, 1, 0);
|
|
||||||
|
|
||||||
INSERT INTO supplier (supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) VALUES
|
INSERT INTO supplier (supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) VALUES
|
||||||
(1, 'PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '403-601-1001'),
|
(1, 'PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '403-601-1001'),
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
INSERT INTO users (username, password, email, firstName, lastName, fullName, phone, avatarUrl, role, staffRole, primaryStoreId, loyaltyPoints, active, tokenVersion)
|
||||||
|
SELECT 'ai.bot',
|
||||||
|
'$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq',
|
||||||
|
'bot@petshop.com',
|
||||||
|
'AI',
|
||||||
|
'Bot',
|
||||||
|
'AI Bot',
|
||||||
|
'000-000-0000',
|
||||||
|
'https://images.petshop.local/users/bot.webp',
|
||||||
|
'STAFF',
|
||||||
|
'CUSTOMER_SERVICE',
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE username = 'ai.bot'
|
||||||
|
);
|
||||||
@@ -6,6 +6,8 @@ public class MessageResponse {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private Long conversationId;
|
private Long conversationId;
|
||||||
private Long senderId;
|
private Long senderId;
|
||||||
|
private String senderRole;
|
||||||
|
private String senderDisplayName;
|
||||||
private String senderAvatarUrl;
|
private String senderAvatarUrl;
|
||||||
private String content;
|
private String content;
|
||||||
private LocalDateTime timestamp;
|
private LocalDateTime timestamp;
|
||||||
@@ -38,6 +40,22 @@ public class MessageResponse {
|
|||||||
this.senderId = senderId;
|
this.senderId = senderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSenderRole() {
|
||||||
|
return senderRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderRole(String senderRole) {
|
||||||
|
this.senderRole = senderRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSenderDisplayName() {
|
||||||
|
return senderDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderDisplayName(String senderDisplayName) {
|
||||||
|
this.senderDisplayName = senderDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSenderAvatarUrl() {
|
public String getSenderAvatarUrl() {
|
||||||
return senderAvatarUrl;
|
return senderAvatarUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,11 @@ public class ChatController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String resolveAuthorLabel(MessageResponse message) {
|
private String resolveAuthorLabel(MessageResponse message) {
|
||||||
|
if ("BOT".equalsIgnoreCase(message.getSenderRole())) {
|
||||||
|
return message.getSenderDisplayName() != null && !message.getSenderDisplayName().isBlank()
|
||||||
|
? message.getSenderDisplayName()
|
||||||
|
: "AI Bot";
|
||||||
|
}
|
||||||
Long currentUserId = UserSession.getInstance().getUserId();
|
Long currentUserId = UserSession.getInstance().getUserId();
|
||||||
if (message.getSenderId() != null && message.getSenderId().equals(currentUserId)) {
|
if (message.getSenderId() != null && message.getSenderId().equals(currentUserId)) {
|
||||||
return "You";
|
return "You";
|
||||||
|
|||||||
Reference in New Issue
Block a user