Spaces:
Runtime error
Runtime error
| import os | |
| import sys | |
| import json | |
| import re | |
| import unicodedata | |
| from pathlib import Path | |
| import pandas as pd | |
| import spacy | |
| import gradio as gr | |
| from translit import latin_to_basic_grc | |
| from paradigm_check import validar_substantivo_2a | |
| BASE_DIR = Path(__file__).resolve().parent | |
| # --- 1. CARREGAMENTO DO MODELO GRECY (grc_perseus_lg) --- | |
| def load_grecy_model(): | |
| """Carrega o modelo greCy grc_perseus_lg instalado via requirements.txt.""" | |
| try: | |
| return spacy.load("grc_perseus_lg") | |
| except Exception as e: | |
| print(f"Erro ao carregar grc_perseus_lg diretamente: {e}") | |
| try: | |
| import grc_perseus_lg | |
| return grc_perseus_lg.load() | |
| except Exception as e2: | |
| print(f"Erro ao importar pacote grc_perseus_lg: {e2}") | |
| return spacy.blank("grc") | |
| nlp = load_grecy_model() | |
| # --- CONFIGURAÇÃO ALPHEIOS & CSS --- | |
| alpheios_css = """<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/alpheios-components@latest/dist/style/style-components.min.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: 'ddgp-plus-space', | |
| 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); | |
| } | |
| }""" | |
| 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' | |
| } | |
| # --- CARREGAMENTO DOS DADOS LOCAIS --- | |
| def load_json(path): | |
| file_path = BASE_DIR / path | |
| if file_path.exists(): | |
| try: | |
| with file_path.open('r', encoding='utf-8') as f: | |
| return json.load(f) | |
| except Exception as e: | |
| print(f"ERRO ao carregar {path}: {e}") | |
| return {} | |
| print(f"AVISO: arquivo não encontrado: {file_path}") | |
| 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 = "" | |
| for f_css in ['style.css', 'style_map.css']: | |
| file_path = BASE_DIR / f_css | |
| if file_path.exists(): | |
| try: | |
| with file_path.open('r', encoding='utf-8') as f: | |
| css_content += f.read() + "\n" | |
| except Exception as e: | |
| print(f"ERRO ao carregar {f_css}: {e}") | |
| css_content += """ | |
| /* Aumenta a fonte da caixa de entrada (textarea) */ | |
| textarea { | |
| font-size: 20px !important; | |
| line-height: 1.6 !important; | |
| font-family: 'Gentium Plus', 'Times New Roman', serif !important; | |
| } | |
| /* Aumenta a fonte das tabelas de análise */ | |
| table, th, td { | |
| font-size: 18px !important; | |
| } | |
| /* Ajusta especificamente o texto dentro das células da tabela */ | |
| .gr-table td { | |
| font-size: 18px !important; | |
| padding: 10px !important; | |
| } | |
| /* Se houver componentes de Markdown/HTML com grego */ | |
| .prose { | |
| font-size: 18px !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'<span class="{classe_css}" title="{desc}">{ab}</span>' | |
| 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'<span class="etimo">〈\1〉</span>', pdesc) | |
| return f""" | |
| <div class="result-box" style="text-transform: none !important; font-variant: normal !important;"> | |
| <div style="color: #1a4d8f; font-size: 1.3em; font-weight: bold; margin-bottom: 6px; text-transform: none !important; font-variant: normal !important;"> | |
| {gword} | |
| </div> | |
| <div style="line-height: 1.6; text-transform: none !important; font-variant: normal !important;"> | |
| {pdesc} | |
| </div> | |
| </div> | |
| """ | |
| # --- 3. CONSULTA E ANÁLISE --- | |
| def consultar_ddgp(termo): | |
| if not termo: return "" | |
| if any(ord(c) < 128 for c in termo if c.isalpha()): | |
| termo = latin_to_basic_grc(termo) | |
| termo_norm = normalizar_grego(termo) | |
| ids = [] | |
| 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): | |
| def limpar_crases(t): | |
| if not t: return "" | |
| crases = { | |
| "ὦνθρωπε": "ὦ ἄνθρωπε", "ὥνθρωπος": "ὁ ἄνθρωπος", "κἀγώ": "καὶ ἐγώ", | |
| "κἀμέ": "καὶ ἐμέ", "τοὔνομα": "τὸ ὄνομα", "θαἰμάτια": "τὰ ἱμάτια", | |
| "τἀνδρεῖα": "τὰ ἀνδρεῖα", "κἄν": "καὶ ἄν", "θἄτερον": "τὸ ἕτερον", | |
| "προὔργου": "πρὸ ἔργου", "κἀκεῖνος": "καὶ ἐκεῖνος" | |
| } | |
| for crase, original in crases.items(): | |
| t = t.replace(crase, original) | |
| return t | |
| def lematizar_sais(palavra): | |
| palavra = palavra.lower() | |
| excecoes_nominais = { | |
| "συρακούσαις": "Συράκουσαι", "ὀδρύσαις": "Ὀδρύσαι", "ἀργινούσαις": "Ἀργινοῦσαι", | |
| "κρήσσαις": "Κρῆσσα", "θρᾴσσαις": "Θρᾷσσα", "κιλίσσαις": "Κίλισσα", | |
| "φοινίσσαις": "Φοινίσσαι", "μελίσσαις": "μέλισσα", "μούσαις": "Μοῦσα", | |
| "μώσαις": "Μοῦσα", "βύρσαις": "βύρσα", "πάσαις": "πᾶς", "ἁπάσαις": "ἅπας", | |
| "ξυμπάσαις": "σύμπας", "ὅσαις": "ὅσος", "ὁπόσαις": "ὅποσος", "ἴσαις": "ἴσος", "σαῖς": "σός" | |
| } | |
| if palavra in excecoes_nominais: | |
| return f"Lema Nominal: {excecoes_nominais[palavra]}" | |
| return None | |
| def tratar_adjetivo_verbal(palavra_norm): | |
| """Identifica e categoriza adjetivos verbais em -τέον / -τέος (obrigatoriedade/necessidade).""" | |
| if palavra_norm.endswith("τεον") or palavra_norm.endswith("τεος"): | |
| return "Adjetivo Verbal (Obrigatoriedade / -τέον, -τέος)" | |
| return None | |
| if not texto: | |
| return None, None, "0", "0", "0", "0", "0", "" | |
| texto_limpo = limpar_crases(texto) | |
| doc = nlp(texto_limpo) | |
| dados = [] | |
| verbetes_dict = {} | |
| lemas_unicos_processados = set() | |
| for token in doc: | |
| l_orig = token.lemma_ | |
| l_norm = normalizar_grego(l_orig) | |
| palavra_norm = normalizar_grego(token.text) | |
| pos_pt = TAG_MAP.get(token.pos_, token.pos_) | |
| 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 = "-" | |
| # Hierarquia de decisão | |
| analise_sais = lematizar_sais(token.text) | |
| correcao_paradigma = validar_substantivo_2a(token.text, l_orig) | |
| analise_verbal = tratar_adjetivo_verbal(palavra_norm) | |
| tem_no_ddgp = l_norm in INDEX_LEMAS | |
| lemas_nominais = ('ος', 'ις', 'ας', 'ης', 'ον') | |
| l_final = l_orig | |
| pos_final = pos_pt | |
| morph_final = morph_info | |
| # 1. Regra para Adjetivos Verbais (-τέον / -τέος) | |
| if analise_verbal: | |
| pos_final = "Adjetivo Verbal" | |
| morph_final = f"{analise_verbal} | {morph_info}" | |
| # 2. Exceções de -sais | |
| elif analise_sais and "Lema Nominal" in analise_sais: | |
| l_final = analise_sais.split(": ")[1] | |
| pos_final = "Substantivo/Adj" | |
| # 3. Correção via paradigma 2a declinação | |
| elif correcao_paradigma: | |
| pos_final = "Substantivo" | |
| morph_final = correcao_paradigma | |
| # 4. Regra DDGP (Substantivo confundido 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})" | |
| if token.pos_ not in ['PUNCT', 'SYM', 'SPACE']: | |
| dados.append({ | |
| 'Palavra': token.text, | |
| 'Lema': l_final, | |
| 'Classe': pos_final, | |
| 'Morfologia': morph_final | |
| }) | |
| 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) | |
| aviso_html = """ | |
| <div style="background-color: #f8f9fa; padding: 10px; border-left: 4px solid #1a4d8f; margin-bottom: 15px; font-size: 0.9em; color: #555;"> | |
| 💡 <b>Dica:</b> Caso um lema não apareça automaticamente abaixo, utilize a aba "Busca direta no DDGP" para consultá-lo manualmente. | |
| </div> | |
| """ | |
| lexico_html = aviso_html | |
| for lema_ord in sorted(verbetes_dict.keys(), key=ordem_grega): | |
| lexico_html += verbetes_dict[lema_ord] | |
| df = pd.DataFrame(dados) | |
| n_tokens = len(df) | |
| n_types = df['Palavra'].str.lower().nunique() if n_tokens > 0 else 0 | |
| n_lemas = df['Lema'].nunique() if n_tokens > 0 else 0 | |
| ttr = (n_types / n_tokens) if n_tokens > 0 else 0 | |
| ltr = (n_lemas / n_tokens) if n_tokens > 0 else 0 | |
| csv_path = str(BASE_DIR / "analise_filologica.csv") | |
| df.to_csv(csv_path, index=False, encoding="utf-8-sig") | |
| return (df, csv_path, | |
| str(n_tokens), str(n_types), str(n_lemas), | |
| f"{ltr:.2f}", f"{ttr:.2f}", lexico_html) | |
| # --- 4. INTERFACE GRADIO & INJEÇÃO DO ALPHEIOS --- | |
| alpheios_head_html = f""" | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/alpheios-components@latest/dist/style/style-components.min.css"/> | |
| <script> | |
| ({alpheios_js})(); | |
| </script> | |
| """ | |
| full_css = css_content + "\n" + alpheios_css | |
| with gr.Blocks( | |
| title="DDGP + greCy", | |
| theme=gr.themes.Default(text_size="lg"), | |
| css=full_css, | |
| head=alpheios_head_html | |
| ) as demo: | |
| with gr.Row(): | |
| with gr.Column(scale=1, min_width=100): | |
| gr.HTML(""" | |
| <div style="display: flex; align-items: center; justify-content: flex-start; height: 80px;"> | |
| <img src="https://raw.githubusercontent.com/aniseferreira/DDGP_Plus/main/ddgp/logo.png" style="height: 80px;"> | |
| </div> | |
| """) | |
| with gr.Column(scale=4): | |
| gr.Markdown("# Estação Filológica DDGP & greCy") | |
| 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...Δειναὶ γὰρ αἱ γυναῖκες εὑρίσκειν τέχνας.") | |
| 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(""" | |
| --- | |
| **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_.<br> | |
| _Desenvolvimento técnico e programação assistida por Gemini (Google AI)_. | |
| Sob licença CC BY-NC-SA 4.0. | |
| """) | |
| # Wrappers de diagnóstico: preservam as funções originais e tornam | |
| # exceções de eventos visíveis no log do Hugging Face. | |
| def analisar_texto_seguro(texto): | |
| try: | |
| print("Iniciando análise de texto...") | |
| resultado = analisar_texto(texto) | |
| print("Análise concluída com sucesso.") | |
| return resultado | |
| except Exception as e: | |
| import traceback | |
| print("ERRO NA ANÁLISE DE TEXTO:") | |
| traceback.print_exc() | |
| return ( | |
| pd.DataFrame(columns=["Palavra", "Lema", "Classe", "Morfologia"]), | |
| None, "0", "0", "0", "0", "0", | |
| f'<div style="color:#b00020;"><b>Erro na análise:</b> {e}</div>' | |
| ) | |
| def consultar_ddgp_seguro(termo): | |
| try: | |
| print(f"Consulta DDGP: {termo}") | |
| return consultar_ddgp(termo) | |
| except Exception as e: | |
| import traceback | |
| print("ERRO NA CONSULTA DDGP:") | |
| traceback.print_exc() | |
| return f'<div style="color:#b00020;"><b>Erro na consulta:</b> {e}</div>' | |
| btn.click( | |
| analisar_texto_seguro, | |
| inputs=txt, | |
| outputs=[out_t, out_f, t1, t2, t3, t4, t5, out_l], | |
| api_name=False | |
| ) | |
| btn_b.click( | |
| consultar_ddgp_seguro, | |
| inputs=in_b, | |
| outputs=out_b, | |
| api_name=False | |
| ) | |
| def carregar_e_analisar(request: gr.Request): | |
| try: | |
| params = request.query_params | |
| texto_url = params.get("text", "") | |
| print(f"Parâmetro URL recebido: {'sim' if texto_url else 'não'}") | |
| return texto_url | |
| except Exception as e: | |
| import traceback | |
| print("ERRO AO LER PARÂMETROS DA URL:") | |
| traceback.print_exc() | |
| return "" | |
| # Mantém o mecanismo original demo.load().then(). | |
| demo.load(carregar_e_analisar, None, txt).then( | |
| fn=analisar_texto_seguro, | |
| inputs=txt, | |
| outputs=[out_t, out_f, t1, t2, t3, t4, t5, out_l], | |
| api_name=False | |
| ) | |
| # --- INICIALIZAÇÃO CORRETA --- | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| show_api=False | |
| ) |