import os import subprocess import sys import urllib.request import json import pandas as pd import spacy import gradio as gr import re import unicodedata from translit import latin_to_basic_grc from paradigm_check import validar_substantivo_2a # Código para o cabeçalho (CSS) # 1. Defina as variáveis (certifique-se que o nome aqui...) alpheios_css = """ """ alpheios_js = """ function() { const loadAlpheios = () => { import("https://cdn.jsdelivr.net/npm/alpheios-embedded@latest/dist/alpheios-embedded.min.js") .then(embedLib => { const alpheios = embedLib.AlpheiosEmbedded.ImportLib(); alpheios.activate({ clientId: 'httpshuggingfacecospacesAniseFpln-ddgp-plus-teste', authStatus: 'notLoggedIn' }); console.log("Alpheios Ativado com Sucesso!"); }).catch(e => console.error("Erro Alpheios:", e)); }; if (document.readyState === 'complete') { loadAlpheios(); } else { window.addEventListener('load', loadAlpheios); } } """ meu_css_customizado = """ /* Força o aumento dos títulos (Labels) acima das tabelas e caixas */ .gradio-container .gr-form label span, .gradio-container span[data-testid="block-info"], .gradio-container .label-wrap span { font-size: 24px !important; font-weight: bold !important; color: #1a472a !important; /* Um verde escuro para destacar */ display: block !important; margin-bottom: 8px !important; } /* Aumenta o cabeçalho das colunas das tabelas */ .thead th span { font-size: 18px !important; font-weight: bold !important; } # --- 1. INSTALAÇÃO E CARREGAMENTO --- def install_odycy(): try: import grc_odycy_joint_sm except ImportError: url = "https://huggingface.co/chcaa/grc_odycy_joint_sm/resolve/main/grc_odycy_joint_sm-any-py3-none-any.whl" valid_wheel_name = "grc_odycy_joint_sm-1.0.0-py3-none-any.whl" urllib.request.urlretrieve(url, valid_wheel_name) subprocess.check_call([sys.executable, "-m", "pip", "install", valid_wheel_name, "--no-deps"]) os.remove(valid_wheel_name) install_odycy() try: import grc_odycy_joint_sm nlp = grc_odycy_joint_sm.load() except: nlp = spacy.blank("grc") TAG_MAP = { 'NOUN': 'Substantivo', 'VERB': 'Verbo', 'ADJ': 'Adjetivo', 'DET': 'Artigo/Det.', 'PRON': 'Pronome', 'ADV': 'Advérbio', 'ADP': 'Preposição', 'CCONJ': 'Conjunção', 'SCONJ': 'Conjunção Sub.', 'PART': 'Partícula', 'PROPN': 'Nome Próprio', 'PUNCT': 'Pontuação', 'AUX': 'Auxiliar/Cópula', 'NUM': 'Numeral' } def load_json(path): if os.path.exists(path): with open(path, 'r', encoding='utf-8') as f: return json.load(f) return {} INDEX_LEMAS = load_json('ddgp_index_lemas.json') INDEX_FORMAS = load_json('ddgp_index_formas_final.json') FORMA_TO_LEMA = load_json('ddgp_forma_to_lema.json') ENTRIES = load_json('ddgp3x_entry.json') ABREV = load_json('abrev.json') css_content = "" # Parte 1: Lê os arquivos existentes (O que você já tinha) for f_css in ['style.css', 'style_map.css']: if os.path.exists(f_css): with open(f_css, 'r', encoding='utf-8') as f: css_content += f.read() + "\n" css_content += "textarea { font-size: 20px !important; line-height: 1.6 !important;font-family: 'Gentium Plus', 'Alegreya', serif !important; } " css_content += "table, th, td { font-size: 20px !important; } " css_content += ".gradio-container label span { font-size: 20px !important; } " css_content += ".gr-table td { font-size: 20px !important; padding: 10px !important; } " css_content += ".prose { font-size: 20px !important; } " # --- 2. FUNÇÕES DE APOIO E ORDENAÇÃO --- def normalizar_grego(texto): if not texto: return "" texto = unicodedata.normalize('NFD', texto.lower()) texto = "".join(c for c in texto if not unicodedata.combining(c)) return unicodedata.normalize('NFC', texto).strip() def ordem_grega(lema): alfabeto_map = { 'α': 1, 'β': 2, 'γ': 3, 'δ': 4, 'ε': 5, 'ζ': 6, 'η': 7, 'θ': 8, 'ι': 9, 'κ': 10, 'λ': 11, 'μ': 12, 'ν': 13, 'ξ': 14, 'ο': 15, 'π': 16, 'ρ': 17, 'σ': 18, 'ς': 18, 'τ': 19, 'υ': 20, 'φ': 21, 'χ': 22, 'ψ': 23, 'ω': 24 } lema_limpo = normalizar_grego(lema) return [alfabeto_map.get(char, 99) for char in lema_limpo] def aplicar_abreviaturas_seguro(texto): if not texto: return "" sorted_abrevs = sorted(ABREV.keys(), key=len, reverse=True) for ab in sorted_abrevs: pattern = r'\b' + re.escape(ab) + r'(?=\s|[.,;:]|$)' info = ABREV[ab] desc = info.get('descricao', '') categoria = info.get('categoria', '') classe_css = "autor-sc" if categoria == 'autor' else "abrev" subst = f'{ab}' texto = re.sub(pattern, subst, texto) return texto def format_entry_html(entry_id): entry = ENTRIES.get(str(entry_id)) if not entry: return None gword = entry.get('gword', '') pdesc = entry.get('pdesc', '') pdesc = aplicar_abreviaturas_seguro(pdesc) pdesc = re.sub(r'〈(.*?)〉', r'〈\1〉', pdesc) return f'''
{gword}
{pdesc}
''' # --- 3. CONSULTA E ANÁLISE --- def consultar_ddgp(termo): if not termo: return "" # Translitera se for latim if any(ord(c) < 128 for c in termo if c.isalpha()): termo = latin_to_basic_grc(termo) termo_norm = normalizar_grego(termo) ids = [] # BUSCA ESTRITA: Tentamos apenas o termo exato ou variações numéricas (ex: logos, logos1) # Isso evita que a busca traga palavras que apenas "começam" com o termo. tentativas = [termo_norm] + [f"{termo_norm}{i}" for i in range(1, 4)] for b in tentativas: if b in INDEX_LEMAS: ids.append(INDEX_LEMAS[b]) if not ids: return "" html = "" for eid in sorted(set(ids)): res = format_entry_html(eid) if res: html += res return html def analisar_texto(texto): # --- SUB-FUNÇÕES DE APOIO (INTERNAS E IDENTADAS) --- def limpar_crases(t): if not t: return "" crases = { "ὦνθρωπε": "ὦ ἄνθρωπε", "ὥνθρωπος": "ὁ ἄνθρωπος", "κἀγώ": "καὶ ἐγώ", "κἀμέ": "καὶ ἐmé", "τοὔνομα": "τὸ ὄνομα", "θαἰμάτια": "τὰ ἱμάτια", "τἀνδρεῖα": "τὰ ἀνδρεῖα", "κἄν": "καὶ ἄν", "θἄτερον": "τὸ ἕτερον", "προὔργου": "πρὸ ἔργου", "κἀκεῖνος": "καὶ ἐκεῖνος" } for crase, original in crases.items(): t = t.replace(crase, original) return t def lematizar_sais(palavra): palavra = palavra.lower() excecoes_nominais = { "συρακούσαις": "Συράκουσαι", "ὀδρύσαις": "Ὀδρύσαι", "ἀργινούσαις": "Ἀργινοῦσαι", "κρήσσαις": "Κρῆσσα", "θρᾴσσαις": "Θρᾷσσα", "κιλίσσαις": "Κίλισσα", "φοινίσσαις": "Φοινίσσαι", "μελίσσαις": "μέλισσα", "μούσαις": "Μοῦσα", "μώσαις": "Μοῦσα", "βύρσαις": "βύρσα", "πάσαις": "πᾶς", "ἁπάσαις": "ἅπας", "ξυμπάσαις": "σύμπas", "ὅσαις": "ὅσος", "ὁπόσαις": "ὅποσος", "ἴσαις": "ἴσος", "σαῖς": "σός" } if palavra in excecoes_nominais: return f"Lema Nominal: {excecoes_nominais[palavra]}" return None # --- INÍCIO DO PROCESSAMENTO --- if not texto: return None, None, "0", "0", "0", "0", "0", "" # 1. Vacina de Crases aplicada antes de tudo texto_limpo = limpar_crases(texto) # 2. Inicialização do spaCy e variáveis doc = nlp(texto_limpo) dados = [] verbetes_dict = {} lemas_unicos_processados = set() for token in doc: # --- PROTEÇÃO CONTRA PONTUAÇÃO (Aqui resolvemos os dois pontos :) --- if token.pos_ == "PUNCT" or token.text in [':', '.', ',', ';', '·']: l_orig = token.text l_norm = token.text pos_pt = "Pontuação" morph_info = "-" else: # A. Variáveis Básicas do Token l_orig = token.lemma_ l_norm = normalizar_grego(l_orig) pos_pt = TAG_MAP.get(token.pos_, token.pos_) # Morfologia Original da IA (Limpeza das tags) morph_info = str(token.morph).replace("Case=", "").replace("Gender=", "").replace("Number=", "").replace("VerbForm=", "").replace("Person=", "").replace("Tense=", "").replace("Mood=", "").replace("Voice=", "") if not morph_info: morph_info = "-" # --- E. HIERARQUIA DE DECISÃO (SEGURA) --- analise_sais = lematizar_sais(token.text) correcao_paradigma = validar_substantivo_2a(token.text, l_orig) tem_no_ddgp = l_norm in INDEX_LEMAS lemas_nominais = ('ος', 'ις', 'ας', 'ης', 'ον') # 1. Definimos o padrão (IA) l_final = l_orig pos_final = pos_pt morph_final = morph_info # 2. Aplicamos as correções por ordem de importância # Se for uma exceção de -sais, corrigimos o LEMA e a CLASSE if analise_sais and "Lema Nominal" in analise_sais: l_final = analise_sais.split(": ")[1] pos_final = "Substantivo/Adj" # Mantemos o morph_final da IA ou Alpheios para ver se é Dativo ou Particípio # Se o Alpheios (Paradigma) encontrou algo, ele define a Morfologia if correcao_paradigma: pos_final = "Substantivo" morph_final = correcao_paradigma # Regra do DDGP (Se a IA confundir substantivo com verbo) elif pos_pt == "Verbo" and tem_no_ddgp and l_norm.endswith(lemas_nominais): pos_final = "Substantivo" morph_final = f"Morfologia nominal (IA: {morph_info})" # --- F. ALIMENTAR A TABELA (Este bloco PRECISA estar aqui) --- if token.pos_ not in ['PUNCT', 'SYM', 'SPACE']: dados.append({ 'Palavra': token.text, 'Lema': l_final, 'Classe': pos_final, 'Morfologia': morph_final }) # Busca no Dicionário apenas uma vez por lema if l_norm not in lemas_unicos_processados: res_html = consultar_ddgp(l_norm) if res_html: verbetes_dict[l_final] = res_html lemas_unicos_processados.add(l_norm) # --- FINALIZAÇÃO E RETORNO PARA GRADIO --- aviso_html = '''
💡 Dica: Caso um lema não apareça automaticamente abaixo, utilize a aba "Busca direta no DDGP" para consultá-lo manualmente.
''' lexico_html = aviso_html # Ordena os verbetes alfabeticamente para o dicionário for lema_ord in sorted(verbetes_dict.keys(), key=ordem_grega): lexico_html += verbetes_dict[lema_ord] # Prepara o DataFrame e Estatísticas df = pd.DataFrame(dados) n_tokens = len(df) n_types = df['Palavra'].str.lower().nunique() n_lemas = df['Lema'].nunique() ttr = (n_types / n_tokens) if n_tokens > 0 else 0 ltr = (n_lemas / n_tokens) if n_tokens > 0 else 0 csv_path = "analise_filologica.csv" df.to_csv(csv_path, index=False) # Retorna os 8 elementos que a sua interface Gradio espera return (df, csv_path, str(n_tokens), str(n_types), str(n_lemas), f"{ltr:.2f}", f"{ttr:.2f}", lexico_html) # --- 4. INTERFACE --- # Modifique esta linha para incluir head e js: with gr.Blocks(title="DDGP + OdyCy", theme=gr.themes.Default(text_size="lg"), css=meu_css_customizado) as demo: with gr.Row(): with gr.Column(scale=1, min_width=100): gr.HTML('''
''') # ... restante do seu código ... with gr.Column(scale=4): gr.Markdown("# Estação Filológica DDGP & OdyCy") gr.Markdown("## DDGP Plus: Análise lexical e consulta ao Dicionário Digital Grego-Português.") with gr.Tab("📝 Análise lexical"): txt = gr.Textbox(label="Texto em Grego Antigo", lines=6, placeholder="Insira o texto aqui sem aspas...Δειναὶ γὰρ αἱ γυναῖκες εὑρίσκειν τέχνας.", elem_classes="text-grande") btn = gr.Button("🚀 Executar Análise", variant="primary") with gr.Row(): t1 = gr.Label(label="Tokens (Total)") t2 = gr.Label(label="Types (Formas Únicas)") t3 = gr.Label(label="Lemas (Entradas)") t4 = gr.Label(label="LTR (Lema-Token)") t5 = gr.Label(label="TTR (Type-Token)") with gr.Row(): with gr.Column(scale=2): out_t = gr.Dataframe(label="Formas") out_f = gr.File(label="Exportar CSV") with gr.Column(scale=1): gr.Markdown("### 📖 Léxico Contextual") out_l = gr.HTML() with gr.Tab("🔍 Busca direta no DDGP"): in_b = gr.Textbox(label="Busca direta no DDGP", placeholder="(ex. λόγος, logos)") btn_b = gr.Button("Consultar Base") out_b = gr.HTML() gr.Markdown(f''' --- **DDGP Plus** — Analisador Morfológico e Dicionário Digital de Grego–Português 2026 v.1 Baseado originalmente no Dicionário Grego-Português e diretamente no Dicionário Digital Grego–Português (DDGP e DGP - ver créditos em [hipatia.fclar.unesp.br](http://hipatia.fclar.unesp.br))
Projetos Abertos em Letras Clássicas Digitais. **Responsável**: Anise D'Orange Ferreira. _Desenvolvimento técnico e programação_ assistida por Gemini (Google AI/Workspace).
Sob licença CC BY-NC-SA 4.0. ''') btn.click(analisar_texto, inputs=txt, outputs=[out_t, out_f, t1, t2, t3, t4, t5, out_l], api_name="analisar") btn_b.click(consultar_ddgp, inputs=in_b, outputs=out_b, api_name="consultar") # No final do seu app.py, substitua demo.launch() por: # No final do bloco 'with gr.Blocks...' def carregar_e_analisar(request: gr.Request): params = request.query_params texto_da_url = params.get("text", "") return texto_da_url # Quando o Space carrega: # 1. Preenche o texto # 2. Se houver texto, dispara a função de análise imediatamente demo.load(carregar_e_analisar, None, txt).then( fn=analisar_texto, inputs=txt, outputs=[out_t, out_f, t1, t2, t3, t4, t5, out_l] ) # Combine os estilos (opcional, mas recomendado para manter seu layout) full_css = css_content + "\n" + alpheios_css # Chame o launch assim: demo.launch( css=full_css, head=alpheios_css, # O Alpheios precisa do link no head js=alpheios_js, ssr_mode=False # <--- Adicione esta linha aqui )