fix: persist Chatwoot outbound message timeline

This commit is contained in:
plx
2026-08-15 01:34:14 +00:00
parent fabadbffdc
commit 71452b1e5c
4 changed files with 430 additions and 2 deletions

View File

@@ -17,6 +17,7 @@ from app.persistence import (
get_state_for_conversation,
mark_raw_event_error,
mark_raw_event_processed,
save_factual_chatwoot_message,
save_raw_event,
)
from app.schemas import AnalyzeRequest
@@ -32,6 +33,12 @@ def as_str(value: Any) -> Optional[str]:
return str(value)
def as_bool(value: Any) -> bool:
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes"}
return bool(value)
def get_nested(data: Dict[str, Any], *keys: str) -> Any:
current: Any = data
@@ -103,6 +110,11 @@ def message_timestamp(message: Dict[str, Any]) -> Any:
)
def source_message_timestamp(payload: Dict[str, Any]) -> Any:
message = payload.get("message") if isinstance(payload.get("message"), dict) else payload
return message.get("created_at") or payload.get("created_at") or message.get("timestamp") or payload.get("timestamp")
def extract_message_content_for_context(message: Dict[str, Any]) -> str:
try:
content = extract_chatwoot_content(message, message)
@@ -261,9 +273,21 @@ def extract_chatwoot_event(payload: Dict[str, Any]) -> Dict[str, Any]:
message_type_text = str(message_type).lower()
is_outgoing = message_type_text in {"outgoing", "outbound", "1"}
# The sender of an outgoing message is normally the agent, not the customer.
# Retain a contact only when Chatwoot supplied an explicit conversation/contact
# identity rather than falling back to the sender object.
if (
is_outgoing
and payload.get("contact_id") is None
and not isinstance(payload.get("contact"), dict)
and not isinstance(conversation.get("contact"), dict)
and not isinstance(get_nested(conversation, "contact_inbox", "contact"), dict)
):
contact_id = None
is_private = bool(
message.get("private")
or payload.get("private")
as_bool(message.get("private"))
or as_bool(payload.get("private"))
or message.get("content_type") == "input_select"
)
@@ -292,6 +316,7 @@ def extract_chatwoot_event(payload: Dict[str, Any]) -> Dict[str, Any]:
"is_private": is_private,
"sender_name": as_str(sender_name),
"sender_type": as_str(sender_type),
"created_at": source_message_timestamp(payload),
}
@@ -375,6 +400,25 @@ async def process_saved_chatwoot_raw_event(raw_event_id: str, payload: Dict[str,
}
if extracted["is_outgoing"]:
factual_message_id = None
if extracted.get("event_type") == "message_created" and not extracted.get("is_private"):
factual_message_id, _ = save_factual_chatwoot_message(
raw_event_id=raw_event_id,
source_event_id=extracted.get("source_event_id"),
conversation_id=extracted.get("conversation_id"),
contact_id=extracted.get("contact_id"),
direction="outbound",
raw_body=extracted["content"],
clean_body=extracted["content"],
source_created_at=extracted.get("created_at"),
metadata={
"message_type": extracted.get("message_type"),
"public": True,
"private": False,
"sender_name": extracted.get("sender_name"),
"sender_type": extracted.get("sender_type"),
},
)
auto_complete_result = auto_complete_task_from_outgoing_message(
conversation_id=extracted["conversation_id"],
contact_id=extracted["contact_id"],
@@ -400,6 +444,7 @@ async def process_saved_chatwoot_raw_event(raw_event_id: str, payload: Dict[str,
"status": "outgoing_processed" if completed else "ignored",
"reason": auto_complete_result.get("status"),
"raw_event_id": raw_event_id,
"message_id": factual_message_id,
"conversation_id": extracted["conversation_id"],
"contact_id": extracted["contact_id"],
"auto_complete": auto_complete_result,