File size: 3,504 Bytes
0157ac7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""Throttled platform UI updates driven by transcript rendering."""

from __future__ import annotations

import time
from collections.abc import Callable

from loguru import logger

from .platforms.base import MessagingPlatform
from .safe_diagnostics import format_exception_for_log
from .transcript import RenderCtx, TranscriptBuffer


class ThrottledTranscriptEditor:
    """Rate-limited status message edits from a growing transcript."""

    def __init__(
        self,
        *,
        platform: MessagingPlatform,
        parse_mode: str | None,
        get_limit_chars: Callable[[], int],
        transcript: TranscriptBuffer,
        render_ctx: RenderCtx,
        node_id: str,
        chat_id: str,
        status_msg_id: str,
        debug_platform_edits: bool,
        log_messaging_error_details: bool = False,
    ) -> None:
        self._platform = platform
        self._parse_mode = parse_mode
        self._get_limit_chars = get_limit_chars
        self._transcript = transcript
        self._render_ctx = render_ctx
        self._node_id = node_id
        self._chat_id = chat_id
        self._status_msg_id = status_msg_id
        self._debug_platform_edits = debug_platform_edits
        self._log_messaging_error_details = log_messaging_error_details
        self._last_ui_update = 0.0
        self._last_displayed_text: str | None = None
        self._last_status: str | None = None

    @property
    def last_status(self) -> str | None:
        return self._last_status

    async def update(self, status: str | None = None, *, force: bool = False) -> None:
        """Render transcript + optional status line and edit the platform message."""
        now = time.time()
        if not force and now - self._last_ui_update < 1.0:
            return

        self._last_ui_update = now
        if status is not None:
            self._last_status = status
        try:
            display = self._transcript.render(
                self._render_ctx,
                limit_chars=self._get_limit_chars(),
                status=status,
            )
        except Exception as e:
            logger.warning(
                "Transcript render failed for node {}: {}",
                self._node_id,
                format_exception_for_log(
                    e, log_full_message=self._log_messaging_error_details
                ),
            )
            return
        if display and display != self._last_displayed_text:
            logger.debug(
                "PLATFORM_EDIT: node_id={} chat_id={} msg_id={} force={} status={!r} chars={}",
                self._node_id,
                self._chat_id,
                self._status_msg_id,
                bool(force),
                status,
                len(display),
            )
            if self._debug_platform_edits:
                logger.debug("PLATFORM_EDIT_TEXT:\n{}", display)
            self._last_displayed_text = display
            try:
                await self._platform.queue_edit_message(
                    self._chat_id,
                    self._status_msg_id,
                    display,
                    parse_mode=self._parse_mode,
                )
            except Exception as e:
                logger.warning(
                    "Failed to update platform for node {}: {}",
                    self._node_id,
                    format_exception_for_log(
                        e, log_full_message=self._log_messaging_error_details
                    ),
                )