Aggiorna la gestione delle configurazioni nel bot Telegram: modifica gli stati della conversazione e aggiungi il supporto per la gestione dei messaggi.

This commit is contained in:
2025-10-07 15:09:51 +02:00
parent f5be7c82aa
commit d7e3dfef68

View File

@@ -3,24 +3,32 @@ from enum import Enum
from typing import Any from typing import Any
from agno.utils.log import log_info # type: ignore from agno.utils.log import log_info # type: ignore
from telegram import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User from telegram import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User
from telegram.ext import Application, CommandHandler, ContextTypes, ConversationHandler, ExtBot, JobQueue, CallbackQueryHandler from telegram.ext import Application, CommandHandler, ContextTypes, ConversationHandler, ExtBot, JobQueue, CallbackQueryHandler, MessageHandler, filters
from app.models import AppModels from app.models import AppModels
from app.predictor import PredictorStyle from app.predictor import PredictorStyle
# conversation states # Lo stato cambia in base al valore di ritorno delle funzioni async
class ConfigStates(Enum): # END state è già definito in telegram.ext.ConversationHandler
# Un semplice schema delle interazioni:
# /start
# ║
# V
# ╔══ CONFIGS <═════╗
# ║ ║ ╚══> SELECT_CONFIG
# ║ V
# ║ start_team (polling for updates)
# ║ ║
# ║ V
# ╚═══> END
CONFIGS, SELECT_CONFIG = range(2)
class ConfigsChat(Enum):
MODEL_TEAM = "Team Model" MODEL_TEAM = "Team Model"
MODEL_OUTPUT = "Output Model" MODEL_OUTPUT = "Output Model"
STRATEGY = "Strategy" STRATEGY = "Strategy"
# conversation stages (checkpoints) class ConfigsRun:
class Checkpoints(Enum):
CONFIGS = 1
TEAM_RUNNING = 2
END = 3
class RunConfigs:
model_team: AppModels model_team: AppModels
model_output: AppModels model_output: AppModels
strategy: PredictorStyle strategy: PredictorStyle
@@ -30,10 +38,12 @@ class RunConfigs:
self.model_output = AppModels.OLLAMA_QWEN_1B self.model_output = AppModels.OLLAMA_QWEN_1B
self.strategy = PredictorStyle.CONSERVATIVE self.strategy = PredictorStyle.CONSERVATIVE
class BotFunctions: class BotFunctions:
# In theory this is already thread-safe if run with CPython # In theory this is already thread-safe if run with CPython
users_req: dict[User, RunConfigs] = {} users_req: dict[User, ConfigsRun] = {}
app_models: list[AppModels] = AppModels.availables() app_models: list[AppModels] = AppModels.availables()
strategies: list[PredictorStyle] = list(PredictorStyle) strategies: list[PredictorStyle] = list(PredictorStyle)
@@ -55,17 +65,18 @@ class BotFunctions:
app = Application.builder().token(token).build() app = Application.builder().token(token).build()
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
per_message=False, # capire a cosa serve perchè da un warning quando parte il server
entry_points=[CommandHandler('start', BotFunctions.__start)], entry_points=[CommandHandler('start', BotFunctions.__start)],
states={ states={
Checkpoints.CONFIGS: [ CONFIGS: [
CallbackQueryHandler(BotFunctions.__model_team, pattern=ConfigStates.MODEL_TEAM.name), CallbackQueryHandler(BotFunctions.__model_team, pattern=ConfigsChat.MODEL_TEAM.name),
CallbackQueryHandler(BotFunctions.__model_output, pattern=ConfigStates.MODEL_OUTPUT.name), CallbackQueryHandler(BotFunctions.__model_output, pattern=ConfigsChat.MODEL_OUTPUT.name),
CallbackQueryHandler(BotFunctions.__strategy, pattern=ConfigStates.STRATEGY.name), CallbackQueryHandler(BotFunctions.__strategy, pattern=ConfigsChat.STRATEGY.name),
CallbackQueryHandler(BotFunctions.__next, pattern='^__next'), CallbackQueryHandler(BotFunctions.__cancel, pattern='^cancel$'),
CallbackQueryHandler(BotFunctions.__cancel, pattern='^cancel$') MessageHandler(filters.TEXT, BotFunctions.__start_team) # Any text message
], ],
Checkpoints.TEAM_RUNNING: [], SELECT_CONFIG: [
Checkpoints.END: [ CallbackQueryHandler(BotFunctions.__select_config, pattern='^__select_config:.*$'),
] ]
}, },
fallbacks=[CommandHandler('start', BotFunctions.__start)], fallbacks=[CommandHandler('start', BotFunctions.__start)],
@@ -81,18 +92,18 @@ class BotFunctions:
######################################## ########################################
@staticmethod @staticmethod
async def start_message(user: User, query: CallbackQuery | Message) -> None: async def start_message(user: User, query: CallbackQuery | Message) -> None:
confs = BotFunctions.users_req.setdefault(user, RunConfigs()) confs = BotFunctions.users_req.setdefault(user, ConfigsRun())
str_model_team = f"{ConfigStates.MODEL_TEAM.value}:\t\t {confs.model_team.name}" str_model_team = f"{ConfigsChat.MODEL_TEAM.value}: {confs.model_team.name}"
str_model_output = f"{ConfigStates.MODEL_OUTPUT.value}:\t\t {confs.model_output.name}" str_model_output = f"{ConfigsChat.MODEL_OUTPUT.value}:\t\t {confs.model_output.name}"
str_strategy = f"{ConfigStates.STRATEGY.value}:\t\t {confs.strategy.name}" str_strategy = f"{ConfigsChat.STRATEGY.value}:\t\t {confs.strategy.name}"
msg, keyboard = ( msg, keyboard = (
"Please choose an option or write your query", "Please choose an option or write your query",
InlineKeyboardMarkup([ InlineKeyboardMarkup([
[InlineKeyboardButton(str_model_team, callback_data=ConfigStates.MODEL_TEAM.name)], [InlineKeyboardButton(str_model_team, callback_data=ConfigsChat.MODEL_TEAM.name)],
[InlineKeyboardButton(str_model_output, callback_data=ConfigStates.MODEL_OUTPUT.name)], [InlineKeyboardButton(str_model_output, callback_data=ConfigsChat.MODEL_OUTPUT.name)],
[InlineKeyboardButton(str_strategy, callback_data=ConfigStates.STRATEGY.name)], [InlineKeyboardButton(str_strategy, callback_data=ConfigsChat.STRATEGY.name)],
[InlineKeyboardButton("Cancel", callback_data='cancel')] [InlineKeyboardButton("Cancel", callback_data='cancel')]
]) ])
) )
@@ -103,14 +114,14 @@ class BotFunctions:
await query.reply_text(msg, reply_markup=keyboard, parse_mode='MarkdownV2') await query.reply_text(msg, reply_markup=keyboard, parse_mode='MarkdownV2')
@staticmethod @staticmethod
async def handle_configs(update: Update, state: ConfigStates, msg: str | None = None) -> Checkpoints: async def handle_configs(update: Update, state: ConfigsChat, msg: str | None = None) -> int:
query, _ = await BotFunctions.handle_callbackquery(update) query, _ = await BotFunctions.handle_callbackquery(update)
models = [(m.name, f"__next:{state}:{m.name}") for m in BotFunctions.app_models] models = [(m.name, f"__select_config:{state}:{m.name}") for m in BotFunctions.app_models]
inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in models] inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in models]
await query.edit_message_text(msg or state.value, reply_markup=InlineKeyboardMarkup(inline_btns)) await query.edit_message_text(msg or state.value, reply_markup=InlineKeyboardMarkup(inline_btns))
return Checkpoints.CONFIGS return SELECT_CONFIG
@staticmethod @staticmethod
async def handle_callbackquery(update: Update) -> tuple[CallbackQuery, User]: async def handle_callbackquery(update: Update) -> tuple[CallbackQuery, User]:
@@ -119,62 +130,92 @@ class BotFunctions:
await query.answer() # Acknowledge the callback query await query.answer() # Acknowledge the callback query
return query, query.from_user return query, query.from_user
@staticmethod
async def handle_message(update: Update) -> tuple[Message, User]:
assert update.message and update.message.from_user, "Update message or user is None"
return update.message, update.message.from_user
######################################### #########################################
# Funzioni async per i comandi e messaggi # Funzioni async per i comandi e messaggi
######################################### #########################################
@staticmethod @staticmethod
async def __start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Checkpoints: async def __start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
assert update.message and update.message.from_user, "Update message or user is None" message, user = await BotFunctions.handle_message(update)
user = update.message.from_user
log_info(f"@{user.username} started the conversation.") log_info(f"@{user.username} started the conversation.")
await BotFunctions.start_message(user, update.message) await BotFunctions.start_message(user, message)
return Checkpoints.CONFIGS return CONFIGS
@staticmethod @staticmethod
async def __cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Checkpoints: async def __model_team(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
query, user = await BotFunctions.handle_callbackquery(update) return await BotFunctions.handle_configs(update, ConfigsChat.MODEL_TEAM)
log_info(f"@{user.username} canceled the conversation.")
if user in BotFunctions.users_req:
del BotFunctions.users_req[user]
await query.edit_message_text("Conversation canceled. Use /start to begin again.")
return Checkpoints.END
@staticmethod @staticmethod
async def __model_team(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Checkpoints: async def __model_output(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
return await BotFunctions.handle_configs(update, ConfigStates.MODEL_TEAM) return await BotFunctions.handle_configs(update, ConfigsChat.MODEL_OUTPUT)
@staticmethod @staticmethod
async def __model_output(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Checkpoints: async def __strategy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
return await BotFunctions.handle_configs(update, ConfigStates.MODEL_OUTPUT)
@staticmethod
async def __strategy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Checkpoints:
query, _ = await BotFunctions.handle_callbackquery(update) query, _ = await BotFunctions.handle_callbackquery(update)
strategies = [(s.name, f"__next:{ConfigStates.STRATEGY}:{s.name}") for s in BotFunctions.strategies] strategies = [(s.name, f"__select_config:{ConfigsChat.STRATEGY}:{s.name}") for s in BotFunctions.strategies]
inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in strategies] inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in strategies]
await query.edit_message_text("Select a strategy", reply_markup=InlineKeyboardMarkup(inline_btns)) await query.edit_message_text("Select a strategy", reply_markup=InlineKeyboardMarkup(inline_btns))
return Checkpoints.CONFIGS return SELECT_CONFIG
@staticmethod @staticmethod
async def __next(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Checkpoints: async def __select_config(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
query, user = await BotFunctions.handle_callbackquery(update) query, user = await BotFunctions.handle_callbackquery(update)
log_info(f"@{user.username} --> {query.data}") log_info(f"@{user.username} --> {query.data}")
req = BotFunctions.users_req[user] req = BotFunctions.users_req[user]
_, state, model_name = str(query.data).split(':') _, state, model_name = str(query.data).split(':')
if state == str(ConfigStates.MODEL_TEAM): if state == str(ConfigsChat.MODEL_TEAM):
req.model_team = AppModels[model_name] req.model_team = AppModels[model_name]
if state == str(ConfigStates.MODEL_OUTPUT): if state == str(ConfigsChat.MODEL_OUTPUT):
req.model_output = AppModels[model_name] req.model_output = AppModels[model_name]
if state == str(ConfigStates.STRATEGY): if state == str(ConfigsChat.STRATEGY):
req.strategy = PredictorStyle[model_name] req.strategy = PredictorStyle[model_name]
await BotFunctions.start_message(user, query) await BotFunctions.start_message(user, query)
return Checkpoints.CONFIGS return CONFIGS
@staticmethod
async def __start_team(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
message, user = await BotFunctions.handle_message(update)
msg2 = await message.reply_text("Elaborating your request...")
confs = BotFunctions.users_req[user]
log_info(f"@{user.username} started the team with [{confs.model_team}, {confs.model_output}, {confs.strategy}]")
await BotFunctions.__run_team(confs, msg2)
return ConversationHandler.END
@staticmethod
async def __cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
query, user = await BotFunctions.handle_callbackquery(update)
log_info(f"@{user.username} canceled the conversation.")
if user in BotFunctions.users_req:
del BotFunctions.users_req[user]
await query.edit_message_text("Conversation canceled. Use /start to begin again.")
return ConversationHandler.END
@staticmethod
async def __run_team(confs: ConfigsRun, msg: Message) -> None:
# TODO fare il run effettivo del team
import asyncio
# Simulate a long-running task
n_simulations = 3
for i in range(n_simulations):
await msg.edit_text(f"Working... {i+1}/{n_simulations}")
await asyncio.sleep(2)
await msg.edit_text("Team work completed.")
if __name__ == "__main__": if __name__ == "__main__":
from dotenv import load_dotenv from dotenv import load_dotenv