surface message edit history (no edit_message tool - shard commits to its words)
This commit is contained in:
parent
9d490f5ca8
commit
bc3a4782cc
4 changed files with 75 additions and 5 deletions
|
|
@ -116,6 +116,7 @@ pub fn wire_event_from(
|
||||||
is_self,
|
is_self,
|
||||||
ts,
|
ts,
|
||||||
in_reply_to,
|
in_reply_to,
|
||||||
|
edit_history,
|
||||||
} => {
|
} => {
|
||||||
let read_by: Vec<String> = read_markers
|
let read_by: Vec<String> = read_markers
|
||||||
.get(event_id)
|
.get(event_id)
|
||||||
|
|
@ -135,6 +136,7 @@ pub fn wire_event_from(
|
||||||
body: body.clone(),
|
body: body.clone(),
|
||||||
in_reply_to: in_reply_to.as_ref().map(|e| e.as_str().to_owned()),
|
in_reply_to: in_reply_to.as_ref().map(|e| e.as_str().to_owned()),
|
||||||
read_by,
|
read_by,
|
||||||
|
edit_history: edit_history.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TimelineItem::Reaction {
|
TimelineItem::Reaction {
|
||||||
|
|
|
||||||
|
|
@ -379,6 +379,7 @@ async fn fetch_event(
|
||||||
body: text.body.clone(),
|
body: text.body.clone(),
|
||||||
in_reply_to,
|
in_reply_to,
|
||||||
read_by: Vec::new(),
|
read_by: Vec::new(),
|
||||||
|
edit_history: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::Reaction(
|
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::Reaction(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use matrix_sdk::{
|
||||||
ruma::{OwnedEventId, OwnedUserId, events::room::message::MessageType},
|
ruma::{OwnedEventId, OwnedUserId, events::room::message::MessageType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::types::TimelineItem;
|
use crate::types::{EditRecord, TimelineItem};
|
||||||
|
|
||||||
/// Format a unix-seconds timestamp as `YYYY-MM-DD HH:MM` UTC. Returns "?" for 0.
|
/// Format a unix-seconds timestamp as `YYYY-MM-DD HH:MM` UTC. Returns "?" for 0.
|
||||||
pub fn format_ts(secs: i64) -> String {
|
pub fn format_ts(secs: i64) -> String {
|
||||||
|
|
@ -83,8 +83,13 @@ pub async fn load_timeline(
|
||||||
let (cache, _handles) = room.event_cache().await?;
|
let (cache, _handles) = room.event_cache().await?;
|
||||||
let events = cache.events().await;
|
let events = cache.events().await;
|
||||||
|
|
||||||
|
use matrix_sdk::ruma::events::room::message::Relation;
|
||||||
|
|
||||||
let mut messages: Vec<TimelineItem> = Vec::new();
|
let mut messages: Vec<TimelineItem> = Vec::new();
|
||||||
let mut reactions: Vec<TimelineItem> = Vec::new();
|
let mut reactions: Vec<TimelineItem> = Vec::new();
|
||||||
|
// Edits stashed by target event_id. Walk is newest-first, so edits may
|
||||||
|
// arrive before their target original. (body, ts) pairs.
|
||||||
|
let mut pending_edits: HashMap<OwnedEventId, Vec<(String, i64)>> = HashMap::new();
|
||||||
let mut earliest_message_ts: Option<i64> = None;
|
let mut earliest_message_ts: Option<i64> = None;
|
||||||
|
|
||||||
for ev in events.iter().rev() {
|
for ev in events.iter().rev() {
|
||||||
|
|
@ -100,17 +105,28 @@ pub async fn load_timeline(
|
||||||
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::RoomMessage(
|
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::RoomMessage(
|
||||||
matrix_sdk::ruma::events::SyncMessageLikeEvent::Original(orig),
|
matrix_sdk::ruma::events::SyncMessageLikeEvent::Original(orig),
|
||||||
) => {
|
) => {
|
||||||
|
let ts = ts_secs_from(orig.origin_server_ts.0);
|
||||||
|
|
||||||
|
// Edit event? Stash the new body for the target original; do
|
||||||
|
// NOT count toward the message limit.
|
||||||
|
if let Some(Relation::Replacement(replacement)) = &orig.content.relates_to {
|
||||||
|
if let MessageType::Text(text) = &replacement.new_content.msgtype {
|
||||||
|
pending_edits
|
||||||
|
.entry(replacement.event_id.clone())
|
||||||
|
.or_default()
|
||||||
|
.push((text.body.clone(), ts));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if messages.len() >= limit {
|
if messages.len() >= limit {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let MessageType::Text(text) = &orig.content.msgtype else {
|
let MessageType::Text(text) = &orig.content.msgtype else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let ts = ts_secs_from(orig.origin_server_ts.0);
|
|
||||||
let in_reply_to = match &orig.content.relates_to {
|
let in_reply_to = match &orig.content.relates_to {
|
||||||
Some(matrix_sdk::ruma::events::room::message::Relation::Reply {
|
Some(Relation::Reply { in_reply_to }) => Some(in_reply_to.event_id.clone()),
|
||||||
in_reply_to,
|
|
||||||
}) => Some(in_reply_to.event_id.clone()),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if earliest_message_ts.is_none_or(|e| ts < e) {
|
if earliest_message_ts.is_none_or(|e| ts < e) {
|
||||||
|
|
@ -123,6 +139,7 @@ pub async fn load_timeline(
|
||||||
is_self: &orig.sender == own_user,
|
is_self: &orig.sender == own_user,
|
||||||
ts,
|
ts,
|
||||||
in_reply_to,
|
in_reply_to,
|
||||||
|
edit_history: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::Reaction(
|
matrix_sdk::ruma::events::AnySyncMessageLikeEvent::Reaction(
|
||||||
|
|
@ -141,6 +158,39 @@ pub async fn load_timeline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply pending edits to their target messages. Chain = [original body,
|
||||||
|
// edits sorted oldest-first]; current `body` becomes the latest, and
|
||||||
|
// everything before it ends up in `edit_history`.
|
||||||
|
for item in &mut messages {
|
||||||
|
if let TimelineItem::Message {
|
||||||
|
event_id,
|
||||||
|
body,
|
||||||
|
ts,
|
||||||
|
edit_history,
|
||||||
|
..
|
||||||
|
} = item
|
||||||
|
{
|
||||||
|
let edits = pending_edits.remove(event_id).unwrap_or_default();
|
||||||
|
if edits.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut chain: Vec<(String, i64)> = vec![(body.clone(), *ts)];
|
||||||
|
chain.extend(edits);
|
||||||
|
chain.sort_by_key(|(_, t)| *t);
|
||||||
|
// pop the most recent - that's the visible body
|
||||||
|
let (latest, _) = chain.pop().expect("chain has at least the original");
|
||||||
|
*body = latest;
|
||||||
|
*edit_history = chain
|
||||||
|
.into_iter()
|
||||||
|
.map(|(b, t)| EditRecord {
|
||||||
|
body: b,
|
||||||
|
ts: t,
|
||||||
|
ts_human: format!("{} UTC", format_ts(t)),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(min_ts) = earliest_message_ts {
|
if let Some(min_ts) = earliest_message_ts {
|
||||||
reactions.retain(|r| r.ts() >= min_ts);
|
reactions.retain(|r| r.ts() >= min_ts);
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +232,7 @@ pub async fn fetch_message(
|
||||||
is_self: &orig.sender == own_user,
|
is_self: &orig.sender == own_user,
|
||||||
ts,
|
ts,
|
||||||
in_reply_to: None,
|
in_reply_to: None,
|
||||||
|
edit_history: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
16
src/types.rs
16
src/types.rs
|
|
@ -6,6 +6,14 @@ use matrix_sdk::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// One prior version of an edited message.
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct EditRecord {
|
||||||
|
pub body: String,
|
||||||
|
pub ts: i64,
|
||||||
|
pub ts_human: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Serializable shape for one timeline event, used both in matrix_turn JSON
|
/// Serializable shape for one timeline event, used both in matrix_turn JSON
|
||||||
/// (input to the shard) and tool response JSON (get_room_history,
|
/// (input to the shard) and tool response JSON (get_room_history,
|
||||||
/// fetch_event).
|
/// fetch_event).
|
||||||
|
|
@ -22,6 +30,9 @@ pub enum WireEvent {
|
||||||
body: String,
|
body: String,
|
||||||
in_reply_to: Option<String>,
|
in_reply_to: Option<String>,
|
||||||
read_by: Vec<String>,
|
read_by: Vec<String>,
|
||||||
|
/// Prior versions of this message, oldest first. Empty when the
|
||||||
|
/// message has never been edited.
|
||||||
|
edit_history: Vec<EditRecord>,
|
||||||
},
|
},
|
||||||
Reaction {
|
Reaction {
|
||||||
sender: String,
|
sender: String,
|
||||||
|
|
@ -94,11 +105,16 @@ pub enum TimelineItem {
|
||||||
Message {
|
Message {
|
||||||
event_id: OwnedEventId,
|
event_id: OwnedEventId,
|
||||||
sender: OwnedUserId,
|
sender: OwnedUserId,
|
||||||
|
/// Latest version of the message body. If the user edited this message,
|
||||||
|
/// this is the most recent edit's content.
|
||||||
body: String,
|
body: String,
|
||||||
is_self: bool,
|
is_self: bool,
|
||||||
/// Unix seconds. 0 if unknown.
|
/// Unix seconds. 0 if unknown.
|
||||||
ts: i64,
|
ts: i64,
|
||||||
in_reply_to: Option<OwnedEventId>,
|
in_reply_to: Option<OwnedEventId>,
|
||||||
|
/// Prior versions of this message (oldest first), excluding the
|
||||||
|
/// current `body`. Empty when the message has never been edited.
|
||||||
|
edit_history: Vec<EditRecord>,
|
||||||
},
|
},
|
||||||
Reaction {
|
Reaction {
|
||||||
sender: OwnedUserId,
|
sender: OwnedUserId,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue