Initial Python rewrite
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationEntryDTO;
|
||||
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
|
||||
import com.example.dto.dialogflow.conversation.MessageType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ConversationEntryMapper {
|
||||
|
||||
public ConversationMessageDTO toConversationMessageDTO(ConversationEntryDTO entry) {
|
||||
MessageType type = switch (entry.entity()) {
|
||||
case USUARIO -> MessageType.USER;
|
||||
case AGENTE -> MessageType.AGENT;
|
||||
case SISTEMA -> MessageType.SYSTEM;
|
||||
case LLM -> MessageType.LLM;
|
||||
};
|
||||
|
||||
return new ConversationMessageDTO(
|
||||
type,
|
||||
entry.timestamp(),
|
||||
entry.text(),
|
||||
entry.parameters(),
|
||||
entry.canal()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.conversation;
|
||||
import com.google.cloud.Timestamp;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
|
||||
import com.example.dto.dialogflow.conversation.MessageType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class ConversationMessageMapper {
|
||||
|
||||
public Map<String, Object> toMap(ConversationMessageDTO message) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("entidad", message.type().name());
|
||||
map.put("tiempo", message.timestamp());
|
||||
map.put("mensaje", message.text());
|
||||
if (message.parameters() != null) {
|
||||
map.put("parametros", message.parameters());
|
||||
}
|
||||
if (message.canal() != null) {
|
||||
map.put("canal", message.canal());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public ConversationMessageDTO fromMap(Map<String, Object> map) {
|
||||
Object timeObject = map.get("tiempo");
|
||||
Instant timestamp = null;
|
||||
if (timeObject instanceof Timestamp) {
|
||||
timestamp = ((Timestamp) timeObject).toDate().toInstant();
|
||||
} else if (timeObject instanceof Instant) {
|
||||
timestamp = (Instant) timeObject;
|
||||
}
|
||||
return new ConversationMessageDTO(
|
||||
MessageType.valueOf((String) map.get("entidad")),
|
||||
timestamp,
|
||||
(String) map.get("mensaje"),
|
||||
(Map<String, Object>) map.get("parametros"),
|
||||
(String) map.get("canal")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryInputDTO;
|
||||
import com.example.util.ProtobufUtil;
|
||||
import com.google.cloud.dialogflow.cx.v3.EventInput;
|
||||
import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest;
|
||||
|
||||
import com.google.cloud.dialogflow.cx.v3.QueryInput;
|
||||
import com.google.cloud.dialogflow.cx.v3.QueryParameters;
|
||||
import com.google.cloud.dialogflow.cx.v3.TextInput;
|
||||
import com.google.protobuf.Struct;
|
||||
import com.google.protobuf.Value;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring component responsible for mapping a custom `DetectIntentRequestDTO`
|
||||
* into a Dialogflow CX `DetectIntentRequest.Builder`. It handles the conversion
|
||||
* of user text or event inputs and the serialization of custom session parameters,
|
||||
* ensuring the data is in the correct Protobuf format for API communication.
|
||||
*/
|
||||
@Component
|
||||
public class DialogflowRequestMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DialogflowRequestMapper.class);
|
||||
|
||||
@org.springframework.beans.factory.annotation.Value("${dialogflow.default-language-code:es}")
|
||||
String defaultLanguageCode;
|
||||
|
||||
public DetectIntentRequest.Builder mapToDetectIntentRequestBuilder(DetectIntentRequestDTO requestDto) {
|
||||
Objects.requireNonNull(requestDto, "DetectIntentRequestDTO cannot be null for mapping.");
|
||||
|
||||
logger.debug(
|
||||
"Building partial Dialogflow CX DetectIntentRequest Protobuf Builder from DTO (only QueryInput and QueryParams).");
|
||||
QueryInput.Builder queryInputBuilder = QueryInput.newBuilder();
|
||||
QueryInputDTO queryInputDTO = requestDto.queryInput();
|
||||
|
||||
String languageCodeToSet = (queryInputDTO.languageCode() != null
|
||||
&& !queryInputDTO.languageCode().trim().isEmpty())
|
||||
? queryInputDTO.languageCode()
|
||||
: defaultLanguageCode;
|
||||
queryInputBuilder.setLanguageCode(languageCodeToSet);
|
||||
logger.debug("Setting languageCode for QueryInput to: {}", languageCodeToSet);
|
||||
|
||||
if (queryInputDTO.text() != null && queryInputDTO.text().text() != null
|
||||
&& !queryInputDTO.text().text().trim().isEmpty()) {
|
||||
queryInputBuilder.setText(TextInput.newBuilder()
|
||||
.setText(queryInputDTO.text().text())
|
||||
.build());
|
||||
logger.debug("Mapped text input for QueryInput: '{}'", queryInputDTO.text().text());
|
||||
|
||||
} else if (queryInputDTO.event() != null && queryInputDTO.event().event() != null
|
||||
&& !queryInputDTO.event().event().trim().isEmpty()) {
|
||||
queryInputBuilder.setEvent(EventInput.newBuilder()
|
||||
.setEvent(queryInputDTO.event().event())
|
||||
.build());
|
||||
logger.debug("Mapped event input for QueryInput: '{}'", queryInputDTO.event().event());
|
||||
|
||||
} else {
|
||||
logger.error("Dialogflow query input (either text or event) is required and must not be empty.");
|
||||
throw new IllegalArgumentException("Dialogflow query input (either text or event) is required.");
|
||||
}
|
||||
|
||||
QueryParameters.Builder queryParametersBuilder = QueryParameters.newBuilder();
|
||||
Struct.Builder paramsStructBuilder = Struct.newBuilder();
|
||||
|
||||
if (requestDto.queryParams() != null && requestDto.queryParams().parameters() != null) {
|
||||
for (Map.Entry<String, Object> entry : requestDto.queryParams().parameters().entrySet()) {
|
||||
Value protobufValue = ProtobufUtil.convertJavaObjectToProtobufValue(entry.getValue());
|
||||
paramsStructBuilder.putFields(entry.getKey(), protobufValue);
|
||||
logger.debug("Added session parameter from DTO queryParams: Key='{}', Value='{}'",
|
||||
entry.getKey(),entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsStructBuilder.getFieldsCount() > 0) {
|
||||
queryParametersBuilder.setParameters(paramsStructBuilder.build());
|
||||
logger.debug("All custom session parameters added to Protobuf request builder.");
|
||||
} else {
|
||||
logger.debug("No custom session parameters to add to Protobuf request.");
|
||||
}
|
||||
|
||||
DetectIntentRequest.Builder detectIntentRequestBuilder = DetectIntentRequest.newBuilder()
|
||||
.setQueryInput(queryInputBuilder.build());
|
||||
|
||||
if (queryParametersBuilder.hasParameters()) {
|
||||
detectIntentRequestBuilder.setQueryParams(queryParametersBuilder.build());
|
||||
}
|
||||
|
||||
logger.debug("Finished building partial DetectIntentRequest Protobuf Builder.");
|
||||
return detectIntentRequestBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.conversation;
|
||||
|
||||
import com.google.cloud.dialogflow.cx.v3.QueryResult;
|
||||
import com.google.cloud.dialogflow.cx.v3.ResponseMessage;
|
||||
import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse;
|
||||
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryResultDTO;
|
||||
import com.example.util.ProtobufUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring component responsible for mapping a Dialogflow CX API response
|
||||
* (`DetectIntentResponse`) to a simplified custom DTO (`DetectIntentResponseDTO`).
|
||||
* It extracts and consolidates the fulfillment text, and converts Protobuf
|
||||
* session parameters into standard Java objects, providing a clean and
|
||||
* decoupled interface for consuming Dialogflow results.
|
||||
*/
|
||||
@Component
|
||||
public class DialogflowResponseMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DialogflowResponseMapper.class);
|
||||
|
||||
public DetectIntentResponseDTO mapFromDialogflowResponse(DetectIntentResponse response, String sessionId) {
|
||||
|
||||
logger.info("Starting mapping of Dialogflow DetectIntentResponse for session: {}", sessionId);
|
||||
|
||||
String responseId = response.getResponseId();
|
||||
QueryResult dfQueryResult = response.getQueryResult();
|
||||
logger.debug("Extracted QueryResult object for session: {}", sessionId);
|
||||
|
||||
StringBuilder responseTextBuilder = new StringBuilder();
|
||||
if (dfQueryResult.getResponseMessagesList().isEmpty()) {
|
||||
logger.debug("No response messages found in QueryResult for session: {}", sessionId);
|
||||
}
|
||||
|
||||
for (ResponseMessage message : dfQueryResult.getResponseMessagesList()) {
|
||||
if (message.hasText()) {
|
||||
logger.debug("Processing text response message for session: {}", sessionId);
|
||||
for (String text : message.getText().getTextList()) {
|
||||
if (responseTextBuilder.length() > 0) {
|
||||
responseTextBuilder.append(" ");
|
||||
}
|
||||
responseTextBuilder.append(text);
|
||||
logger.debug("Appended text segment: '{}' to fulfillment text for session: {}", text, sessionId);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Skipping non-text response message type: {} for session: {}", message.getMessageCase(), sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
String responseText = responseTextBuilder.toString().trim();
|
||||
|
||||
Map<String, Object> parameters = new LinkedHashMap<>(); // Inicializamos vacío para evitar NPEs después
|
||||
|
||||
if (dfQueryResult.hasParameters()) {
|
||||
// Usamos un forEach en lugar de Collectors.toMap para tener control total sobre nulos
|
||||
dfQueryResult.getParameters().getFieldsMap().forEach((key, value) -> {
|
||||
try {
|
||||
Object convertedValue = ProtobufUtil.convertProtobufValueToJavaObject(value);
|
||||
|
||||
// Si el valor convertido es nulo, decidimos qué hacer.
|
||||
// Lo mejor es poner un String vacío o ignorarlo para que no explote tu lógica.
|
||||
if (convertedValue != null) {
|
||||
parameters.put(key, convertedValue);
|
||||
} else {
|
||||
logger.warn("El parámetro '{}' devolvió un valor nulo al convertir. Se ignorará.", key);
|
||||
// Opcional: parameters.put(key, "");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error convirtiendo el parámetro '{}' de Protobuf a Java: {}", key, e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug("Extracted parameters: {} for session: {}", parameters, sessionId);
|
||||
} else {
|
||||
logger.debug("No parameters found in QueryResult for session: {}. Using empty map.", sessionId);
|
||||
}
|
||||
|
||||
QueryResultDTO ourQueryResult = new QueryResultDTO(responseText, parameters);
|
||||
logger.debug("Internal QueryResult DTO created for session: {}. Details: {}", sessionId, ourQueryResult);
|
||||
|
||||
DetectIntentResponseDTO finalResponse = new DetectIntentResponseDTO(responseId, ourQueryResult);
|
||||
logger.info("Finished mapping DialogflowDetectIntentResponse for session: {}. Full response ID: {}", sessionId, responseId);
|
||||
return finalResponse;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.ExternalConvRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryInputDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryParamsDTO;
|
||||
import com.example.dto.dialogflow.conversation.TextInputDTO;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring component responsible for mapping a simplified, external API request
|
||||
* into a structured `DetectIntentRequestDTO` for Dialogflow. It processes
|
||||
* user messages and relevant context, such as phone numbers and channel information,
|
||||
* and populates the `QueryInputDTO` and `QueryParamsDTO` fields required for
|
||||
* a Dialogflow API call.
|
||||
*/
|
||||
@Component
|
||||
public class ExternalConvRequestMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExternalConvRequestMapper.class);
|
||||
private static final String DEFAULT_LANGUAGE_CODE = "es";
|
||||
|
||||
public DetectIntentRequestDTO mapExternalRequestToDetectIntentRequest(ExternalConvRequestDTO externalRequest) {
|
||||
Objects.requireNonNull(externalRequest, "ExternalRequestDTO cannot be null for mapping.");
|
||||
|
||||
if (externalRequest.message() == null || externalRequest.message().isBlank()) {
|
||||
throw new IllegalArgumentException("External request 'mensaje' (message) is required.");
|
||||
}
|
||||
TextInputDTO textInput = new TextInputDTO(externalRequest.message());
|
||||
QueryInputDTO queryInputDTO = new QueryInputDTO(textInput,null,DEFAULT_LANGUAGE_CODE);
|
||||
|
||||
// 2. Map ALL relevant external fields into QueryParamsDTO.parameters
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
String primaryPhoneNumber = null;
|
||||
if (externalRequest.user() != null && externalRequest.user().telefono() != null
|
||||
&& !externalRequest.user().telefono().isBlank()) {
|
||||
primaryPhoneNumber = externalRequest.user().telefono();
|
||||
parameters.put("telefono", primaryPhoneNumber);
|
||||
}
|
||||
|
||||
if (primaryPhoneNumber == null || primaryPhoneNumber.isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Phone number is required in the 'usuario' field for conversation management.");
|
||||
}
|
||||
|
||||
String resolvedUserId = null;
|
||||
// Derive from phone number if not provided by 'userId' parameter
|
||||
resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", "");
|
||||
parameters.put("usuario_id", resolvedUserId); // Ensure derived ID is also in params
|
||||
logger.warn("User ID not provided in external request. Using derived ID from phone number: {}", resolvedUserId);
|
||||
|
||||
|
||||
if (externalRequest.channel() != null && !externalRequest.channel().isBlank()) {
|
||||
parameters.put("canal", externalRequest.channel());
|
||||
logger.debug("Mapped 'canal' from external request: {}", externalRequest.channel());
|
||||
}
|
||||
|
||||
if (externalRequest.user() != null && externalRequest.user().nickname() != null
|
||||
&& !externalRequest.user().nickname().isBlank()) {
|
||||
parameters.put("nickname", externalRequest.user().nickname());
|
||||
logger.debug("Mapped 'nickname' from external request: {}", externalRequest.user().nickname());
|
||||
}
|
||||
if (externalRequest.tipo() != null) {
|
||||
parameters.put("tipo", externalRequest.tipo());
|
||||
logger.debug("Mapped 'tipo' from external request: {}", externalRequest.tipo());
|
||||
}
|
||||
|
||||
QueryParamsDTO queryParamsDTO = new QueryParamsDTO(parameters);
|
||||
|
||||
// 3. Construct the final DetectIntentRequestDTO
|
||||
return new DetectIntentRequestDTO(queryInputDTO, queryParamsDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
|
||||
import com.google.cloud.Timestamp;
|
||||
import com.google.cloud.firestore.DocumentSnapshot;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class FirestoreConversationMapper {
|
||||
|
||||
public ConversationSessionDTO mapFirestoreDocumentToConversationSessionDTO(DocumentSnapshot document) {
|
||||
if (document == null || !document.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Timestamp createdAtTimestamp = document.getTimestamp("fechaCreacion");
|
||||
Timestamp lastModifiedTimestamp = document.getTimestamp("ultimaActualizacion");
|
||||
|
||||
Instant createdAt = (createdAtTimestamp != null) ? createdAtTimestamp.toDate().toInstant() : null;
|
||||
Instant lastModified = (lastModifiedTimestamp != null) ? lastModifiedTimestamp.toDate().toInstant() : null;
|
||||
|
||||
return new ConversationSessionDTO(
|
||||
document.getString("sessionId"),
|
||||
document.getString("userId"),
|
||||
document.getString("telefono"),
|
||||
createdAt,
|
||||
lastModified,
|
||||
document.getString("ultimoMensaje"),
|
||||
document.getString("pantallaContexto")
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> createSessionMap(ConversationSessionDTO session) {
|
||||
Map<String, Object> sessionMap = new HashMap<>();
|
||||
sessionMap.put("sessionId", session.sessionId());
|
||||
sessionMap.put("userId", session.userId());
|
||||
sessionMap.put("telefono", session.telefono());
|
||||
sessionMap.put("fechaCreacion", session.createdAt());
|
||||
sessionMap.put("ultimaActualizacion", session.lastModified());
|
||||
sessionMap.put("ultimoMensaje", session.lastMessage());
|
||||
sessionMap.put("pantallaContexto", session.pantallaContexto());
|
||||
return sessionMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.example.mapper.messagefilter;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
|
||||
import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
|
||||
import com.example.dto.dialogflow.conversation.MessageType;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class ConversationContextMapper {
|
||||
|
||||
@Value("${conversation.context.message.limit:60}")
|
||||
private int messageLimit;
|
||||
|
||||
@Value("${conversation.context.days.limit:30}")
|
||||
private int daysLimit;
|
||||
|
||||
private static final int MAX_HISTORY_BYTES = 50 * 1024; // 50 KB
|
||||
|
||||
private static final String NOTIFICATION_TEXT_PARAM = "notification_text";
|
||||
|
||||
public String toText(ConversationSessionDTO session, List<ConversationMessageDTO> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return toTextFromMessages(messages);
|
||||
}
|
||||
|
||||
public String toTextWithLimits(ConversationSessionDTO session, List<ConversationMessageDTO> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Instant thirtyDaysAgo = Instant.now().minus(daysLimit, ChronoUnit.DAYS);
|
||||
|
||||
List<ConversationMessageDTO> recentEntries = messages.stream()
|
||||
.filter(entry -> entry.timestamp() != null && entry.timestamp().isAfter(thirtyDaysAgo))
|
||||
.sorted(Comparator.comparing(ConversationMessageDTO::timestamp).reversed())
|
||||
.limit(messageLimit)
|
||||
.sorted(Comparator.comparing(ConversationMessageDTO::timestamp))
|
||||
.collect(Collectors.toList());
|
||||
return toTextWithTruncation(recentEntries);
|
||||
}
|
||||
|
||||
public String toTextFromMessages(List<ConversationMessageDTO> messages) {
|
||||
return messages.stream()
|
||||
.map(this::formatEntry)
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public String toTextWithTruncation(List<ConversationMessageDTO> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder textBlock = new StringBuilder();
|
||||
List<String> formattedMessages = messages.stream()
|
||||
.map(this::formatEntry)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (int i = formattedMessages.size() - 1; i >= 0; i--) {
|
||||
String message = formattedMessages.get(i) + "\n";
|
||||
if (textBlock.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8).length + message.getBytes(java.nio.charset.StandardCharsets.UTF_8).length > MAX_HISTORY_BYTES) {
|
||||
break;
|
||||
}
|
||||
textBlock.insert(0, message);
|
||||
}
|
||||
|
||||
return textBlock.toString().trim();
|
||||
}
|
||||
|
||||
private String formatEntry(ConversationMessageDTO entry) {
|
||||
String prefix = "User: ";
|
||||
String content = entry.text();
|
||||
|
||||
if (entry.type() != null) {
|
||||
switch (entry.type()) {
|
||||
case AGENT:
|
||||
prefix = "Agent: ";
|
||||
break;
|
||||
case SYSTEM:
|
||||
prefix = "System: ";
|
||||
// fix: add notification in the conversation.
|
||||
if (entry.parameters() != null && entry.parameters().containsKey(NOTIFICATION_TEXT_PARAM)) {
|
||||
Object paramText = entry.parameters().get(NOTIFICATION_TEXT_PARAM);
|
||||
if (paramText != null && !paramText.toString().isBlank()) {
|
||||
content = paramText.toString();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LLM:
|
||||
prefix = "System: ";
|
||||
break;
|
||||
case USER:
|
||||
default:
|
||||
prefix = "User: ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String text = prefix + content;
|
||||
|
||||
if (entry.type() == MessageType.AGENT) {
|
||||
text = cleanAgentMessage(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private String cleanAgentMessage(String message) {
|
||||
return message.replaceAll("\\s*\\{.*\\}\\s*$", "").trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.messagefilter;
|
||||
|
||||
import com.example.dto.dialogflow.notification.NotificationDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class NotificationContextMapper {
|
||||
|
||||
public String toText(NotificationDTO notification) {
|
||||
if (notification == null || notification.texto() == null) {
|
||||
return "";
|
||||
}
|
||||
return notification.texto();
|
||||
}
|
||||
|
||||
public String toText(List<NotificationDTO> notifications) {
|
||||
if (notifications == null || notifications.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return notifications.stream()
|
||||
.map(NotificationDTO::texto)
|
||||
.filter(texto -> texto != null && !texto.isBlank())
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
|
||||
package com.example.mapper.notification;
|
||||
|
||||
import com.example.dto.dialogflow.notification.ExternalNotRequestDTO;
|
||||
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryInputDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryParamsDTO;
|
||||
import com.example.dto.dialogflow.conversation.TextInputDTO;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring component for mapping an external notification request to a Dialogflow `DetectIntentRequestDTO`.
|
||||
* This class takes a simplified `ExternalNotRequestDTO` and converts it into the structured
|
||||
* DTO required for a Dialogflow API call, specifically for triggering a notification event.
|
||||
* It ensures required parameters like the phone number are present and populates the
|
||||
* request with event-specific details.
|
||||
*/
|
||||
@Component
|
||||
public class ExternalNotRequestMapper {
|
||||
private static final String LANGUAGE_CODE = "es";
|
||||
private static final String TELEPHONE_PARAM_NAME = "telefono";
|
||||
private static final String NOTIFICATION_TEXT_PARAM = "notification_text";
|
||||
private static final String NOTIFICATION_LABEL = "NOTIFICACION";
|
||||
private static final String PREFIX_PO_PARAM = "notification_po_";
|
||||
|
||||
|
||||
|
||||
public DetectIntentRequestDTO map(ExternalNotRequestDTO request) {
|
||||
Objects.requireNonNull(request, "NotificationRequestDTO cannot be null for mapping.");
|
||||
|
||||
if (request.phoneNumber() == null || request.phoneNumber().isEmpty()) {
|
||||
throw new IllegalArgumentException("Phone numbers is required and cannot be empty in NotificationRequestDTO.");
|
||||
}
|
||||
String phoneNumber = request.phoneNumber();
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put(TELEPHONE_PARAM_NAME, phoneNumber);
|
||||
parameters.put(NOTIFICATION_TEXT_PARAM, request.text());
|
||||
|
||||
|
||||
if (request.hiddenParameters() != null && !request.hiddenParameters().isEmpty()) {
|
||||
StringBuilder poBuilder = new StringBuilder();
|
||||
request.hiddenParameters().forEach((key, value) -> {
|
||||
parameters.put(PREFIX_PO_PARAM + key, value);
|
||||
poBuilder.append(key).append(": ").append(value).append("\n");
|
||||
});
|
||||
parameters.put("po", poBuilder.toString());
|
||||
}
|
||||
|
||||
TextInputDTO textInput = new TextInputDTO(NOTIFICATION_LABEL);
|
||||
QueryInputDTO queryInput = new QueryInputDTO(textInput, null, LANGUAGE_CODE);
|
||||
|
||||
|
||||
QueryParamsDTO queryParams = new QueryParamsDTO(parameters);
|
||||
|
||||
return new DetectIntentRequestDTO(queryInput, queryParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
|
||||
* Your use of it is subject to your agreement with Google.
|
||||
*/
|
||||
package com.example.mapper.notification;
|
||||
|
||||
import com.example.dto.dialogflow.notification.NotificationDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.cloud.firestore.DocumentSnapshot;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring component for mapping notification data between application DTOs and Firestore documents.
|
||||
* This class handles the transformation of Dialogflow event details and notification metadata
|
||||
* into a `NotificationDTO` for persistence and provides methods to serialize and deserialize
|
||||
* this DTO to and from Firestore-compatible data structures.
|
||||
*/
|
||||
@Component
|
||||
public class FirestoreNotificationMapper {
|
||||
|
||||
private static final String DEFAULT_LANGUAGE_CODE = "es";
|
||||
private static final String FIXED_EVENT_NAME = "notificacion";
|
||||
private final ObjectMapper objectMapper;
|
||||
private static final String DEFAULT_NOTIFICATION_STATUS="ACTIVE";
|
||||
|
||||
public FirestoreNotificationMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public NotificationDTO mapToFirestoreNotification(
|
||||
String notificationId,
|
||||
String telephone,
|
||||
String notificationText,
|
||||
Map<String, Object> parameters) {
|
||||
|
||||
Objects.requireNonNull(notificationId, "Notification ID cannot be null for mapping.");
|
||||
Objects.requireNonNull(notificationText, "Notification text cannot be null for mapping.");
|
||||
Objects.requireNonNull(parameters, "Dialogflow parameters map cannot be null.");
|
||||
|
||||
return new NotificationDTO(
|
||||
notificationId,
|
||||
telephone,
|
||||
Instant.now(),
|
||||
notificationText,
|
||||
FIXED_EVENT_NAME,
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
parameters,
|
||||
DEFAULT_NOTIFICATION_STATUS
|
||||
);
|
||||
}
|
||||
|
||||
public NotificationDTO mapFirestoreDocumentToNotificationDTO(DocumentSnapshot documentSnapshot) {
|
||||
Objects.requireNonNull(documentSnapshot, "DocumentSnapshot cannot be null for mapping.");
|
||||
if (!documentSnapshot.exists()) {
|
||||
throw new IllegalArgumentException("DocumentSnapshot does not exist.");
|
||||
}
|
||||
try {
|
||||
return objectMapper.convertValue(documentSnapshot.getData(), NotificationDTO.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to convert Firestore document data to NotificationDTO for ID " + documentSnapshot.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> mapNotificationDTOToMap(NotificationDTO notificationDTO) {
|
||||
Objects.requireNonNull(notificationDTO, "NotificationDTO cannot be null for mapping to map.");
|
||||
return objectMapper.convertValue(notificationDTO, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user