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 import subprocess
from openai import OpenAI from openai import OpenAI
import telegram
from telegram import BotCommand, Update from telegram import BotCommand, Update
from telegram.error import BadRequest, TimedOut from telegram.error import BadRequest, TimedOut
from telegram.ext import ( 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 = get_messages(chat_id)
messages.append({"role": "user", "content": user_text}) messages.append({"role": "user", "content": user_text})
# Send "typing" indicator # Send immediate "Thinking ..." placeholder so user knows the bot read their message
await update.message.chat.send_action("typing") thinking = await _safe_reply(update, "Thinking ...")
# Run the LLM loop (with tool calls) # Run the LLM loop (with tool calls)
max_rounds = 10 max_rounds = 10
@ -346,7 +347,7 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None
) )
except Exception as e: except Exception as e:
log.error("OpenAI API error: %s", 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 return
choice = response.choices[0] 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 {} json.loads(tc.function.arguments) if tc.function.arguments else {}
) )
log.info("Tool call: %s(%s)", tc.function.name, list(args.keys())) 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: try:
result = handle_tool_call(tc.function.name, args) result = handle_tool_call(tc.function.name, args)
except Exception as e: 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 # Continue the loop for the LLM to process tool results
await update.message.chat.send_action("typing")
continue continue
# No tool calls — we have a final text reply # 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: if len(messages) > 60:
messages[:] = messages[:1] + messages[-40:] messages[:] = messages[:1] + messages[-40:]
await _safe_reply(update, msg.content) await _safe_edit(thinking, update, msg.content)
return return
await _safe_reply( await _safe_edit(
update, "I ran out of steps — please try again with a simpler request." 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.""" """Reply to a message, falling back to send_message if the original is gone."""
try: try:
await update.message.reply_text(text, **kwargs) return await update.message.reply_text(text, **kwargs)
except BadRequest: except BadRequest:
log.warning("Could not reply to message, sending without reply") 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: async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: