"""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)