Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import re | |
| import numpy as np | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| import joblib | |
| import pickle | |
| import os | |
| from datetime import datetime | |
| # ====================== CONFIGURAÇÕES ====================== | |
| AREAS = { | |
| "IP": "Instituto de Pesquisa", | |
| "FD": "Faculdade de Direito", | |
| "FE": "Faculdade de Educação", | |
| "Reitoria": "Reitoria" | |
| } | |
| TIPO_MANIFESTACAO = ["Reclamação", "Sugestão"] | |
| # ====================== SISTEMA DE VALIDAÇÃO ====================== | |
| class ValidadorManifestacoes: | |
| def __init__(self): | |
| self.carregar_datasets() | |
| self.preparar_modelos() | |
| def carregar_datasets(self): | |
| """Carrega os datasets de validação""" | |
| # Palavras ofensivas padrão | |
| self.palavras_ofensivas = [ | |
| "idiota", "burro", "imbecil", "estupido", "cretino", "analfabeto", | |
| "incompetente", "ignorante", "nojento", "asco", "vergonha", "lixo" | |
| ] | |
| # Frases sem sentido padrão | |
| self.frases_sem_sentido = [ | |
| "asdfghjkl", "qwertyuiop", "123456789", "aaa bbb ccc", | |
| "teste teste", "xyz abc", "mmmm nnnn oooo" | |
| ] | |
| # Padrões de frases incompletas | |
| self.padroes_incompletos = [ | |
| "e depois não sei", "mas não", "porque sim", "sem motivo", | |
| "só isso", "nada mais", "e pronto" | |
| ] | |
| def preparar_modelos(self): | |
| """Prepara modelos ML para validação de sentido""" | |
| self.criar_modelo_sentido_padrao() | |
| def criar_modelo_sentido_padrao(self): | |
| """Cria modelo padrão se não existir""" | |
| # Dados de treino para frases com/sem sentido | |
| frases_com_sentido = [ | |
| "gostaria de reclamar do atendimento", | |
| "sugiro melhorar a limpeza", | |
| "o serviço está muito lento", | |
| "precisamos de mais computadores", | |
| "a internet está caindo muito", | |
| "as salas de aula precisam de ar condicionado", | |
| "a biblioteca deveria ficar aberta até mais tarde", | |
| "os banheiros precisam de manutenção urgente" | |
| ] | |
| frases_sem_sentido = [ | |
| "asdf ghjk qwert", | |
| "123 456 789", | |
| "teste teste teste", | |
| "aaa bbb ccc ddd", | |
| "xyz abc def ghi", | |
| "mmmm nnnn oooo pppp", | |
| "111 222 333 444", | |
| "qqqq wwww eeee rrrr" | |
| ] | |
| textos = frases_com_sentido + frases_sem_sentido | |
| labels = [1] * len(frases_com_sentido) + [0] * len(frases_sem_sentido) | |
| self.vectorizer_sentido = TfidfVectorizer(max_features=50) | |
| X = self.vectorizer_sentido.fit_transform(textos) | |
| self.modelo_sentido = LogisticRegression() | |
| self.modelo_sentido.fit(X, labels) | |
| print("✅ Modelo de validação de sentido criado com sucesso!") | |
| def validar_ofensas(self, texto): | |
| """Verifica palavras ofensivas""" | |
| texto_lower = texto.lower() | |
| ofensas_encontradas = [] | |
| for palavra in self.palavras_ofensivas: | |
| if re.search(r'\b' + re.escape(palavra) + r'\b', texto_lower): | |
| ofensas_encontradas.append(palavra) | |
| return { | |
| "tem_ofensa": len(ofensas_encontradas) > 0, | |
| "palavras": ofensas_encontradas, | |
| "nivel": "ALTO" if len(ofensas_encontradas) > 2 else "MÉDIO" if len(ofensas_encontradas) > 0 else "BAIXO" | |
| } | |
| def validar_sentido(self, texto): | |
| """Verifica se a frase faz sentido usando ML""" | |
| if len(texto.split()) < 3: | |
| return {"tem_problema": True, "tipo": "FRASE_MUITO_CURTA"} | |
| # Verificar padrões de spam | |
| texto_lower = texto.lower() | |
| for padrao in self.frases_sem_sentido: | |
| if padrao in texto_lower: | |
| return {"tem_problema": True, "tipo": "PADRAO_SPAM"} | |
| # Usar modelo ML | |
| try: | |
| X = self.vectorizer_sentido.transform([texto]) | |
| predicao = self.modelo_sentido.predict(X)[0] | |
| proba = self.modelo_sentido.predict_proba(X)[0][1] | |
| if predicao == 0 or proba < 0.3: | |
| return {"tem_problema": True, "tipo": "SEM_SENTIDO", "confianca": proba} | |
| else: | |
| return {"tem_problema": False, "tipo": "OK", "confianca": proba} | |
| except Exception as e: | |
| print(f"⚠️ Erro no modelo de sentido: {e}") | |
| return {"tem_problema": False, "tipo": "OK", "confianca": 0.5} | |
| def validar_completude(self, texto): | |
| """Verifica se a frase está completa""" | |
| texto_limpo = texto.strip() | |
| # Verifica se termina com pontuação | |
| termina_pontuacao = texto_limpo.endswith(('.', '!', '?', ';', ':')) | |
| # Verifica tamanho mínimo | |
| palavras = texto_limpo.split() | |
| tem_tamanho_minimo = len(palavras) >= 5 | |
| # Verifica padrões incompletos | |
| texto_lower = texto_limpo.lower() | |
| tem_padrao_incompleto = any( | |
| padrao in texto_lower for padrao in self.padroes_incompletos | |
| ) | |
| # Verifica se começa com conectores e não continua | |
| conectores = ["e", "mas", "porque", "então", "porém", "contudo"] | |
| primeira_palavra = palavras[0].lower() if palavras else "" | |
| comeca_conector = primeira_palavra in conectores | |
| problemas = [] | |
| if not termina_pontuacao: | |
| problemas.append("FALTA_PONTUACAO") | |
| if not tem_tamanho_minimo: | |
| problemas.append("FRASE_MUITO_CURTA") | |
| if tem_padrao_incompleto: | |
| problemas.append("PADRAO_INCOMPLETO") | |
| if comeca_conector and len(palavras) < 4: | |
| problemas.append("INICIO_INCOMPLETO") | |
| return { | |
| "tem_problema": len(problemas) > 0, | |
| "problemas": problemas, | |
| "pontuacao_ok": termina_pontuacao, | |
| "tamanho_ok": tem_tamanho_minimo | |
| } | |
| def validar_tudo(self, texto): | |
| """Executa todas as validações""" | |
| resultado_ofensas = self.validar_ofensas(texto) | |
| resultado_sentido = self.validar_sentido(texto) | |
| resultado_completude = self.validar_completude(texto) | |
| alertas = [] | |
| if resultado_ofensas["tem_ofensa"]: | |
| alertas.append({ | |
| "tipo": "🚫 OFENSA", | |
| "mensagem": f"Palavras ofensivas detectadas: {', '.join(resultado_ofensas['palavras'])}", | |
| "nivel": "ALTO", | |
| "cor": "#FF0000" | |
| }) | |
| if resultado_sentido["tem_problema"]: | |
| alertas.append({ | |
| "tipo": "🤔 SEM SENTIDO", | |
| "mensagem": f"Frase pode não fazer sentido ({resultado_sentido['tipo']})", | |
| "nivel": "MÉDIO", | |
| "cor": "#FF9900" | |
| }) | |
| if resultado_completude["tem_problema"]: | |
| problemas_texto = ", ".join(resultado_completude["problemas"]) | |
| alertas.append({ | |
| "tipo": "📝 INCOMPLETO", | |
| "mensagem": f"Frase incompleta ou mal estruturada: {problemas_texto}", | |
| "nivel": "BAIXO", | |
| "cor": "#FFCC00" | |
| }) | |
| return { | |
| "alertas": alertas, | |
| "ofensas": resultado_ofensas, | |
| "sentido": resultado_sentido, | |
| "completude": resultado_completude, | |
| "aprovado": len(alertas) == 0 | |
| } | |
| # ====================== SISTEMA DE CLASSIFICAÇÃO ====================== | |
| class ClassificadorAreas: | |
| def __init__(self): | |
| self.modelo = None | |
| self.vectorizer = None | |
| self.criar_modelo_padrao() | |
| def criar_modelo_padrao(self): | |
| """Cria modelo padrão baseado em palavras-chave""" | |
| # Dados de treino para cada área | |
| dados_treino = { | |
| "IP": [ | |
| "pesquisa científica", "laboratório", "experimento", "artigo científico", | |
| "publicação", "projeto de pesquisa", "cientista", "dados de pesquisa", | |
| "metodologia", "análise estatística" | |
| ], | |
| "FD": [ | |
| "direito constitucional", "processo judicial", "advogado", "tribunal", | |
| "lei", "jurisprudência", "contrato", "ação judicial", "penal", "civil" | |
| ], | |
| "FE": [ | |
| "educação infantil", "professor", "pedagogia", "ensino fundamental", | |
| "currículo escolar", "didática", "aprendizagem", "avaliação", "alfabetização" | |
| ], | |
| "Reitoria": [ | |
| "administração universitária", "reitor", "decisão institucional", | |
| "gestão acadêmica", "políticas da universidade", "coordenação geral", | |
| "orçamento universitário", "planejamento institucional" | |
| ] | |
| } | |
| textos = [] | |
| labels = [] | |
| for area, frases in dados_treino.items(): | |
| for frase in frases: | |
| textos.append(frase) | |
| labels.append(area) | |
| self.vectorizer = TfidfVectorizer(max_features=100) | |
| X = self.vectorizer.fit_transform(textos) | |
| self.modelo = LogisticRegression() | |
| self.modelo.fit(X, labels) | |
| print("✅ Modelo de classificação de áreas criado com sucesso!") | |
| def classificar_area(self, texto, area_selecionada): | |
| """Classifica a área baseada no texto""" | |
| try: | |
| X = self.vectorizer.transform([texto]) | |
| predicao = self.modelo.predict(X)[0] | |
| probabilidade = np.max(self.modelo.predict_proba(X)[0]) | |
| # Se a área predita for diferente da selecionada e tiver boa confiança, alertar | |
| if predicao != area_selecionada and probabilidade > 0.6: | |
| return predicao, probabilidade | |
| else: | |
| return area_selecionada, probabilidade | |
| except Exception as e: | |
| print(f"⚠️ Erro na classificação de área: {e}") | |
| return area_selecionada, 1.0 | |
| # ====================== INICIALIZAR SISTEMAS ====================== | |
| print("🔄 Inicializando sistemas...") | |
| validador = ValidadorManifestacoes() | |
| classificador = ClassificadorAreas() | |
| print("✅ Sistemas inicializados com sucesso!") | |
| # ====================== FUNÇÕES DA INTERFACE ====================== | |
| def processar_manifestacao(tipo, area, mensagem): | |
| """Processa a manifestação com todas as validações""" | |
| if not mensagem.strip(): | |
| return ( | |
| "<div style='color: red; padding: 20px; text-align: center;'>⚠️ Digite uma mensagem!</div>", | |
| "", | |
| {}, | |
| "AGUARDANDO" | |
| ) | |
| # Validar mensagem | |
| validacao = validador.validar_tudo(mensagem) | |
| # Classificar área sugerida | |
| area_sugerida, confianca_area = classificador.classificar_area(mensagem, area) | |
| # Resultado base | |
| resultado = { | |
| "tipo": tipo, | |
| "area_selecionada": area, | |
| "area_sugerida": area_sugerida, | |
| "confianca_area": f"{confianca_area:.1%}", | |
| "mensagem": mensagem, | |
| "data_hora": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), | |
| "validacao": validacao, | |
| "status": "APROVADA" if validacao["aprovado"] else "REQUER REVISÃO" | |
| } | |
| # Formatar alertas para exibição | |
| alertas_html = "<div style='max-height: 300px; overflow-y: auto;'>" | |
| if validacao["aprovado"]: | |
| alertas_html += """ | |
| <div style=' | |
| background-color: #10B981; | |
| color: white; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin: 10px 0; | |
| text-align: center; | |
| font-weight: bold; | |
| '> | |
| ✅ MENSAGEM APROVADA - PRONTA PARA ENVIO | |
| </div> | |
| """ | |
| else: | |
| for alerta in validacao["alertas"]: | |
| alertas_html += f""" | |
| <div style=' | |
| background-color: {alerta["cor"]}20; | |
| border-left: 5px solid {alerta["cor"]}; | |
| padding: 12px; | |
| margin: 8px 0; | |
| border-radius: 5px; | |
| '> | |
| <strong style='color: {alerta["cor"]};'>{alerta["tipo"]} ({alerta["nivel"]}):</strong><br> | |
| {alerta["mensagem"]} | |
| </div> | |
| """ | |
| alertas_html += "</div>" | |
| # Detalhes da validação | |
| detalhes_html = f""" | |
| <div style='margin-top: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 8px;'> | |
| <h4 style='margin-top: 0;'>📋 Detalhes da Validação:</h4> | |
| <p><strong>🔍 Palavras ofensivas:</strong> {len(validacao['ofensas']['palavras'])} encontradas</p> | |
| <p><strong>🧠 Faz sentido:</strong> {'✅ Sim' if not validacao['sentido']['tem_problema'] else '❌ Não'} ({validacao['sentido'].get('confianca', 0.5):.1%})</p> | |
| <p><strong>📝 Frase completa:</strong> {'✅ Sim' if not validacao['completude']['tem_problema'] else '❌ Não'}</p> | |
| <p><strong>🏛️ Área sugerida pelo sistema:</strong> {area_sugerida} ({confianca_area:.1%} de confiança)</p> | |
| <p><strong>📊 Status final:</strong> <span style='color: {'#10B981' if validacao['aprovado'] else '#F59E0B'}; font-weight: bold;'>{resultado['status']}</span></p> | |
| </div> | |
| """ | |
| # JSON para download/armazenamento | |
| json_resultado = { | |
| "manifestacao": { | |
| "tipo": tipo, | |
| "area_original": area, | |
| "area_sugerida": area_sugerida, | |
| "mensagem": mensagem, | |
| "data_hora": resultado["data_hora"], | |
| "status": resultado["status"] | |
| }, | |
| "validacoes": { | |
| "ofensas": validacao["ofensas"], | |
| "sentido": validacao["sentido"], | |
| "completude": validacao["completude"] | |
| } | |
| } | |
| return alertas_html, detalhes_html, json_resultado, resultado["status"] | |
| def atualizar_descricao(area): | |
| """Atualiza descrição da área selecionada""" | |
| return f"**Descrição:** {AREAS.get(area, 'Área não encontrada')}" | |
| def atualizar_contador(texto): | |
| """Atualiza contador de caracteres""" | |
| palavras = len(texto.split()) | |
| return f"<div style='text-align: right; color: #666; font-size: 14px;'>📝 Caracteres: {len(texto)} | 📊 Palavras: {palavras}</div>" | |
| # ====================== INTERFACE GRADIO ====================== | |
| with gr.Blocks(title="📋 Sistema de Manifestações Universitárias") as app: | |
| # CSS customizado | |
| app.css = """ | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| margin: 0 auto !important; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| .header-container { | |
| padding: 25px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 10px; | |
| margin-bottom: 25px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .header-title { | |
| color: white !important; | |
| text-align: center; | |
| margin-bottom: 10px !important; | |
| font-size: 2.2em !important; | |
| font-weight: 700 !important; | |
| } | |
| .header-subtitle { | |
| color: rgba(255,255,255,0.9); | |
| text-align: center; | |
| margin-bottom: 0; | |
| font-size: 1.1em; | |
| } | |
| .section-title { | |
| background: linear-gradient(90deg, #f0f4ff, #e6f7ff); | |
| padding: 12px 20px; | |
| border-radius: 8px; | |
| margin: 20px 0 15px 0; | |
| border-left: 5px solid #667eea; | |
| } | |
| .example-btn { | |
| margin: 5px !important; | |
| } | |
| .status-aprovado { | |
| color: #10B981 !important; | |
| font-weight: bold !important; | |
| } | |
| .status-revisao { | |
| color: #F59E0B !important; | |
| font-weight: bold !important; | |
| } | |
| """ | |
| # Cabeçalho | |
| gr.HTML(""" | |
| <div class="header-container"> | |
| <h1 class="header-title">🏛️ Sistema de Manifestações Universitárias</h1> | |
| <p class="header-subtitle">Registre reclamações ou sugestões para as áreas da universidade</p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 1️⃣ Tipo de Manifestação") | |
| tipo_input = gr.Dropdown( | |
| choices=TIPO_MANIFESTACAO, | |
| label="Selecione o tipo", | |
| value="Reclamação", | |
| interactive=True | |
| ) | |
| gr.Markdown("### 2️⃣ Área Destino") | |
| area_input = gr.Dropdown( | |
| choices=list(AREAS.keys()), | |
| label="Selecione a área", | |
| value="Reitoria", | |
| interactive=True | |
| ) | |
| # Mostrar descrição da área | |
| area_desc = gr.Markdown(f"**Descrição:** {AREAS['Reitoria']}") | |
| area_input.change(atualizar_descricao, inputs=area_input, outputs=area_desc) | |
| gr.Markdown("---") | |
| gr.Markdown("### 📊 Estatísticas") | |
| contador_total = gr.Textbox("Total de manifestações: 0", label="", interactive=False) | |
| with gr.Column(scale=2): | |
| gr.Markdown("### 3️⃣ Escreva sua Manifestação") | |
| mensagem_input = gr.Textbox( | |
| label="Descreva detalhadamente sua reclamação ou sugestão:", | |
| placeholder="Ex: Gostaria de sugerir a instalação de mais bebedouros no prédio da Faculdade de Educação, pois os existentes são insuficientes para a quantidade de alunos, especialmente nos horários de pico.", | |
| lines=8, | |
| interactive=True | |
| ) | |
| # Contador de caracteres | |
| contador_chars = gr.HTML("<div style='text-align: right; color: #666; font-size: 14px;'>📝 Caracteres: 0 | 📊 Palavras: 0</div>") | |
| mensagem_input.change(atualizar_contador, inputs=mensagem_input, outputs=contador_chars) | |
| with gr.Row(): | |
| btn_enviar = gr.Button("📤 Enviar Manifestação", variant="primary", size="lg") | |
| btn_limpar = gr.Button("🗑️ Limpar Tudo", variant="secondary") | |
| # Resultados | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### ⚠️ Alertas de Validação") | |
| alertas_output = gr.HTML(label="", elem_classes=["alertas-container"]) | |
| with gr.Column(): | |
| gr.Markdown("### 📊 Resultado da Análise") | |
| detalhes_output = gr.HTML(label="", elem_classes=["detalhes-container"]) | |
| status_output = gr.Textbox(label="📈 Status Final", interactive=False) | |
| # JSON para download | |
| json_output = gr.JSON(label="📁 Dados Completos (para download)") | |
| # Histórico - CORRIGIDO: removido parâmetro 'height' | |
| with gr.Accordion("📜 Histórico de Manifestações (últimas 10)", open=False): | |
| historico_df = gr.Dataframe( | |
| headers=["Data", "Tipo", "Área", "Status", "Preview"], | |
| value=[], | |
| interactive=False | |
| ) | |
| # Estado para histórico | |
| historico_state = gr.State([]) | |
| # Processamento completo | |
| def processar_e_armazenar(tipo, area, mensagem, historico): | |
| """Processa e armazena a manifestação""" | |
| alertas, detalhes, json_data, status = processar_manifestacao(tipo, area, mensagem) | |
| # Adicionar ao histórico | |
| nova_entrada = [ | |
| datetime.now().strftime("%d/%m %H:%M"), | |
| tipo, | |
| area, | |
| status, | |
| mensagem[:50] + ("..." if len(mensagem) > 50 else "") | |
| ] | |
| historico.append(nova_entrada) | |
| # Atualizar contador | |
| contador_atualizado = f"Total de manifestações: {len(historico)}" | |
| return ( | |
| alertas, | |
| detalhes, | |
| json_data, | |
| status, | |
| historico, | |
| pd.DataFrame(historico[-10:]), | |
| contador_atualizado | |
| ) | |
| # Limpar tudo | |
| def limpar_tudo(): | |
| return ["", "", {}, "", [], pd.DataFrame([]), "Total de manifestações: 0"] | |
| # Conectar eventos | |
| btn_enviar.click( | |
| fn=processar_e_armazenar, | |
| inputs=[tipo_input, area_input, mensagem_input, historico_state], | |
| outputs=[alertas_output, detalhes_output, json_output, status_output, historico_state, historico_df, contador_total] | |
| ) | |
| btn_limpar.click( | |
| fn=limpar_tudo, | |
| inputs=None, | |
| outputs=[mensagem_input, alertas_output, detalhes_output, json_output, status_output, historico_state, historico_df, contador_total] | |
| ) | |
| # Exemplos pré-definidos | |
| gr.Markdown("### 💡 Exemplos Rápidos (clique para carregar)") | |
| with gr.Row(): | |
| exemplo1 = gr.Button("📡 Internet lenta (IP)", size="sm", elem_classes=["example-btn"]) | |
| exemplo2 = gr.Button("🚰 Mais bebedouros (FE)", size="sm", elem_classes=["example-btn"]) | |
| exemplo3 = gr.Button("🪑 Cadeiras quebradas (FD)", size="sm", elem_classes=["example-btn"]) | |
| exemplo4 = gr.Button("⚠️ Exemplo com problema", size="sm", elem_classes=["example-btn"]) | |
| def carregar_exemplo1(): | |
| return "Reclamação", "IP", "A internet do laboratório de pesquisas está extremamente lenta, impossibilitando o acesso aos artigos científicos. Isso está atrasando nossas pesquisas significativamente. Precisamos de uma solução urgente!" | |
| def carregar_exemplo2(): | |
| return "Sugestão", "FE", "Sugiro a instalação de mais bebedouros nos corredores da Faculdade de Educação, pois os existentes são insuficientes para a quantidade de alunos, especialmente nos horários de pico entre as aulas." | |
| def carregar_exemplo3(): | |
| return "Reclamação", "FD", "As cadeiras da sala 205 estão quebradas há mais de um mês e ninguém fez a manutenção. Isso compromete o conforto e a segurança dos alunos durante as aulas." | |
| def carregar_exemplo4(): | |
| return "Reclamação", "Reitoria", "Isso é uma vergonha! Incompetentes! asdfgh nada funciona direito nessa universidade." | |
| exemplo1.click(carregar_exemplo1, outputs=[tipo_input, area_input, mensagem_input]) | |
| exemplo2.click(carregar_exemplo2, outputs=[tipo_input, area_input, mensagem_input]) | |
| exemplo3.click(carregar_exemplo3, outputs=[tipo_input, area_input, mensagem_input]) | |
| exemplo4.click(carregar_exemplo4, outputs=[tipo_input, area_input, mensagem_input]) | |
| # Rodapé informativo | |
| gr.Markdown("---") | |
| gr.Markdown(""" | |
| <div style='background-color: #f8f9fa; padding: 20px; border-radius: 10px;'> | |
| <h3 style='margin-top: 0; color: #333;'>ℹ️ Como funciona o sistema:</h3> | |
| <div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-top: 15px;'> | |
| <div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);'> | |
| <h4 style='color: #667eea; margin-top: 0;'>✅ Validações automáticas</h4> | |
| <p><strong>🚫 Detecção de ofensas:</strong> Verifica palavras inadequadas no texto</p> | |
| <p><strong>🤔 Análise de sentido:</strong> Identifica frases sem sentido ou spam</p> | |
| <p><strong>📝 Verificação de completude:</strong> Checa se a mensagem está bem estruturada</p> | |
| </div> | |
| <div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);'> | |
| <h4 style='color: #667eea; margin-top: 0;'>🏛️ Classificação inteligente</h4> | |
| <p>Sugere a área mais apropriada baseada no conteúdo</p> | |
| <p>Compara com a área selecionada pelo usuário</p> | |
| <p>Mostra nível de confiança da sugestão</p> | |
| </div> | |
| <div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);'> | |
| <h4 style='color: #667eea; margin-top: 0;'>📊 Resultados</h4> | |
| <p>Status claro (APROVADA ou REQUER REVISÃO)</p> | |
| <p>Alertas detalhados com cores por gravidade</p> | |
| <p>Histórico das últimas manifestações</p> | |
| <p>Dados exportáveis em JSON</p> | |
| </div> | |
| </div> | |
| </div> | |
| """) | |
| # ====================== INICIAR APLICAÇÃO ====================== | |
| if __name__ == "__main__": | |
| print("🚀 Iniciando aplicação Gradio...") | |
| app.launch( | |
| debug=True, | |
| share=False, | |
| server_name="0.0.0.0", | |
| server_port=7860 | |
| ) |