GabrielGD commited on
Commit
73edeed
·
verified ·
1 Parent(s): 976e6e1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +354 -156
app.py CHANGED
@@ -1,178 +1,376 @@
1
  import os
2
  import io
3
  import re
 
4
  import streamlit as st
5
  from docx import Document
6
  from docx.shared import Cm, Pt
7
  from docx.enum.text import WD_ALIGN_PARAGRAPH
8
- from pydantic import BaseModel, Field
9
  from google import genai
10
  from google.genai import types
11
- from tenacity import retry, stop_after_attempt, wait_exponential
12
 
13
- # ─── CONFIG ───────────────────────────────────────────────────────────────
14
- st.set_page_config(page_title="Corretor TCC - UNIFAGOC", page_icon="⚖", layout="wide")
15
- api_key = st.secrets.get("GEMINI_API_KEY", os.environ.get("GEMINI_API_KEY"))
16
- if not api_key:
17
- st.error("❌ Defina GEMINI_API_KEY nos Secrets do Streamlit.")
18
- st.stop()
19
- client = genai.Client(api_key=api_key)
20
- MODEL = "gemini-3.5-flash"
21
 
22
- # ─── MODELOS ──────────────────────────────────────────────────────────────
23
  class AuditoriaTCC(BaseModel):
24
- aderencia_percentual: int = Field(ge=0, le=100)
25
- diagnostico_geral: str = ""
26
- problema_identificado_no_projeto: str = ""
27
- tcc_respondeu_ao_problema: bool = False
28
- justificativa_problema: str = ""
29
- objetivos_especificos_identificados: list[str] = Field(default_factory=list)
30
- objetivos_nao_cumpridos_no_tcc: list[str] = Field(default_factory=list)
31
- desvios_de_tema_ou_linguica: list[str] = Field(default_factory=list)
32
- indicios_texto_generico_ia: list[str] = Field(default_factory=list)
33
- nota_tema_problema: float = 0.0
34
- nota_introducao_metodo: float = 0.0
35
- nota_estruturacao: float = 0.0
36
- nota_formatacao_abnt: float = 0.0
37
- justificativa_notas_banca: str = ""
38
 
39
  class ErroTexto(BaseModel):
40
  original: str
41
  corrigido: str
42
- motivo: str = ""
43
 
44
  class RevisaoInteligente(BaseModel):
45
- correcoes: list[ErroTexto] = Field(default_factory=list)
46
- termos_estrangeiros_para_italico: list[str] = Field(default_factory=list)
47
-
48
- # ─── HELPERS ──────────────────────────────────────────────────────────────
49
- @retry(wait=wait_exponential(2, 30), stop=stop_after_attempt(3))
50
- def chamar_gemini(prompt, schema, temp=0.0):
51
- return client.models.generate_content(
52
- model=MODEL,
53
- contents=prompt,
54
- config=types.GenerateContentConfig(
55
- temperature=temp,
56
- response_mime_type="application/json",
57
- response_schema=schema
58
- )
59
- )
60
-
61
- @st.cache_data(show_spinner=False)
62
- def extrair_texto_docx(_bytes) -> str:
63
- doc = Document(io.BytesIO(_bytes))
64
- return "\n\n".join(p.text.strip() for p in doc.paragraphs if p.text.strip())
 
 
 
 
 
 
 
 
 
 
65
 
66
  def docx_to_bytes(doc: Document) -> bytes:
67
- bio = io.BytesIO()
68
- doc.save(bio)
69
- return bio.getvalue()
70
 
71
  def estimar_paginas(doc: Document) -> int:
