Spaces:
Sleeping
Sleeping
| """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.""" | |
| def create_header() -> str: | |
| """Cria o cabeçalho da aplicação com tema dinâmico.""" | |
| return DarkThemeUIComponents.create_header() | |
| 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) | |
| 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) | |
| 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) | |
| 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) | |
| def create_input_section() -> gr.Column: | |
| """Cria seção de entrada de dados.""" | |
| with gr.Column() as input_section: | |
| gr.HTML(""" | |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff;"> | |
| <h3 style="margin: 0 0 10px 0; color: #495057;">📊 Dados de Mercado</h3> | |
| <p style="margin: 0; color: #6c757d; font-size: 0.9em;"> | |
| Cole os dados do ativo ou digite manualmente os valores | |
| </p> | |
| </div> | |
| """) | |
| # 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 | |
| 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 | |
| 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) | |
| 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.""" | |
| 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""" | |
| <div style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 12px; padding: 25px; margin: 15px 0; border: 2px solid #dee2e6;"> | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <div style="font-size: 3em; margin-bottom: 10px;">{action_emojis['main']}</div> | |
| <h2 style="margin: 0; color: {action_color}; font-size: 2em; text-transform: uppercase; letter-spacing: 1px;"> | |
| {action_emojis['action']} {action} | |
| </h2> | |
| </div> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;"> | |
| <div style="text-align: center; padding: 15px; background: var(--bg, white); border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 0.9em; color: #6c757d; margin-bottom: 5px;">PREÇO ATUAL</div> | |
| <div style="font-size: 1.8em; font-weight: bold; color: #495057;">{formatted_price}</div> | |
| <div style="font-size: 1.1em; color: {variation_color}; font-weight: 600;">{formatted_variation}</div> | |
| </div> | |
| <div style="text-align: center; padding: 15px; background: var(--bg, white); border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 0.9em; color: #6c757d; margin-bottom: 5px;">CONFIANÇA</div> | |
| <div style="font-size: 1.8em; font-weight: bold; color: #495057;">{confidence}%</div> | |
| <div style="font-size: 0.9em; color: #6c757d;">{confidence_level}</div> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 15px;"> | |
| <div style="font-size: 0.9em; color: #6c757d; margin-bottom: 8px;">NÍVEL DE CONFIANÇA</div> | |
| <div style="font-family: monospace; font-size: 1.2em; letter-spacing: 2px; color: #495057;">{confidence_bar}</div> | |
| </div> | |
| </div> | |
| """ | |
| 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""" | |
| <div style="background: var(--bg, white); border-radius: 8px; padding: 20px; border: 1px solid #dee2e6;"> | |
| <h3 style="color: #495057; margin-top: 0; border-bottom: 2px solid #007bff; padding-bottom: 10px;"> | |
| 📊 Indicadores Técnicos | |
| </h3> | |
| <div style="background: var(--surface, #f8f9fa); padding: 15px; border-radius: 6px; margin-bottom: 20px;"> | |
| {market_summary} | |
| </div> | |
| <h4 style="color: #495057; margin-bottom: 15px;">🎯 Sinais Detectados</h4> | |
| <div style="background: var(--surface, #f8f9fa); padding: 15px; border-radius: 6px; font-family: monospace; white-space: pre-line;"> | |
| {signals_list} | |
| </div> | |
| </div> | |
| """ | |
| 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) | |
| def format_recommendations(analysis_result: Dict[str, Any]) -> str: | |
| """Formata recomendações de trading com tema dinâmico.""" | |
| return DarkThemeUIComponents.format_recommendations(analysis_result) | |
| 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) | |
| 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 | |
| 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)}" | |
| 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 | |
| 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""" | |
| <div style="background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 8px; padding: 15px; color: #721c24;"> | |
| <h4 style="margin-top: 0;">❌ Erro na Análise</h4> | |
| <p style="margin: 0;">{error_msg}</p> | |
| </div> | |
| """ | |
| return ( | |
| UIComponents._get_ai_status_html(False), | |
| error_html, | |
| error_html, | |
| error_html, | |
| error_html, | |
| error_html, | |
| error_html, | |
| {'error': error_msg} | |
| ) | |
| 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""" | |
| <div style="background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 8px; padding: 15px; color: #721c24;"> | |
| <h4 style="margin-top: 0;">❌ Erro na Análise Manual</h4> | |
| <p style="margin: 0;">{error_msg}</p> | |
| </div> | |
| """ | |
| 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""" | |
| <script> | |
| setTimeout(() => {{ | |
| {js_code} | |
| }}, 100); | |
| </script> | |
| """ | |
| # 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""" | |
| <script> | |
| setTimeout(() => {{ | |
| {js_code} | |
| }}, 50); | |
| </script> | |
| """ | |
| 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) |