ux: show Thinking placeholder immediately, edit with status and final reply

This commit is contained in:
Andre Kamarudin 2026-04-16 08:39:53 +08:00
parent 3c6cafd62d
commit 9316a38699

38
main.py
View file

@ -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: