From 9316a386994e8951ee1bfb256fcbcb8b985509bb Mon Sep 17 00:00:00 2001 From: Andre K Date: Thu, 16 Apr 2026 08:39:53 +0800 Subject: [PATCH] ux: show Thinking placeholder immediately, edit with status and final reply --- main.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index 5281088..bf350c5 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ import pathlib import subprocess from openai import OpenAI +import telegram from telegram import BotCommand, Update from telegram.error import BadRequest, TimedOut from telegram.ext import ( @@ -331,8 +332,8 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None messages = get_messages(chat_id) messages.append({"role": "user", "content": user_text}) - # Send "typing" indicator - await update.message.chat.send_action("typing") + # Send immediate "Thinking ..." placeholder so user knows the bot read their message + thinking = await _safe_reply(update, "Thinking ...") # Run the LLM loop (with tool calls) max_rounds = 10 @@ -346,7 +347,7 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None ) except Exception as e: log.error("OpenAI API error: %s", e) - await _safe_reply(update, f"Sorry, I hit an error: {e}") + await _safe_edit(thinking, update, f"Sorry, I hit an error: {e}") return choice = response.choices[0] @@ -361,6 +362,9 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None json.loads(tc.function.arguments) if tc.function.arguments else {} ) log.info("Tool call: %s(%s)", tc.function.name, list(args.keys())) + # Update placeholder with current tool status + status = f"Working ... ({tc.function.name})" + await _safe_edit(thinking, update, status) try: result = handle_tool_call(tc.function.name, args) except Exception as e: @@ -373,7 +377,6 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None } ) # Continue the loop for the LLM to process tool results - await update.message.chat.send_action("typing") continue # No tool calls — we have a final text reply @@ -382,21 +385,36 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None if len(messages) > 60: messages[:] = messages[:1] + messages[-40:] - await _safe_reply(update, msg.content) + await _safe_edit(thinking, update, msg.content) return - await _safe_reply( - update, "I ran out of steps — please try again with a simpler request." + await _safe_edit( + thinking, update, "I ran out of steps — please try again with a simpler request." ) -async def _safe_reply(update: Update, text: str, **kwargs) -> None: +async def _safe_reply(update: Update, text: str, **kwargs) -> "telegram.Message | None": """Reply to a message, falling back to send_message if the original is gone.""" try: - await update.message.reply_text(text, **kwargs) + return await update.message.reply_text(text, **kwargs) except BadRequest: log.warning("Could not reply to message, sending without reply") - await update.effective_chat.send_message(text, **kwargs) + return await update.effective_chat.send_message(text, **kwargs) + + +async def _safe_edit( + thinking: "telegram.Message | None", + update: Update, + text: str, +) -> None: + """Edit the placeholder message, falling back to a new message if it fails.""" + if thinking: + try: + await thinking.edit_text(text) + return + except BadRequest: + log.warning("Could not edit placeholder, sending new message") + await update.effective_chat.send_message(text) async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: