"""Módulo de interface do usuário com Gradio.""" import gradio as gr from typing import Dict, Any, Optional, Tuple from config.config import UIConfig, AppConfig, ExampleData from src.utils.utils import ( DateTimeUtils, NumberUtils, ConfidenceUtils, ActionUtils, SentimentUtils, FormatUtils ) from src.ui.theme_manager import theme_manager, ThemeType from src.ui.dark_theme_components import DarkThemeUIComponents # Importar sistema de logging try: from src.utils.request_logger import log_requests_responses except ImportError: log_requests_responses = None class UIComponents: """Componentes da interface do usuário.""" @staticmethod def create_header() -> str: """Cria o cabeçalho da aplicação com tema dinâmico.""" return DarkThemeUIComponents.create_header() @staticmethod def format_harmonic_patterns(analysis_result: Dict[str, Any]) -> str: """Formata padrões harmônicos detectados com tema dinâmico.""" return DarkThemeUIComponents.format_harmonic_patterns(analysis_result) @staticmethod def format_fibonacci_alerts(analysis_result: Dict[str, Any]) -> str: """Formata alertas de Fibonacci com tema dinâmico.""" return DarkThemeUIComponents.format_fibonacci_alerts(analysis_result) @staticmethod def format_bot_analysis_result(analysis_result: Dict[str, Any]) -> str: """Formata resultado específico da análise do bot externo com tema dinâmico.""" return DarkThemeUIComponents.format_bot_analysis_result(analysis_result) @staticmethod def format_bot_response(response: str, analysis_result: Dict[str, Any] = None) -> str: """Formata resposta do bot com tema dinâmico.""" return DarkThemeUIComponents.format_bot_response(response, analysis_result) @staticmethod def create_input_section() -> gr.Column: """Cria seção de entrada de dados.""" with gr.Column() as input_section: gr.HTML("""

📊 Dados de Mercado

Cole os dados do ativo ou digite manualmente os valores

""") # Abas para diferentes tipos de entrada with gr.Tabs(): with gr.Tab("📊 Análise Manual"): symbol_input = gr.Textbox( label="Símbolo do Ativo", placeholder="Ex: BTCUSDT", lines=1 ) price_input = gr.Number( label="Preço Atual", placeholder="Ex: 45000.50" ) volume_input = gr.Number( label="Volume (Opcional)", placeholder="Ex: 1000000" ) sentiment_input = gr.Textbox( label="Texto para Análise de Sentimento (Opcional)", placeholder="Ex: Notícias ou comentários sobre o ativo", lines=3 ) analyze_manual_btn = gr.Button( "🔍 Analisar Manualmente", variant="primary", size="lg" ) with gr.Tab("🤖 Log do Bot"): market_input = gr.Textbox( label="Log do Bot de Trading", placeholder=ExampleData.SAMPLE_MARKET_DATA['bullish'], lines=8, max_lines=15 ) analyze_btn = gr.Button( "🔍 Analisar Log do Bot", variant="primary", size="lg" ) return input_section, market_input, analyze_btn, symbol_input, price_input, volume_input, sentiment_input, analyze_manual_btn @staticmethod def create_output_section() -> Tuple[gr.Column, Dict[str, Any]]: """Cria seção de saída de resultados.""" outputs = {} with gr.Column() as output_section: # Status do modelo de IA outputs['ai_status'] = gr.HTML( UIComponents._get_ai_status_html(available=False) ) # Resultado principal outputs['main_result'] = gr.HTML() # Abas de detalhes with gr.Tabs(): # Aba de Análise Técnica with gr.Tab("📊 Análise Técnica"): outputs['technical_analysis'] = gr.HTML() # Aba de Análise de Sentimento with gr.Tab("🧠 Análise de Sentimento"): outputs['sentiment_analysis'] = gr.HTML() # Aba de Recomendações with gr.Tab("💡 Recomendações"): outputs['recommendations'] = gr.HTML() # Aba de Padrões Harmônicos with gr.Tab("🎵 Padrões Harmônicos"): outputs['harmonic_patterns'] = gr.HTML() # Aba de Alertas Fibonacci with gr.Tab("📐 Fibonacci"): outputs['fibonacci_alerts'] = gr.HTML() # Aba de Dados Brutos with gr.Tab("🔍 Dados Detalhados"): outputs['raw_data'] = gr.JSON() return output_section, outputs @staticmethod def create_footer(model_info: Optional[Dict[str, Any]] = None) -> str: """Cria rodapé da aplicação com tema dinâmico.""" return DarkThemeUIComponents.create_footer(model_info) @staticmethod def _get_ai_status_html(available: bool, model_description: str = "") -> str: """Gera HTML para status da IA com tema dinâmico.""" return DarkThemeUIComponents._get_ai_status_html(available, model_description) class ResultFormatter: """Formatador de resultados para a interface.""" @staticmethod def format_main_result(analysis_result: Dict[str, Any]) -> str: """Formata resultado principal da análise.""" action = analysis_result.get('action', 'AGUARDAR') confidence = analysis_result.get('confidence', 0) market_data = analysis_result.get('market_data', {}) # Obter informações da ação action_emojis = ActionUtils.get_action_emojis(action) action_color = ActionUtils.get_action_color(action) confidence_level = ConfidenceUtils.get_confidence_level(confidence) confidence_bar = ConfidenceUtils.generate_confidence_bar(confidence) # Formatação do preço price = market_data.get('price', 0) variation = market_data.get('variation', 0) formatted_price = NumberUtils.format_price(price) formatted_variation = NumberUtils.format_percentage(variation) # Cor da variação variation_color = "#28a745" if variation >= 0 else "#dc3545" return f"""
{action_emojis['main']}

