diff --git a/src/app/__main__.py b/src/app/__main__.py index 578ef35..93b6174 100644 --- a/src/app/__main__.py +++ b/src/app/__main__.py @@ -3,12 +3,17 @@ from dotenv import load_dotenv from agno.utils.log import log_info #type: ignore from app.utils import ChatManager from app.agents import Pipeline +from app.utils.telegram_app import BotFunctions -if __name__ == "__main__": - # Inizializzazioni - load_dotenv() - pipeline = Pipeline() +# Disabilita TUTTI i log di livello inferiore a WARNING +# La maggior parte arrivano da httpx +import logging +logging.getLogger().setLevel(logging.WARNING) + + + +def gradio_app(pipeline: Pipeline, server: str = "0.0.0.0", port: int = 8000) -> str: chat = ChatManager() ######################################## @@ -73,7 +78,18 @@ if __name__ == "__main__": save_btn.click(save_current_chat, inputs=None, outputs=None) load_btn.click(load_previous_chat, inputs=None, outputs=[chatbot, chatbot]) - server, port = ("0.0.0.0", 8000) # 0.0.0.0 per accesso esterno (Docker) - server_log = "localhost" if server == "0.0.0.0" else server - log_info(f"Starting UPO AppAI Chat on http://{server_log}:{port}") # noqa - demo.launch(server_name=server, server_port=port, quiet=True) + _app, local, share = demo.launch(server_name=server, server_port=port, quiet=True, prevent_thread_lock=True) + log_info(f"UPO AppAI Chat is running on {local} and {share}") + return share + + + +if __name__ == "__main__": + load_dotenv() # Carica le variabili d'ambiente dal file .env + + pipeline = Pipeline() + url = gradio_app(pipeline) + + telegram = BotFunctions.create_bot(pipeline, url) + telegram.run_polling() + diff --git a/src/app/utils/__init__.py b/src/app/utils/__init__.py index 1a511c1..96cfda9 100644 --- a/src/app/utils/__init__.py +++ b/src/app/utils/__init__.py @@ -1,5 +1,6 @@ from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info from app.utils.wrapper_handler import WrapperHandler from app.utils.chat_manager import ChatManager +from app.utils.telegram_app import BotFunctions -__all__ = ["aggregate_history_prices", "aggregate_product_info", "WrapperHandler", "ChatManager"] +__all__ = ["aggregate_history_prices", "aggregate_product_info", "WrapperHandler", "ChatManager", "BotFunctions"] diff --git a/src/app/utils/telegram_app.py b/src/app/utils/telegram_app.py index 45f945e..945a7d6 100644 --- a/src/app/utils/telegram_app.py +++ b/src/app/utils/telegram_app.py @@ -1,4 +1,6 @@ import os +import json +import httpx from enum import Enum from typing import Any from agno.utils.log import log_info # type: ignore @@ -6,6 +8,7 @@ from telegram import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, from telegram.constants import ChatAction from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, ConversationHandler, ExtBot, JobQueue, MessageHandler, filters from app.agents import AppModels, PredictorStyle +from app.agents.pipeline import Pipeline # Lo stato cambia in base al valore di ritorno delle funzioni async # END state è già definito in telegram.ext.ConversationHandler @@ -29,9 +32,9 @@ class ConfigsChat(Enum): class ConfigsRun: def __init__(self): - self.model_team = BotFunctions.app_models[0] - self.model_output = BotFunctions.app_models[0] - self.strategy = PredictorStyle.CONSERVATIVE + self.model_team = BotFunctions.pipeline.available_models[0] + self.model_output = BotFunctions.pipeline.available_models[0] + self.strategy = BotFunctions.pipeline.all_styles[0] self.user_query = "" @@ -39,13 +42,12 @@ class ConfigsRun: class BotFunctions: # In theory this is already thread-safe if run with CPython - users_req: dict[User, ConfigsRun] = {} - app_models: list[AppModels] = AppModels.availables() - strategies: list[PredictorStyle] = list(PredictorStyle) + users_req: dict[User, ConfigsRun] + pipeline: Pipeline # che incubo di typing @staticmethod - def create_bot() -> Application[ExtBot[None], ContextTypes.DEFAULT_TYPE, dict[str, Any], dict[str, Any], dict[str, Any], JobQueue[ContextTypes.DEFAULT_TYPE]]: + def create_bot(pipeline: Pipeline, miniapp_url: str | None = None) -> Application[ExtBot[None], ContextTypes.DEFAULT_TYPE, dict[str, Any], dict[str, Any], dict[str, Any], JobQueue[ContextTypes.DEFAULT_TYPE]]: """ Create a Telegram bot application instance. Assumes the TELEGRAM_BOT_TOKEN environment variable is set. @@ -54,10 +56,13 @@ class BotFunctions: Raises: AssertionError: If the TELEGRAM_BOT_TOKEN environment variable is not set. """ + BotFunctions.users_req = {} + BotFunctions.pipeline = pipeline token = os.getenv("TELEGRAM_BOT_TOKEN", '') assert token, "TELEGRAM_BOT_TOKEN environment variable not set" + if miniapp_url: BotFunctions.update_miniapp_url(miniapp_url, token) app = Application.builder().token(token).build() conv_handler = ConversationHandler( @@ -113,7 +118,7 @@ class BotFunctions: async def handle_configs(update: Update, state: ConfigsChat, msg: str | None = None) -> int: query, _ = await BotFunctions.handle_callbackquery(update) - models = [(m.name, f"__select_config:{state}:{m.name}") for m in BotFunctions.app_models] + models = [(m.name, f"__select_config:{state}:{m.name}") for m in BotFunctions.pipeline.available_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)) @@ -131,6 +136,20 @@ class BotFunctions: assert update.message and update.message.from_user, "Update message or user is None" return update.message, update.message.from_user + @staticmethod + def update_miniapp_url(url: str, token: str) -> None: + try: + endpoint = f"https://api.telegram.org/bot{token}/setChatMenuButton" + payload = {"menu_button": json.dumps({ + "type": "web_app", + "text": "Apri Mini App", # Il testo che appare sul pulsante + "web_app": { + "url": url + } + })} + httpx.post(endpoint, data=payload) + except httpx.HTTPError as e: + log_info(f"Failed to update mini app URL: {e}") ######################################### # Funzioni async per i comandi e messaggi @@ -154,7 +173,7 @@ class BotFunctions: async def __strategy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: query, _ = await BotFunctions.handle_callbackquery(update) - strategies = [(s.name, f"__select_config:{ConfigsChat.STRATEGY}:{s.name}") for s in BotFunctions.strategies] + strategies = [(s.name, f"__select_config:{ConfigsChat.STRATEGY}:{s.name}") for s in BotFunctions.pipeline.all_styles] 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)) @@ -238,13 +257,3 @@ class BotFunctions: document = io.BytesIO(report_content.encode('utf-8')) await bot.send_document(chat_id=chat_id, document=document, filename="report.md", parse_mode='MarkdownV2', caption=full_message) - - - - -if __name__ == "__main__": - from dotenv import load_dotenv - load_dotenv() - bot_app = BotFunctions.create_bot() - bot_app.run_polling() -