From 4fd0e734ea0e04a5b15ddb6d890ddeca4e5cc756 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 12 Aug 2025 16:36:21 +0400 Subject: [PATCH] Initial support for streamed bot responses. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_updates.cpp | 9 ++- Telegram/SourceFiles/history/history.cpp | 9 +++ Telegram/SourceFiles/history/history.h | 4 ++ .../history/history_streamed_drafts.cpp | 59 +++++++++++++++++++ .../history/history_streamed_drafts.h | 33 +++++++++++ 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 Telegram/SourceFiles/history/history_streamed_drafts.cpp create mode 100644 Telegram/SourceFiles/history/history_streamed_drafts.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 42e0a54ba4..ac8cad7fe9 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -952,6 +952,8 @@ PRIVATE history/history_inner_widget.h history/history_location_manager.cpp history/history_location_manager.h + history/history_streamed_drafts.cpp + history/history_streamed_drafts.h history/history_translation.cpp history/history_translation.h history/history_unread_things.cpp diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index c33a66b1a0..2b65484848 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" +#include "history/history_streamed_drafts.h" #include "history/history_unread_things.h" #include "core/application.h" #include "storage/storage_account.h" @@ -1096,6 +1097,9 @@ void Updates::handleSendActionUpdate( const auto from = (fromId == session().userPeerId()) ? session().user().get() : session().data().peerLoaded(fromId); + const auto when = requestingDifference() + ? 0 + : base::unixtime::now(); if (action.type() == mtpc_speakingInGroupCallAction) { handleSpeakingInCall(peer, fromId, from); } @@ -1109,11 +1113,10 @@ void Updates::handleSendActionUpdate( handleEmojiInteraction(peer, qs(data.vemoticon())); return; } else if (action.type() == mtpc_sendMessageTextDraftAction) { + const auto &data = action.c_sendMessageTextDraftAction(); + history->streamedDrafts().apply(rootId, fromId, when, data); return; } - const auto when = requestingDifference() - ? 0 - : base::unixtime::now(); session().data().sendActionManager().registerFor( history, rootId, diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 80114cdeb4..25432b37fb 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" +#include "history/history_streamed_drafts.h" #include "history/history_translation.h" #include "history/history_unread_things.h" #include "core/ui_integration.h" @@ -3749,6 +3750,13 @@ void History::checkLocalMessages() { } } +HistoryStreamedDrafts &History::streamedDrafts() { + if (!_streamedDrafts) { + _streamedDrafts = std::make_unique(this); + } + return *_streamedDrafts; +} + HistoryItem *History::joinedMessageInstance() const { return _joinedMessage; } @@ -3907,6 +3915,7 @@ void History::clear(ClearType type, bool markEmpty) { _unreadBarView = nullptr; _firstUnreadView = nullptr; removeJoinedMessage(); + base::take(_streamedDrafts); forgetScrollState(); blocks.clear(); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index b7fbcf5afc..e44f994523 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -23,6 +23,7 @@ class HistoryItem; struct HistoryItemCommonFields; struct HistoryMessageMarkupData; class HistoryMainElementDelegateMixin; +class HistoryStreamedDrafts; struct LanguageId; namespace Data { @@ -91,6 +92,8 @@ public: [[nodiscard]] const Data::HistoryMessages &messages() const; [[nodiscard]] Data::HistoryMessages *maybeMessages(); + [[nodiscard]] HistoryStreamedDrafts &streamedDrafts(); + [[nodiscard]] HistoryItem *joinedMessageInstance() const; void checkLocalMessages(); void removeJoinedMessage(); @@ -641,6 +644,7 @@ private: std::unordered_set> _items; std::unique_ptr _messages; + std::unique_ptr _streamedDrafts; // This almost always is equal to _lastMessage. The only difference is // for a group that migrated to a supergroup. Then _lastMessage can diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp new file mode 100644 index 0000000000..121ada660e --- /dev/null +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -0,0 +1,59 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/history_streamed_drafts.h" + +#include "api/api_text_entities.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" + +HistoryStreamedDrafts::HistoryStreamedDrafts(not_null history) +: _history(history) { +} + +HistoryStreamedDrafts::~HistoryStreamedDrafts() { + for (const auto &[rootId, draft] : base::take(_drafts)) { + draft.message->destroy(); + } +} + +void HistoryStreamedDrafts::apply( + MsgId rootId, + PeerId fromId, + TimeId when, + const MTPDsendMessageTextDraftAction &data) { + const auto now = crl::now(); + const auto text = Api::ParseTextWithEntities( + &_history->session(), + data.vtext()); + const auto randomId = data.vrandom_id().v; + const auto i = _drafts.find(rootId); + if (i != end(_drafts)) { + if (when && i->second.randomId == randomId) { + i->second.message->setText(text); + i->second.updated = now; + return; + } + i->second.message->destroy(); + _drafts.erase(i); + } + if (!when) { + return; + } + _drafts.emplace(rootId, Draft{ + .message = _history->addNewLocalMessage({ + .id = _history->owner().nextLocalMessageId(), + .flags = MessageFlag::Local, + .from = fromId, + .replyTo = { .topicRootId = rootId }, + .date = when, + }, text, MTP_messageMediaEmpty()), + .randomId = randomId, + .updated = now, + }); +} diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.h b/Telegram/SourceFiles/history/history_streamed_drafts.h new file mode 100644 index 0000000000..91da41e30d --- /dev/null +++ b/Telegram/SourceFiles/history/history_streamed_drafts.h @@ -0,0 +1,33 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class History; + +class HistoryStreamedDrafts final { +public: + explicit HistoryStreamedDrafts(not_null history); + ~HistoryStreamedDrafts(); + + void apply( + MsgId rootId, + PeerId fromId, + TimeId when, + const MTPDsendMessageTextDraftAction &data); + +private: + struct Draft { + not_null message; + uint64 randomId = 0; + crl::time updated = 0; + }; + + const not_null _history; + base::flat_map _drafts; + +};