{action_emojis['action']} {action}

PREÇO ATUAL
{formatted_price}
{formatted_variation}
CONFIANÇA
{confidence}%
{confidence_level}
NÍVEL DE CONFIANÇA
{confidence_bar}
""" @staticmethod def format_technical_analysis(analysis_result: Dict[str, Any]) -> str: """Formata análise técnica.""" market_data = analysis_result.get('market_data', {}) signals = analysis_result.get('signals', []) # Resumo dos dados de mercado market_summary = FormatUtils.format_market_summary(market_data) # Lista de sinais signals_list = FormatUtils.format_signal_list(signals) return f"""

📊 Indicadores Técnicos

{market_summary}

🎯 Sinais Detectados

{signals_list}
""" @staticmethod def format_sentiment_analysis(analysis_result: Dict[str, Any]) -> str: """Formata análise de sentimento com tema dinâmico.""" return DarkThemeUIComponents.format_sentiment_analysis(analysis_result) @staticmethod def format_recommendations(analysis_result: Dict[str, Any]) -> str: """Formata recomendações de trading com tema dinâmico.""" return DarkThemeUIComponents.format_recommendations(analysis_result) @staticmethod def format_harmonic_patterns(analysis_result: Dict[str, Any]) -> str: """Formata padrões harmônicos detectados com tema dinâmico.""" return DarkThemeUIComponents.format_harmonic_patterns(analysis_result) @staticmethod def format_fibonacci_alerts(analysis_result: Dict[str, Any]) -> str: """Formata alertas de Fibonacci com tema dinâmico.""" return DarkThemeUIComponents.format_fibonacci_alerts(analysis_result) class GradioInterface: """Interface principal do Gradio.""" def __init__(self, analysis_function, model_info: Optional[Dict[str, Any]] = None): """Inicializa interface.""" self.analysis_function = analysis_function self.model_info = model_info or {'available': False} self.interface = None @log_requests_responses("analyze_market") if log_requests_responses else lambda f: f def analyze_market(self, symbol, price, volume, sentiment_text): """Função principal de análise""" try: # Validar entrada if not symbol or not price: return "❌ Erro: Símbolo e preço são obrigatórios" price = float(price) volume = float(volume) if volume else 0 # Criar dados de mercado market_data = { 'price': price, 'variation': 0, # Será calculado se necessário 'rsi': 50, # Valor padrão 'ema_trend': 'NEUTRO', 'bb_position': 'DENTRO', 'volume': volume } # Executar análise result = self.analysis_function(f"{symbol}: {price}") # Formatar resultado return self.format_analysis_result(result, sentiment_text, symbol) except Exception as e: return f"❌ Erro na análise: {str(e)}" @log_requests_responses("analyze_bot_log") if log_requests_responses else lambda f: f def analyze_bot_log(self, log_content): """Função para analisar logs do bot externo""" try: if not log_content.strip(): return "❌ Erro: Conteúdo do log é obrigatório" # Executar análise do log result = self.analysis_function(log_content) if 'error' in result: return f"❌ {result['error']}" # Formatar resultado específico do bot return self.format_bot_analysis_result(result) except Exception as e: return f"❌ Erro na análise do log: {str(e)}" def format_analysis_result(self, result, sentiment_text, symbol): """Formata resultado da análise""" return ResultFormatter.format_main_result(result) def format_bot_analysis_result(self, result): """Formata resultado específico da análise do bot""" return ResultFormatter.format_main_result(result) def create_interface(self) -> gr.Blocks: """Cria interface completa do Gradio.""" with gr.Blocks( title=AppConfig.APP_TITLE, theme=gr.themes.Base(), css=self._get_custom_css() ) as interface: # Cabeçalho com botão de tema with gr.Row(): with gr.Column(scale=4): gr.HTML(UIComponents.create_header()) with gr.Column(scale=1, min_width=120): theme_toggle_btn = gr.Button( "🌙 Tema Escuro" if theme_manager.current_theme == ThemeType.LIGHT else "☀️ Tema Claro", variant="secondary", size="sm", elem_id="theme-toggle-btn" ) # Layout principal with gr.Row(): # Coluna de entrada (40%) with gr.Column(scale=2): input_section, market_input, analyze_btn, symbol_input, price_input, volume_input, sentiment_input, analyze_manual_btn = UIComponents.create_input_section() # Coluna de saída (60%) with gr.Column(scale=3): output_section, outputs = UIComponents.create_output_section() # Rodapé gr.HTML(UIComponents.create_footer(self.model_info)) # Configurar eventos de análise analyze_btn.click( fn=self._analyze_wrapper, inputs=[market_input], outputs=[ outputs['ai_status'], outputs['main_result'], outputs['technical_analysis'], outputs['sentiment_analysis'], outputs['recommendations'], outputs['harmonic_patterns'], outputs['fibonacci_alerts'], outputs['raw_data'] ] ) analyze_manual_btn.click( fn=self._analyze_manual_wrapper, inputs=[symbol_input, price_input, volume_input, sentiment_input], outputs=[ outputs['ai_status'], outputs['main_result'], outputs['technical_analysis'], outputs['sentiment_analysis'], outputs['recommendations'], outputs['harmonic_patterns'], outputs['fibonacci_alerts'], outputs['raw_data'] ] ) # Componente HTML invisível para injeção de JavaScript theme_update_html = gr.HTML(value="", visible=False) # Configurar evento de alternância de tema theme_toggle_btn.click( fn=self._toggle_theme, outputs=[theme_toggle_btn, theme_update_html] ) # Atualizar status da IA na inicialização interface.load( fn=lambda: UIComponents._get_ai_status_html( self.model_info.get('available', False), self.model_info.get('description', '') ), outputs=[outputs['ai_status']] ) # Aplicar tema padrão na inicialização interface.load( fn=self._apply_initial_theme, outputs=[theme_update_html] ) self.interface = interface return interface @log_requests_responses("_analyze_wrapper") if log_requests_responses else lambda f: f def _analyze_wrapper(self, market_input: str) -> Tuple[str, str, str, str, str, str, str, Dict[str, Any]]: """Wrapper para função de análise com formatação de saída.""" try: # Executar análise analysis_result = self.analysis_function(market_input) # Formatear resultados ai_status = UIComponents._get_ai_status_html( self.model_info.get('available', False), self.model_info.get('description', '') ) main_result = ResultFormatter.format_main_result(analysis_result) technical_analysis = ResultFormatter.format_technical_analysis(analysis_result) sentiment_analysis = ResultFormatter.format_sentiment_analysis(analysis_result) recommendations = ResultFormatter.format_recommendations(analysis_result) harmonic_patterns = ResultFormatter.format_harmonic_patterns(analysis_result) fibonacci_alerts = ResultFormatter.format_fibonacci_alerts(analysis_result) # Dados brutos para debug raw_data = { 'timestamp': DateTimeUtils.get_current_datetime(), 'analysis_result': analysis_result } return ( ai_status, main_result, technical_analysis, sentiment_analysis, recommendations, harmonic_patterns, fibonacci_alerts, raw_data ) except Exception as e: error_msg = f"Erro na análise: {str(e)}" error_html = f"""

