From f87ee63a1d1ab167d1dd2aaccd81c726cb5475f3 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 23:40:24 +0100 Subject: [PATCH] Preserve failed composer drafts and cancel PM submissions with a result --- tools/chatgui/src/inputbar.cpp | 10 ++++-- tools/chatgui/transport/gui_bridge.h | 3 ++ tools/chatgui/transport/gui_bridge_impl.cpp | 38 ++++++++++++++++----- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/tools/chatgui/src/inputbar.cpp b/tools/chatgui/src/inputbar.cpp index 7866cf07..901fdc37 100644 --- a/tools/chatgui/src/inputbar.cpp +++ b/tools/chatgui/src/inputbar.cpp @@ -279,7 +279,7 @@ bool InputBar::submissionPending() const { /* Канальный текст и каждая часть ответа имеют собственное подтверждение и снимок черновика. */ quint64 InputBar::beginSubmission(const QByteArray& reply, quint64 generation, const QString& target, const QString& text, const QString& source) { - if (reply.isEmpty() && (m_dmMode || text.isEmpty())) return 0; + if (reply.isEmpty() && text.isEmpty()) return 0; const quint64 request = ++s_submissionSequence; m_pendingSubmissions.insert(request, {generation, target, text, source, m_input->document()->revision(), m_attachmentRevision}); GUI_DEBUG("send: pending ch=%s reply=%s request=%llu", qPrintable(target), reply.toHex().constData(), (unsigned long long)request); @@ -287,6 +287,11 @@ quint64 InputBar::beginSubmission(const QByteArray& reply, quint64 generation, c } void InputBar::submissionResult(quint64 request, bool success) { + if (!request && !success) { + m_submissionFailed = true; + GUI_ERROR("send: untracked attachment rejected target=%s", qPrintable(m_channelIdForRecord)); + emit attachmentRejected(QCoreApplication::translate("ChatGui", "Could not send message. Please try again.")); return; + } if (request && request == m_editRequest) { m_editRequest = 0; m_input->setReadOnly(false); if (success) cancelEdit(); @@ -515,13 +520,14 @@ void InputBar::onSendClicked() { emit sendFileAttachment(target, attachmentSource, attachmentDestination, attachmentName, reply, requestId); } if (!alive || context != m_attachmentContext) return; + if (m_submissionFailed) return; if (reply.isEmpty()) clearPendingAttachment(); } if (!text.isEmpty()) { const quint64 request = beginSubmission(reply, m_replyGeneration, m_channelIdForRecord, text); emit sendMessage(text, reply, request); if (!alive || context != m_attachmentContext) return; - if (!request) m_input->clear(); + if (!request && !m_submissionFailed) m_input->clear(); if (m_emojiPanel) m_emojiPanel->hide(); m_input->setFocus(); } diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index 6672ff6f..5f2a27ec 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -135,6 +135,9 @@ int gui_bridge_update_name(const char* name); * При -1 результата нет: вызывающий освобождает req и сам сообщает об отказе своему composer. */ struct chat_msg_submit; int gui_bridge_submit_message(struct chat_msg_submit* req); +/* PM-текст с request_id: привязка/владение/отмена и MSG_SUBMITTED как у канального composer. */ +struct dm_send_req; +int gui_bridge_submit_dm_message(struct dm_send_req* req); /* Подготовка вложения: тот же контракт результата, но отмена также освобождает PCM/compressor/временный source * и сообщает MSG_SUBMITTED(false). При -1 caller вызывает attachment_send_discard(req). */ struct attachment_send_req; diff --git a/tools/chatgui/transport/gui_bridge_impl.cpp b/tools/chatgui/transport/gui_bridge_impl.cpp index 18698e69..0b64da72 100644 --- a/tools/chatgui/transport/gui_bridge_impl.cpp +++ b/tools/chatgui/transport/gui_bridge_impl.cpp @@ -1019,14 +1019,19 @@ int gui_bridge_update_name(const char* name) { return 0; } +/* Общий codec composer: ID little-endian и success=0, без вызова GUI под mutex. */ +static void post_submission_failure(uint64_t id) { + if (!id) return; + uint8_t result[9] = {0}; + for (unsigned i = 0; i < 8; ++i) result[i] = (uint8_t)(id >> (8 * i)); + gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result)); +} + /* Отмена выполняется в uasync, но не вызывает обработчик ядра под bridge_mutex; результат копируется в Qt-очередь. */ static void cancel_message_submit(void* arg) { auto* request = static_cast(arg); DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "composer submission cancelled: request=%llu", (unsigned long long)request->request_id); - if (!request->request_id) return; - uint8_t result[9] = {0}; - for (unsigned i = 0; i < 8; ++i) result[i] = (uint8_t)(request->request_id >> (8 * i)); - gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result)); + post_submission_failure(request->request_id); } int gui_bridge_submit_message(struct chat_msg_submit* req) { @@ -1038,13 +1043,30 @@ int gui_bridge_submit_message(struct chat_msg_submit* req) { return post_command(chat_core_submit_trampoline, req, true, cancel_message_submit); } +/* PM composer получает отказ также при detach до исполнения native trampoline. */ +static void cancel_dm_submit(void* arg) { + auto* request = static_cast(arg); + DEBUG_WARN(DEBUG_CATEGORY_DM, "PM submission cancelled: request=%llu", (unsigned long long)request->request_id); + post_submission_failure(request->request_id); +} + +int gui_bridge_submit_dm_message(struct dm_send_req* req) { + std::lock_guard guard(bridge_mutex); + if (!req || !g_inst || !g_ua || !uasync_is_running(g_ua)) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM submission: core/request unavailable"); return -1; + } + req->inst = g_inst; + if (post_command(dm_send_trampoline, req, true, cancel_dm_submit) < 0) { + req->inst = nullptr; DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM submission: queue allocation failed"); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_DM, "PM text queued: conversation=%s request=%llu", req->conv_id, (unsigned long long)req->request_id); + return 0; +} + /* Отмена до native trampoline не требует живого media owner; callback освобождает весь запрос сам. */ static void cancel_attachment_submit(void* arg) { auto* request = static_cast(arg); - if (request->request_id) { - uint8_t result[9] = {0}; - memcpy(result, &request->request_id, 8); gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result)); - } + post_submission_failure(request->request_id); attachment_send_discard(request); }