AniseF's picture
Update app.py
ed5b569 verified
Raw
History Blame Contribute Delete
16.5 kB
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
)