❌ Erro na Análise

{error_msg}

""" return ( UIComponents._get_ai_status_html(False), error_html, error_html, error_html, error_html, error_html, error_html, {'error': error_msg} ) @log_requests_responses("_analyze_manual_wrapper") if log_requests_responses else lambda f: f def _analyze_manual_wrapper(self, symbol: str, price: float, volume: float, sentiment_text: str) -> Tuple[str, str, str, str, str, str, str, Dict[str, Any]]: """Wrapper para análise manual com formatação de saída.""" try: # Validar entrada if not symbol or not price: raise ValueError("Símbolo e preço são obrigatórios") # Criar entrada formatada market_input = f"{symbol}: Preço={price}, Volume={volume or 0}" if sentiment_text: market_input += f", Sentimento={sentiment_text}" # Executar análise usando o wrapper padrão return self._analyze_wrapper(market_input) except Exception as e: error_msg = f"Erro na análise manual: {str(e)}" error_html = f"""

❌ Erro na Análise Manual

{error_msg}

""" return ( UIComponents._get_ai_status_html(False), error_html, error_html, error_html, error_html, error_html, error_html, {'error': error_msg} ) def _get_custom_css(self) -> str: """Retorna CSS customizado para a interface com tema dinâmico.""" return theme_manager.get_custom_css() def _toggle_theme(self) -> Tuple[str, str]: """Alterna entre temas claro e escuro.""" new_theme = theme_manager.toggle_theme() # Atualizar CSS dinamicamente new_css = theme_manager.get_custom_css() # JavaScript para atualizar o CSS js_code = f""" // Remover CSS antigo const oldStyle = document.getElementById('dynamic-theme-css'); if (oldStyle) {{ oldStyle.remove(); }} // Adicionar novo CSS const style = document.createElement('style'); style.id = 'dynamic-theme-css'; style.textContent = `{new_css.replace('`', '\\`').replace('\n', '\\n')}`; document.head.appendChild(style); // Aplicar tema ao body document.body.setAttribute('data-theme', '{new_theme.value}'); """ # HTML com JavaScript para injeção html_update = f""" """ # Retornar novo texto do botão e HTML de atualização button_text = "🌙 Tema Escuro" if new_theme == ThemeType.LIGHT else "☀️ Tema Claro" return button_text, html_update def _apply_initial_theme(self) -> str: """Aplica o tema padrão na inicialização da interface.""" current_theme = theme_manager.current_theme current_css = theme_manager.get_custom_css() # JavaScript para aplicar tema inicial js_code = f""" // Aplicar tema inicial document.body.setAttribute('data-theme', '{current_theme.value}'); // Adicionar CSS do tema const style = document.createElement('style'); style.id = 'dynamic-theme-css'; style.textContent = `{current_css.replace('`', '\\`').replace('\n', '\\n')}`; document.head.appendChild(style); """ return f""" """ def launch(self, **kwargs) -> None: """Lança a interface.""" if not self.interface: self.create_interface() default_kwargs = { 'server_name': '127.0.0.1', 'server_port': 7860, 'share': False, 'show_error': True, 'max_threads': 10, 'show_api': False, 'ssr_mode': False # Compatibilidade com Hugging Face Spaces } # Mesclar argumentos padrão com os fornecidos launch_kwargs = {**default_kwargs, **kwargs} self.interface.launch(**launch_kwargs)