72
- chars = 0
73
- for p in doc.paragraphs:
74
- if re.search(r"REFER[ÊE]NCIAS", p.text, re.I): break
75
- chars += len(p.text)
76
- return max(1, chars // 1800)
77
-
78
- def extrair_referencias_docx(doc: Document):
79
- refs, em_refs = [], False
80
- for i, p in enumerate(doc.paragraphs):
81
- t = p.text.strip()
82
- if not t: continue
83
- if re.match(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", t, re.I):
84
- em_refs = True; continue
85
- if em_refs:
86
- if re.match(r"^(ANEXOS?|AP[ÊE]NDICES?|APENDICE)$", t, re.I): break
87
- if len(t) < 80 and t.isupper(): break
88
- refs.append((i, t))
89
- return refs
90
-
91
- def aplicar_formatacao_unifagoc(doc: Document):
92
- for s in doc.sections:
93
- s.top_margin = s.left_margin = Cm(3)
94
- s.bottom_margin = s.right_margin = Cm(2)
95
- normal = doc.styles['Normal']
96
- normal.font.name = 'Calibri'
97
- normal.font.size = Pt(12)
98
- normal.paragraph_format.line_spacing = 1.5
99
- normal.paragraph_format.first_line_indent = Cm(1.25)
100
-
101
- em_ref, em_resumo = False, False
102
- for p in doc.paragraphs:
103
- txt = p.text.strip()
104
- if not txt: continue
105
- up = txt.upper()
106
- if up.startswith("RESUMO"): em_resumo = True
107
- elif up.startswith("INTRODUÇÃO"): em_resumo = False
108
- elif re.match(r"REFER[ÊE]NCIAS", txt, re.I): em_ref = True
109
-
110
- if em_ref:
111
- p.alignment = WD_ALIGN_PARAGRAPH.LEFT
112
- p.paragraph_format.first_line_indent = Cm(0)
113
- for r in p.runs: r.font.size = Pt(11)
114
- elif em_resumo and up not in ("RESUMO",) and not up.startswith("PALAVRAS"):
115
- p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
116
- for r in p.runs: r.font.italic = True; r.font.size = Pt(11)
117
- elif txt.isupper() and len(txt) < 120:
118
- p.alignment = WD_ALIGN_PARAGRAPH.CENTER
119
- p.paragraph_format.first_line_indent = Cm(0)
120
-
121
- def aplicar_italico_seguro(paragrafo, termo):
122
- texto = paragrafo.text
123
- if not re.search(rf'\b{re.escape(termo)}\b', texto, re.I): return False
124
- partes = re.split(rf'(\b{re.escape(termo)}\b)', texto, flags=re.I)
125
- paragrafo.clear()
126
- for parte in partes:
127
- run = paragrafo.add_run(parte)
128
- if parte.lower() == termo.lower(): run.italic = True
129
- return True
130
-
131
- # ─── UI ───────────────────────────────────────────────────────────────────
132
- st.title("⚖ Assistente Acadêmico - Direito UNIFAGOC")
133
- aba1, aba2, aba3 = st.tabs(["🔍 Auditoria", "🧠 Revisão", "📐 ABNT"])
134
-
135
- with aba1:
136
- fase = st.radio("Fase:", ["TCC 1", "TCC 2"], horizontal=True)
137
- c1, c2 = st.columns(2)
138
- with c1: proj = st.file_uploader("Pré-Projeto", type="docx", key="p1")
139
- with c2: tcc = st.file_uploader("TCC", type="docx", key="t1")
140
- if st.button("Auditar", type="primary", disabled=not(proj and tcc)):
141
- with st.status("Auditando...", expanded=True) as s:
142
- txt_p = extrair_texto_docx(proj.getvalue())
143
- txt_t = extrair_texto_docx(tcc.getvalue())
144
- regra = "TCC 1: não penalize ausência de Resumo/Conclusão." if fase=="TCC 1" else "TCC 2: trabalho completo."
145
- prompt = f"Você é examinador UNIFAGOC. {regra}\nPROJETO:\n{txt_p}\n\nTCC:\n{txt_t}\nAvalie 80 pontos."
146
- resp = chamar_gemini(prompt, AuditoriaTCC, 0.0)
147
- dados: AuditoriaTCC = resp.parsed
148
- s.update(label="Concluído", state="complete")
149
- st.metric("Aderência", f"{dados.aderencia_percentual}%")
150
- total = sum([dados.nota_tema_problema, dados.nota_introducao_metodo, dados.nota_estruturacao, dados.nota_formatacao_abnt])
151
- st.info(f"Nota textual: {total}/80 — {dados.justificativa_notas_banca}")
152
-
153
- with aba2:
154
- arq = st.file_uploader("TCC para revisão", type="docx", key="r1")
155
- if st.button("Revisar", disabled=not arq):
156
- with st.spinner("Revisando texto integral..."):
157
- txt = extrair_texto_docx(arq.getvalue())
158
- prompt = f"Revise ortografia, CAIXA ALTA indevida e liste termos latinos/estrangeiros para itálico. Texto:\n{txt}"
159
- resp = chamar_gemini(prompt, RevisaoInteligente, 0.1)
160
- rev: RevisaoInteligente = resp.parsed
161
- doc = Document(io.BytesIO(arq.getvalue()))
162
- mapa = {e.original: e.corrigido for e in rev.correcoes}
163
- for p in doc.paragraphs:
164
- for orig, corr in mapa.items():
165
- if orig in p.text:
166
- for r in p.runs: r.text = r.text.replace(orig, corr)
167
- for termo in rev.termos_estrangeiros_para_italico:
168
- for p in doc.paragraphs: aplicar_italico_seguro(p, termo)
169
- st.download_button("Baixar revisado", docx_to_bytes(doc), "TCC_Revisado.docx")
170
-
171
- with aba3:
172
- arq3 = st.file_uploader("TCC para ABNT", type="docx", key="a1")
173
- if st.button("Formatar", disabled=not arq3):
174
- doc = Document(io.BytesIO(arq3.getvalue()))
175
- st.write(f"Páginas estimadas: {estimar_paginas(doc)}")
176
- aplicar_formatacao_unifagoc(doc)
177
- refs = extrair_referencias_docx(doc)
178
- st.download_button("Baixar formatado", docx_to_bytes(doc), "TCC_ABNT.docx")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import io
3
  import re
4
+ import json
5
  import streamlit as st
6
  from docx import Document
7
  from docx.shared import Cm, Pt
8
  from docx.enum.text import WD_ALIGN_PARAGRAPH
9
+ from pydantic import BaseModel
10
  from google import genai
11
  from google.genai import types
 
12
 
13
+ # ─── CONFIGURAÇÃO DA PÁGINA ───────────────────────────────────────────────
14
+ st.set_page_config(page_title="Corretor TCC - UNIFAGOC", page_icon="⚖️", layout="wide")
 
 
 
 
 
 
15
 
16
+ # ─── ESQUEMAS DE DADOS (PYDANTIC) ─────────────────────────────────────────
17
  class AuditoriaTCC(BaseModel):
18
+ aderencia_percentual: int
19
+ diagnostico_geral: str
20
+ problema_identificado_no_projeto: str
21
+ tcc_respondeu_ao_problema: bool
22
+ justificativa_problema: str
23
+ objetivos_especificos_identificados: list[str]
24
+ objetivos_nao_cumpridos_no_tcc: list[str]
25
+ desvios_de_tema_ou_linguica: list[str]
26
+ indicios_texto_generico_ia: list[str]
27
+ nota_tema_problema: float
28
+ nota_introducao_metodo: float
29
+ nota_estruturacao: float
30
+ nota_formatacao_abnt: float
31
+ justificativa_notas_banca: str
32
 
33
  class ErroTexto(BaseModel):
34
  original: str
35
  corrigido: str
36
+ motivo: str
37
 
38
  class RevisaoInteligente(BaseModel):
39
+ correcoes: list[ErroTexto]
40
+ termos_estrangeiros_para_italico: list[str]
41
+
42
+ # ─── AUTENTICAÇÃO GEMINI ──────────────────────────────────────────────────
43
+ api_key = os.environ.get("GEMINI_API_KEY")
44
+ if not api_key:
45
+ st.error("❌ ERRO: A variável de ambiente GEMINI_API_KEY não foi encontrada nos Secrets.")
46
+ st.stop()
47
+
48
+ client = genai.Client(api_key=api_key)
49
+
50
+ # ─── FUNÇÕES AUXILIARES ───────────────────────────────────────────────────
51
+ def extrair_texto_docx(arquivo_upload) -> str:
52
+ doc = Document(arquivo_upload)
53
+ linhas = [para.text.strip() for para in doc.paragraphs if para.text.strip()]
54
+ return "\n\n".join(linhas)
55
+
56
+ def extrair_referencias_docx(doc: Document) -> list:
57
+ resultado = []
58
+ em_refs = False
59
+ for i, para in enumerate(doc.paragraphs):
60
+ texto = para.text.strip()
61
+ if not texto: continue
62
+ if re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", texto, re.IGNORECASE):
63
+ em_refs = True
64
+ continue
65
+ if em_refs:
66
+ if len(texto) < 120 and texto.isupper() and not texto[0].isdigit(): break
67
+ resultado.append((i, texto))
68
+ return resultado
69
 
70
  def docx_to_bytes(doc: Document) -> bytes:
71
+ byte_io = io.BytesIO()
72
+ doc.save(byte_io)
73
+ return byte_io.getvalue()
74
 
75
  def estimar_paginas(doc: Document) -> int:
76
+ palavras_totales = 0
77
+ for para in doc.paragraphs:
78
+ if re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", para.text, re.IGNORECASE): break
79
+ palavras_totales += len(para.text.split())
80
+ return max(1, palavras_totales // 350)
81
+
82
+ def aplicar_formatacao_estrutural_unifagoc(doc: Document):
83
+ """Aplica o template de forma segura, sem corromper o XML original."""
84
+ # 1. Margens Oficiais da Instituição
85
+ for section in doc.sections:
86
+ section.page_height = Cm(29.7)
87
+ section.page_width = Cm(21.0)
88
+ section.top_margin = Cm(3.0)
89
+ section.left_margin = Cm(3.0)
90
+ section.bottom_margin = Cm(2.0)
91
+ section.right_margin = Cm(2.0)
92
+
93
+ # 2. Configura o estilo "Normal" global para evitar quebra de runs
94
+ if 'Normal' in doc.styles:
95
+ style = doc.styles['Normal']
96
+ if style.font:
97
+ style.font.name = 'Calibri'
98
+ style.font.size = Pt(12)
99
+
100
+ em_referencias = False
101
+ em_resumo = False
102
+
103
+ # 3. Formatação Segura de Parágrafos
104
+ for para in doc.paragraphs:
105
+ texto = para.text.strip()
106
+ if not texto: continue
107
+
108
+ texto_upper = texto.upper()
109
+ if texto_upper.startswith("RESUMO"): em_resumo = True
110
+ elif texto_upper.startswith("INTRODUÇÃO"): em_resumo = False
111
+ elif re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", texto, re.IGNORECASE): em_referencias = True
112
+
113
+ # Alinhamento e Recuos
114
+ if em_referencias:
115
+ para.alignment = WD_ALIGN_PARAGRAPH.LEFT
116
+ para.paragraph_format.first_line_indent = Cm(0)
117
+ para.paragraph_format.space_after = Pt(6)
118
+ for run in para.runs:
119
+ run.font.name = 'Calibri'
120
+ run.font.size = Pt(11)
121
+
122
+ elif em_resumo:
123
+ para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
124
+ para.paragraph_format.first_line_indent = Cm(0)
125
+ for run in para.runs:
126
+ run.font.name = 'Calibri'
127
+ run.font.size = Pt(11)
128
+ if texto_upper != "RESUMO" and not texto_upper.startswith("PALAVRAS-CHAVE"):
129
+ run.font.italic = True
130
+
131
+ elif texto.isupper() and len(texto) < 100: # Identifica prováveis Títulos
132
+ para.alignment = WD_ALIGN_PARAGRAPH.CENTER
133
+ para.paragraph_format.first_line_indent = Cm(0)
134
+ for run in para.runs:
135
+ run.font.name = 'Calibri'
136
+ run.font.size = Pt(12)
137
+
138
+ else:
139
+ para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
140
+ para.paragraph_format.first_line_indent = Cm(1.25)
141
+ # Limpa formatações estranhas para forçar o herdeiro "Normal" (Calibri 12)
142
+ for run in para.runs:
143
+ run.font.name = 'Calibri'
144
+ run.font.size = Pt(12)
145
+
146
+ # ─── INTERFACE PRINCIPAL (ABAS) ───────────────────────────────────────────
147
+ st.title("⚖️ Assistente Acadêmico - Direito UNIFAGOC")
148
+ aba_auditoria, aba_revisao, aba_abnt = st.tabs(["🔍 Auditoria de Conteúdo e Notas", "🧠 Revisão Textual Inteligente", "📐 Formatação Total ABNT"])
149
+
150
+ # ==============================================================================
151
+ # ABA 1: AUDITORIA E PONTUAÇÃO UNIFAGOC
152
+ # ==============================================================================
153
+ with aba_auditoria:
154
+ st.markdown("Cruze o **Pré-Projeto** com o **TCC** para identificar fuga do tema e gerar a **Nota da Banca (80 pontos)**.")
155
+ fase_tcc = st.radio("Selecione a fase atual do trabalho:", ["TCC 1 (Apenas Introdução e Capítulo 1)", "TCC 2 (TCC Completo com Resumo e Conclusão)"], horizontal=True)
156
+
157
+ col1, col2 = st.columns(2)
158
+ with col1: arq_projeto = st.file_uploader("1. Envie o Pré-Projeto (.docx)", type=["docx"], key="proj")
159
+ with col2: arq_tcc = st.file_uploader("2. Envie os Capítulos do TCC (.docx)", type=["docx"], key="tcc_aud")
160
+
161
+ if st.button("🔍 Iniciar Auditoria e Avaliação", type="primary"):
162
+ if not arq_projeto or not arq_tcc:
163
+ st.warning("⚠️ Envie os DOIS arquivos para realizar a comparação.")
164
+ else:
165
+ with st.spinner("Avaliando argumentos e calculando notas da banca..."):
166
+ texto_projeto = extrair_texto_docx(arq_projeto)
167
+ texto_tcc = extrair_texto_docx(arq_tcc)
168
+ regra_fase = "ATENÇÃO: O aluno está em TCC 1 (Apenas Introdução e Capítulo 1). NÃO penalize a ausência de Resumo e Conclusão." if "TCC 1" in fase_tcc else "O aluno está em TCC 2. O trabalho deve estar completo (Resumo, Introdução, Desenvolvimento, Conclusão e Referências)."
169
+
170
+ prompt = f"""Você é um Professor Examinador de Direito da UNIFAGOC.
171
+ {regra_fase}
172
+
173
+ === PRÉ-PROJETO ===
174
+ {texto_projeto}
175
+
176
+ === TCC ===
177
+ {texto_tcc}
178
+
179
+ TAREFA 1: Identifique o Problema, Objetivos e procure por fugas de tema genéricas.
180
+ TAREFA 2: Avalie o TCC conforme os 80 pontos da FICHA DE AVALIAÇÃO DA UNIFAGOC:
181
+ - Tema, Delimitação do Problema e Objeto (0 a 20 pts)
182
+ - Introdução e Método (0 a 20 pts)
183
+ - Estruturação e Desenvolvimento (0 a 20 pts)
184
+ - Formatação e ABNT (0 a 20 pts)
185
+ Seja rigoroso e acadêmico na atribuição das notas.
186
+ """
187
+ config = types.GenerateContentConfig(temperature=0.0, response_mime_type="application/json", response_schema=AuditoriaTCC)
188
+
189
+ try:
190
+ resposta = client.models.generate_content(model="gemini-3.5-flash", contents=prompt, config=config)
191
+ dados = json.loads(resposta.text)
192
+
193
+ st.success(f"Auditoria e Avaliação concluídas para {fase_tcc.split(' ')[0]}!")
194
+
195
+ st.header("🏆 Avaliação da Banca UNIFAGOC")
196
+ st.markdown("*Distribuição de 80 pontos textuais (20 pts de apresentação não inclusos).*")
197
+
198
+ nota_total = sum([
199
+ dados.get('nota_tema_problema', 0),
200
+ dados.get('nota_introducao_metodo', 0),
201
+ dados.get('nota_estruturacao', 0),
202
+ dados.get('nota_formatacao_abnt', 0)
203
+ ])
204
+
205
+ c_nota1, c_nota2, c_nota3, c_nota4 = st.columns(4)
206
+ c_nota1.metric("Tema/Problema", f"{dados.get('nota_tema_problema')}/20")
207
+ c_nota2.metric("Intro/Método", f"{dados.get('nota_introducao_metodo')}/20")
208
+ c_nota3.metric("Estrutura/Linguagem", f"{dados.get('nota_estruturacao')}/20")
209
+ c_nota4.metric("Formatação/ABNT", f"{dados.get('nota_formatacao_abnt')}/20")
210
+
211
+ st.info(f"**NOTA TEXTUAL FINAL: {nota_total}/80 pontos** \n*Justificativa da Banca:* {dados.get('justificativa_notas_banca')}")
212
+ st.divider()
213
+
214
+ st.metric(label="Índice de Aderência ao Projeto", value=f"{dados.get('aderencia_percentual')}%")
215
+ st.write(f"**Diagnóstico:** {dados.get('diagnostico_geral')}")
216
+
217
+ c1, c2 = st.columns(2)
218
+ with c1:
219
+ st.subheader("🎯 Problema de Pesquisa")
220
+ st.write(f"**Promessa:** {dados.get('problema_identificado_no_projeto')}")
221
+ if dados.get("tcc_respondeu_ao_problema"): st.success(f"✅ O TCC respondeu: {dados.get('justificativa_problema')}")
222
+ else: st.error(f"❌ Não respondeu: {dados.get('justificativa_problema')}")
223
+ with c2:
224
+ st.subheader("📋 Objetivos")
225
+ for f in dados.get("objetivos_nao_cumpridos_no_tcc", []): st.error(f"Faltou: {f}")
226
+ if not dados.get("objetivos_nao_cumpridos_no_tcc"): st.success("✅ Objetivos abordados.")
227
+ st.subheader("🚨 Filtro Anti-Embromação")
228
+ for fuga in dados.get("desvios_de_tema_ou_linguica", []): st.warning(f"Fuga de Tema: {fuga}")
229
+ for ia in dados.get("indicios_texto_generico_ia", []): st.error(f"Texto Genérico/IA: {ia}")
230
+ except Exception as e:
231
+ if "429" in str(e): st.error("❌ A API está sobrecarregada. Aguarde 1 minuto.")
232
+ else: st.error(f"Erro: {e}")
233
+
234
+ # ==============================================================================
235
+ # ABA 2: REVISÃO TEXTUAL INTELIGENTE
236
+ # ==============================================================================
237
+ with aba_revisao:
238
+ st.markdown("A IA analisa o contexto para corrigir **Ortografia**, parágrafos indevidamente em **CAIXA ALTA** e aplica **Itálico** em termos estrangeiros/latinos.")
239
+ arq_revisao = st.file_uploader("Envie o TCC (.docx) para revisão inteligente", type=["docx"], key="rev")
240
+
241
+ if st.button("🧠 Realizar Revisão Inteligente"):
242
+ if arq_revisao:
243
+ with st.spinner("Lendo o documento para identificar jargões, caixa alta e erros..."):
244
+ texto = extrair_texto_docx(arq_revisao)
245
+ prompt_rev = f"""Analise o texto acadêmico de Direito abaixo e identifique:
246
+ 1. Erros ortográficos e gramaticais.
247
+ 2. Trechos do corpo do texto que estão indevidamente em CAIXA ALTA (retorne a versão corrigida). Ignore títulos.
248
+ 3. Termos estrangeiros ou jargões em latim (ex: habeas corpus, fake news) que necessitam de itálico.
249
+ TEXTO: {texto[:25000]}"""
250
+
251
+ try:
252
+ res = client.models.generate_content(
253
+ model="gemini-3.5-flash",
254
+ contents=prompt_rev,
255
+ config=types.GenerateContentConfig(temperature=0.1, response_mime_type="application/json", response_schema=RevisaoInteligente)
256
+ )
257
+ dados_rev = json.loads(res.text)
258
+ correcoes = dados_rev.get("correcoes", [])
259
+ italicos = dados_rev.get("termos_estrangeiros_para_italico", [])
260
+
261
+ if not correcoes and not italicos:
262
+ st.success("🎉 O texto está perfeitamente revisado e sem jargões para formatar.")
263
+ else:
264
+ doc_rev = Document(arq_revisao)
265
+ aplicadas_texto, aplicadas_italico = 0, 0
266
+
267
+ if correcoes:
268
+ st.subheader("📝 Correções de Texto e Caixa Alta")
269
+ for c in correcoes:
270
+ orig, corr = c.get("original", ""), c.get("corrigido", "")
271
+ st.write(f"❌ `{orig}` ➔ ✅ `{corr}` (*{c.get('motivo')}*)")
272
+ for para in doc_rev.paragraphs:
273
+ if orig in para.text:
274
+ for run in para.runs:
275
+ if orig in run.text:
276
+ run.text = run.text.replace(orig, corr, 1)
277
+ aplicadas_texto += 1
278
+ break
279
+
280
+ if italicos:
281
+ st.subheader("🪄 Termos Estrangeiros (Itálico Aplicado)")
282
+ st.write(", ".join([f"*{i}*" for i in italicos]))
283
+ for termo in italicos:
284
+ for para in doc_rev.paragraphs:
285
+ matches = list(re.finditer(r'\b' + re.escape(termo) + r'\b', para.text, re.IGNORECASE))
286
+ for match in matches:
287
+ pos_ini, pos_fim, pos_atual = match.start(), match.end(), 0
288
+ for run in para.runs:
289
+ fim_run = pos_atual + len(run.text)
290
+ if pos_atual < pos_fim and fim_run > pos_ini:
291
+ run.italic = True
292
+ aplicadas_italico += 1
293
+ pos_atual += len(run.text)
294
+
295
+ st.success(f"✅ {aplicadas_texto} correções de texto e {aplicadas_italico} itálicos aplicados!")
296
+ st.download_button("⬇️ Baixar Documento Revisado (.docx)", data=docx_to_bytes(doc_rev), file_name="TCC_Revisado.docx", mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document")
297
+ except Exception as e:
298
+ if "429" in str(e): st.error("❌ A API está sobrecarregada. Aguarde 1 minuto.")
299
+ else: st.error(f"Erro ao processar: {e}")
300
+
301
+ # ==============================================================================
302
+ # ABA 3: FORMATAÇÃO TOTAL E REFERÊNCIAS ABNT
303
+ # ==============================================================================
304
+ with aba_abnt:
305
+ st.markdown("Varre todo o documento forçando a fonte **Calibri**, Margens **3x2**, Alinhamento e Espaçamentos do edital.")
306
+ st.warning("⏱️ **Aviso:** Se muitos alunos tentarem formatar ao mesmo tempo, aguarde 60 segundos.")
307
+
308
+ arq_abnt = st.file_uploader("Envie o TCC (.docx) para formatar o layout estrutural", type=["docx"], key="abnt")
309
+
310
+ if st.button("📐 Formatar Layout e Referências"):
311
+ if arq_abnt:
312
+ with st.spinner("Padronizando fontes, margens e referências... Isso pode levar um minuto."):
313
+ doc_abnt = Document(arq_abnt)
314
+
315
+ paginas_estimadas = estimar_paginas(doc_abnt)
316
+ if paginas_estimadas > 18: st.error(f"🚨 **Atenção:** Seu TCC tem aprox. {paginas_estimadas} páginas. O limite da banca é 18.")
317
+ else: st.info(f"��� Extensão estimada: {paginas_estimadas} páginas.")
318
+
319
+ aplicar_formatacao_estrutural_unifagoc(doc_abnt)
320
+ st.success("✅ Estrutura Física (Margens, Fontes e Alinhamento) salva com sucesso!")
321
+
322
+ referencias = extrair_referencias_docx(doc_abnt)
323
+ if not referencias:
324
+ st.warning("Aviso: Nenhuma seção com o título 'REFERÊNCIAS' foi localizada.")
325
+ st.download_button("⬇️ Baixar TCC Formatado", data=docx_to_bytes(doc_abnt), file_name="TCC_Padrao_UNIFAGOC.docx", mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document")
326
+ else:
327
+ texto_refs = "\n".join(f"{i+1}. {texto}" for i, (_, texto) in enumerate(referencias))
328
+ prompt_abnt = f"Verifique as referências abaixo seguindo a ABNT NBR 6023:2018. Retorne um JSON:\n{{\"problemas\": [{{\"numero\": int, \"erros\": [\"erro\"], \"elemento_negrito\": \"palavra destaque\"}}]}}\nREFERÊNCIAS:\n{texto_refs}"
329
+
330
+ try:
331
+ res = client.models.generate_content(
332
+ model="gemini-3.5-flash",
333
+ contents=prompt_abnt,
334
+ config=types.GenerateContentConfig(temperature=0.0, response_mime_type="application/json")
335
+ )
336
+ problemas = json.loads(res.text).get("problemas", [])
337
+
338
+ def aplicar_negrito_ref(doc, refs_problematicas):
339
+ em_refs, ref_idx = False, 0
340
+ for para in doc.paragraphs:
341
+ texto = para.text.strip()
342
+ if not texto: continue
343
+ if re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", texto, re.IGNORECASE):
344
+ em_refs = True
345
+ continue
346
+ if not em_refs or len(texto) < 10: continue
347
+
348
+ nums_prob = {p.get("numero", 0) - 1 for p in refs_problematicas}
349
+ if ref_idx in nums_prob:
350
+ match = re.match(r"^((?:[A-ZÁÉÍÓÚÀÂÊÔÃÕÇ][^.;]+[.;]\s*)+)\s*(.+?)(\.\s+(?:\d+\.\s+ed|[A-Z]).*|$)", para.text)
351
+ if match:
352
+ autoria, titulo = match.group(1).strip(), match.group(2).strip()
353
+ pos_atual, pos_ini = 0, para.text.find(titulo, len(autoria))
354
+ pos_fim = pos_ini + len(titulo)
355
+ for run in para.runs:
356
+ fim_run = pos_atual + len(run.text)
357
+ if pos_atual >= pos_ini and fim_run <= pos_fim: run.bold = True
358
+ elif pos_atual < pos_fim and fim_run > pos_ini: run.bold = True
359
+ pos_atual += len(run.text)
360
+ ref_idx += 1
361
+
362
+ aplicar_negrito_ref(doc_abnt, problemas)
363
+
364
+ if problemas:
365
+ for p in problemas: st.write(f"⚠️ Referência #{p['numero']} ajustada: {', '.join(p['erros'])}")
366
+ st.success("✅ Formatação de destaque (negrito) nas referências aplicada!")
367
+
368
+ st.download_button(
369
+ label="⬇️ Baixar TCC Totalmente Formatado (.docx)",
370
+ data=docx_to_bytes(doc_abnt),
371
+ file_name="TCC_Padrao_UNIFAGOC.docx",
372
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
373
+ )
374
+ except Exception as e:
375
+ if "429" in str(e): st.error("❌ A API está sobrecarregada. Aguarde 1 minuto.")
376
+ else: st.error(f"Erro na verificação pela IA: {e}")