summaryrefslogtreecommitdiff
path: root/indra/newview/llimview.cpp
diff options
context:
space:
mode:
authorPepper <3782201+rohvani@users.noreply.github.com>2026-09-17 11:03:27 -0400
committerGitHub <noreply@github.com>2026-09-17 11:03:27 -0400
commitcc5d3dd3cd653971fff16cf2bc1d380edd5ea7f2 (patch)
treea825cb021b74e45ceacb90213260b625f5234ebb /indra/newview/llimview.cpp
parent56b34b332727299f2d54d4d6e8dbe5a7cb54cb11 (diff)
parentc9cb3694cce949e7ab4638200646e91814c1cc7b (diff)
Merge pull request #6298 from secondlife/pepper/chat-fixes-2
additional fixes/cleanup for chat modernization project viewer
Diffstat (limited to 'indra/newview/llimview.cpp')
-rw-r--r--indra/newview/llimview.cpp223
1 files changed, 63 insertions, 160 deletions
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index e27ad96a63..0bdb114c17 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -90,31 +90,6 @@ const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size()
// Tokens are process-unique so a stale completion cannot match a recreated P2P session.
static U64 sChatHistoryLoadToken = 0;
-namespace
-{
-bool sameHistoryRow(LLSD left, LLSD right)
-{
- // Positional indexes are rebuilt with the overlay and do not change presentation.
- left.erase("index");
- right.erase("index");
- return llsd_equals(left, right);
-}
-
-bool sameHistory(const LLIMModel::chat_message_list_t& left,
- const LLIMModel::chat_message_list_t& right)
-{
- return left.size() == right.size() &&
- std::equal(left.begin(), left.end(), right.begin(), sameHistoryRow);
-}
-
-bool isStrictHistoryPrefix(const LLIMModel::chat_message_list_t& prefix,
- const LLIMModel::chat_message_list_t& complete)
-{
- return prefix.size() < complete.size() &&
- std::equal(prefix.begin(), prefix.end(), complete.begin(), sameHistoryRow);
-}
-}
-
/** Timeout of outgoing session initialization (in seconds) */
const static U32 SESSION_INITIALIZATION_TIMEOUT = 30;
@@ -626,39 +601,37 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit
}
void translateSuccess(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text,
- U64 time_n_flags, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language)
+ U64 time_n_flags, LLSD history_context, std::string expectLang, std::string translation, const std::string detected_language)
{
std::string message_txt(utf8_text);
// filter out non-interesting responses
if (!translation.empty()
&& ((detected_language.empty()) || (expectLang != detected_language))
- && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0))
+ && (LLStringUtil::compareInsensitive(translation, utf8_text) != 0))
{ // Note - if this format changes, also fix code in addMessagesFromServerHistory()
message_txt += XL8_START_TAG + LLTranslate::removeNoTranslateTags(translation) + XL8_END_TAG;
}
- // Extract info packed in time_n_flags
bool log2file = (bool)(time_n_flags & (1LL << 32));
bool is_region_msg = (bool)(time_n_flags & (1LL << 33));
U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff);
- LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp);
+ LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp, history_context);
}
void translateFailure(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text,
- U64 time_n_flags, int status, const std::string err_msg)
+ U64 time_n_flags, LLSD history_context, int status, const std::string err_msg)
{
std::string message_txt(utf8_text);
std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg));
LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages
message_txt += XL8_START_TAG + msg + XL8_END_TAG;
- // Extract info packed in time_n_flags
bool log2file = (bool)(time_n_flags & (1LL << 32));
bool is_region_msg = (bool)(time_n_flags & (1LL << 33));
U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff);
- LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp);
+ LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp, history_context);
}
void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp)
@@ -1394,8 +1367,8 @@ void LLIMModel::LLIMSession::loadHistory()
{
mChatHistoryLoadToken = ++sChatHistoryLoadToken;
mChatHistoryLocalLoading = false;
- mChatServiceHistoricalValue.clear();
- replaceHistoricalMessages(mChatServiceHistoricalValue);
+ mChatServiceHistory.clear();
+ replaceHistoricalMessages({});
return;
}
@@ -1403,8 +1376,8 @@ void LLIMModel::LLIMSession::loadHistory()
{
mChatHistoryLoadToken = ++sChatHistoryLoadToken;
mChatHistoryLocalLoading = false;
- mChatServiceHistoricalValue.clear();
- replaceHistoricalMessages(mChatServiceHistoricalValue);
+ mChatServiceHistory.clear();
+ replaceHistoricalMessages({});
return;
}
@@ -1463,9 +1436,9 @@ void LLIMModel::LLIMSession::loadHistory()
// current, then replace the historical overlay in one operation.
session->mChatHistoryArchiveSerial = result.archive_serial;
session->mChatHistoryLocalLoading = false;
- session->mChatServiceHistoricalValue = LLChatServiceHistory::mergeHeadPreview(
- result.messages, snapshot, 200);
- session->replaceHistoricalMessages(session->mChatServiceHistoricalValue);
+ session->mChatServiceHistory.setLoaded(result.messages);
+ session->replaceHistoricalMessages(LLChatServiceHistory::composeHistory(
+ session->mChatServiceHistory, snapshot, 200, session_id));
}))
{
// If dispatch infrastructure is unavailable, retain ordinary plaintext
@@ -1476,7 +1449,7 @@ void LLIMModel::LLIMSession::loadHistory()
{
chat_message_list_t legacy;
LLLogChat::loadChatHistory(mHistoryFileName, legacy);
- mChatServiceHistoricalValue = legacy;
+ mChatServiceHistory.setLoaded(legacy);
replaceHistoricalMessages(legacy);
}
}
@@ -1484,86 +1457,12 @@ void LLIMModel::LLIMSession::loadHistory()
void LLIMModel::LLIMSession::replaceHistoricalMessages(const chat_message_list_t& history)
{
- // Separate the append-only live tail from the currently presented historical baseline.
- chat_message_list_t live;
- chat_message_list_t current_historical;
- for (const LLSD& message : mMsgs)
- {
- if (!message["is_history"].asBoolean())
- {
- live.push_back(message);
- }
- else
- {
- current_historical.push_back(message);
- }
- }
-
- // A refresh may contain rows that this open session already
- // displays live. Consume those exact occurrences before rebuilding the overlay.
- const chat_message_list_t filtered = isP2P()
- ? LLChatServiceHistory::filterLiveDuplicates(history, live) : history;
-
- // Normalize stitched LLLogChat fields into the IM model shape and reverse the
- // ascending input into the model's newest-first historical order.
- chat_message_list_t historical;
- for (const LLSD& source : filtered)
+ if (LLChatServiceHistory::replaceHistory(mMsgs, history, isP2P()))
{
- const std::string from = source[LL_IM_FROM].asString();
- LLUUID from_id;
-
- // Plaintext transcripts store sender names but usually omit UUIDs. Recover
- // resident identity from local name caches without scheduling a lookup.
- if (source[LL_IM_FROM_ID].isDefined())
+ if (LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID))
{
- from_id = source[LL_IM_FROM_ID].asUUID();
+ floater->reloadMessages(false);
}
- else
- {
- const std::string legacy_name = gCacheName->buildLegacyName(from);
- from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name);
- }
-
- LLSD message = source;
- message["from"] = from;
- message["from_id"] = from_id;
- message["message"] = source[LL_IM_TEXT];
- message["time"] = source[LL_IM_TIME];
- message["timestamp"] = source["timestamp"].asInteger();
- message["is_history"] = true;
- message["is_region_msg"] = false;
- historical.push_front(message);
- }
-
- const bool consumed_live = filtered.size() < history.size();
- LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID);
- const bool capped_live_rollover =
- floater && floater->isInVisibleChain() &&
- consumed_live &&
- !historical.empty() &&
- history.size() == current_historical.size() &&
- isStrictHistoryPrefix(historical, current_historical);
-
- // Re-publishing the same bounded history, including eviction of its oldest
- // rows after live messages become durable, must not reconstruct a visible transcript.
- if (sameHistory(current_historical, historical) || capped_live_rollover)
- {
- return;
- }
-
- live.insert(live.end(), historical.begin(), historical.end());
- mMsgs.swap(live);
-
- // Rebuild positional indexes only after the complete live/history overlay exists.
- S32 index = static_cast<S32>(mMsgs.size());
- for (LLSD& message : mMsgs)
- {
- message["index"] = --index;
- }
-
- if (floater)
- {
- floater->reloadMessages(false);
}
}
@@ -1573,8 +1472,8 @@ void LLIMModel::LLIMSession::clearHistoricalMessages()
// after presentation has been cleared.
mChatHistoryLoadToken = ++sChatHistoryLoadToken;
mChatHistoryLocalLoading = false;
- mChatServiceHistoricalValue.clear();
- replaceHistoricalMessages(mChatServiceHistoricalValue);
+ mChatServiceHistory.clear();
+ replaceHistoricalMessages({});
}
void LLIMModel::LLIMSession::clearForHistoryDeletion()
@@ -1584,7 +1483,7 @@ void LLIMModel::LLIMSession::clearForHistoryDeletion()
mChatHistoryLocalLoading = false;
mChatHistoryArchiveSerial = 0;
mChatServicePresentationAllowed = false;
- mChatServiceHistoricalValue.clear();
+ mChatServiceHistory.clear();
mLastHistoryCacheDateTime.clear();
mLastHistoryCacheMsgs.clear();
mMsgs.clear();
@@ -1607,33 +1506,15 @@ void LLIMModel::LLIMSession::applyChatServiceSnapshot(
return;
}
- // Revoking service presentation strips only service-tagged historical rows;
- // legacy rows and every live row remain intact.
const bool presentation_changed =
snapshot.service_presentation_allowed != mChatServicePresentationAllowed;
mChatServicePresentationAllowed = snapshot.service_presentation_allowed;
- if (presentation_changed && !snapshot.service_presentation_allowed)
+ // Apply previews and presentation changes through the shared history composer.
+ if (presentation_changed || !snapshot.head_preview.empty())
{
- chat_message_list_t legacy_only;
- for (const LLSD& message : mChatServiceHistoricalValue)
- {
- if (!message["chat_service_msg_id"].isString())
- {
- legacy_only.push_back(message);
- }
- }
-
- mChatServiceHistoricalValue.swap(legacy_only);
- replaceHistoricalMessages(mChatServiceHistoricalValue);
- }
-
- // The bounded first page can update presentation before durable archive publication.
- if (snapshot.service_presentation_allowed && !snapshot.head_preview.empty())
- {
- mChatServiceHistoricalValue = LLChatServiceHistory::mergeHeadPreview(
- mChatServiceHistoricalValue, snapshot, 200);
- replaceHistoricalMessages(mChatServiceHistoricalValue);
+ replaceHistoricalMessages(LLChatServiceHistory::composeHistory(
+ mChatServiceHistory, snapshot, 200, mSessionID));
}
// The active read will restart itself if its captured presentation or archive
@@ -2008,8 +1889,7 @@ bool LLIMModel::addToHistory(const LLUUID& session_id,
return false;
}
- // P2P live rows keep their actual arrival/send time so a later asynchronous
- // history refresh can reconcile the same logged occurrence exactly by minute.
+ // Fill missing direct timestamps for display and plaintext reconciliation.
U32 model_timestamp = timestamp;
if (!model_timestamp && session->isP2PSessionType())
{
@@ -2060,27 +1940,34 @@ void LLIMModel::proccessOnlineOfflineNotification(
}
void LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
- const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* = false */ U32 time_stamp /* = 0 */)
+ const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* = false */ U32 time_stamp /* = 0 */,
+ LLSD history_context)
{
+ // Capture direct-message timing before an asynchronous translation can delay it.
+ LLIMSession* session = findIMSession(session_id);
+ if (session && session->isP2P())
+ {
+ history_context = LLChatServiceHistory::prepareLiveMessage(utf8_text, time_stamp, history_context);
+ }
if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM))
{
const std::string from_lang = ""; // leave empty to trigger autodetect
const std::string to_lang = LLTranslate::getTranslateLanguage();
U64 time_n_flags = ((U64) time_stamp) | (log2file ? (1LL << 32) : 0) | (is_region_msg ? (1LL << 33) : 0); // boost::bind has limited parameters
LLTranslate::translateMessage(from_lang, to_lang, utf8_text,
- boost::bind(&translateSuccess, session_id, from, from_id, utf8_text, time_n_flags, utf8_text, from_lang, _1, _2),
- boost::bind(&translateFailure, session_id, from, from_id, utf8_text, time_n_flags, _1, _2));
+ boost::bind(&translateSuccess, session_id, from, from_id, utf8_text, time_n_flags, history_context, from_lang, _1, _2),
+ boost::bind(&translateFailure, session_id, from, from_id, utf8_text, time_n_flags, history_context, _1, _2));
}
else
{
- processAddingMessage(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp);
+ processAddingMessage(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp, history_context);
}
}
void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
- const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp)
+ const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp, LLSD history_context)
{
- LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp);
+ LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp, history_context);
if (!session)
return;
@@ -2107,7 +1994,7 @@ void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string
LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* false */
- U32 timestamp /* = 0 */)
+ U32 timestamp /* = 0 */, LLSD history_context)
{
LLIMSession* session = findIMSession(session_id);
@@ -2123,7 +2010,16 @@ LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id,
from_name = SYSTEM_FROM;
}
+ // Direct silent callers also capture provenance; live delivery always appends.
+ if (session->isP2P())
+ {
+ history_context = LLChatServiceHistory::prepareLiveMessage(utf8_text, timestamp, history_context);
+ }
addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg, timestamp);
+ if (session->isP2P())
+ {
+ LLChatServiceHistory::recordLiveMessage(session->mMsgs.front(), history_context);
+ }
if (log2file)
{
logToFile(getHistoryFileName(session_id), from_name, from_id, utf8_text);
@@ -2344,11 +2240,11 @@ void LLIMModel::sendMessage(const std::string& utf8_text,
if((dialog == IM_NOTHING_SPECIAL) &&
(other_participant_id.notNull()))
{
- // The packet has been sent; direct outbound bursts share one quiet service
- // refresh while first contact also starts bounded discovery immediately.
+ // The packet has been sent; incoming and outgoing IMs share one quiet
+ // service refresh while first contact starts bounded discovery immediately.
if (session && session->isP2PSessionType())
{
- LLChatServiceHistory::noteOutboundDirectMessage(other_participant_id);
+ LLChatServiceHistory::noteDirectMessageActivity(other_participant_id);
}
// Do we have to replace the /me's here?
@@ -3502,7 +3398,8 @@ void LLIMMgr::addMessage(
bool is_region_msg,
U32 timestamp, // May be zero
LLUUID display_id,
- std::string_view display_name)
+ std::string_view display_name,
+ const LLSD& original_text)
{
LLUUID other_participant_id = target_id;
std::string message_display_name = (display_name.empty()) ? from : std::string(display_name);
@@ -3631,14 +3528,20 @@ void LLIMMgr::addMessage(
if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message)
{
- // Accepted inbound direct rows share the same immediate priority path used by an opener.
- if (LLChatServiceHistory::isPersistedDirectDialog(dialog) &&
+ // Online direct timestamps describe receipt; offline timestamps describe
+ // the saved send. Preserve that distinction through translation.
+ const bool incoming_direct = LLChatServiceHistory::isPersistedDirectDialog(dialog) &&
!is_region_msg && message_display_name != SYSTEM_FROM &&
- other_participant_id.notNull() && other_participant_id != gAgentID)
+ other_participant_id.notNull() && other_participant_id != gAgentID;
+ if (incoming_direct)
{
- LLChatServiceHistory::prioritizeResident(other_participant_id, true);
+ // Incoming activity shares the outgoing quiet deadline before reconciliation.
+ LLChatServiceHistory::noteDirectMessageActivity(other_participant_id);
}
- LLIMModel::instance().addMessage(new_session_id, message_display_name, display_id, msg, true, is_region_msg, timestamp);
+ LLIMModel::instance().addMessage(new_session_id, message_display_name, display_id, msg, true, is_region_msg, timestamp,
+ incoming_direct
+ ? LLChatServiceHistoryCore::incomingContext(original_text, !is_offline_msg)
+ : LLSD());
}
// Open conversation floater if offline messages are present