pln-ddgp-plus / app.py
AniseF's picture
Update app.py
d80f633 verified
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
# --- 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 = ""
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"
# --- 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 ""
# 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):
if not texto: return None, None, "0", "0", "0", "0", "0", ""
doc = nlp(texto)
dados = []
lemas_unicos_processados = set()
verbetes_dict = {}
for token in doc:
l_orig = token.lemma_
l_norm = normalizar_grego(l_orig)
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 = "-"
# Busca no dicionário apenas uma vez por lema
if token.pos_ not in ['PUNCT', 'SYM', 'SPACE'] and l_norm not in lemas_unicos_processados:
res_html = consultar_ddgp(l_norm)
if res_html:
verbetes_dict[l_orig] = res_html
lemas_unicos_processados.add(l_norm)
dados.append({
'Palavra': token.text,
'Lema': l_orig,
'Classe': pos_pt,
'Morfologia': morph_info
})
# Cabeçalho com o aviso solicitado
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)
tokens_df = df[~df['Classe'].isin(['Pontuação', 'PUNCT', 'SYM', 'SPACE'])]
n_tokens = len(tokens_df)
n_types = tokens_df['Palavra'].str.lower().nunique()
n_lemas = tokens_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)
return (df.head(100), csv_path,
str(n_tokens), str(n_types), str(n_lemas),
f"{ltr:.2f}", f"{ttr:.2f}", lexico_html)
# --- 4. INTERFACE ---
with gr.Blocks(css=css_content, title="DDGP + OdyCy") 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 & 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...Δειναὶ γὰρ αἱ γυναῖκες εὑρίσκειν τέχνας.")
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).*
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]
)
demo.launch()