eroha-agentapi / app.py
Yermek68's picture
Update app.py
928a3cd verified
raw
history blame
6.92 kB
import os
from datetime import datetime
import gradio as gr
from transformers import pipeline
import pdfplumber
from docx import Document
from fpdf import FPDF
# ===== НАСТРОЙКИ =====
# Имя шрифта TTF, который лежит в корне Space (Files → root)
FONT_PATH = "DejaVuSans.ttf"
FONT_FAMILY = "DejaVu"
# Максимальная длина текста в одном заходе в модель (по символам)
# Это грубая оценка, чтобы не превышать лимит ~1024 токена у BART
CHUNK_SIZE = 2000
# Ленивая инициализация summarizer, чтобы не грузить модель при импортe
_summarizer = None
def get_summarizer():
global _summarizer
if _summarizer is None:
_summarizer = pipeline(
"summarization",
model="facebook/bart-large-cnn"
)
return _summarizer
# ===== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ =====
def read_text_from_file(file_path: str) -> str:
"""Читает текст из PDF или TXT."""
if not file_path:
return ""
path_lower = file_path.lower()
# PDF
if path_lower.endswith(".pdf"):
text = []
with pdfplumber.open(file_path) as pdf:
for page in pdf.pages:
page_text = page.extract_text() or ""
text.append(page_text)
return "\n".join(text)
# TXT (или любой другой текстовый)
with open(file_path, "rb") as f:
raw = f.read()
return raw.decode("utf-8", errors="ignore")
def split_into_chunks(text: str, chunk_size: int = CHUNK_SIZE):
"""Режет длинный текст на куски по chunk_size символов."""
text = text.strip()
if len(text) <= chunk_size:
return [text]
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
# стараемся резать по границе предложения/абзаца
if end < len(text):
dot_pos = text.rfind(".", start, end)
newline_pos = text.rfind("\n", start, end)
sep_pos = max(dot_pos, newline_pos)
if sep_pos > start + chunk_size * 0.3:
end = sep_pos + 1
chunks.append(text[start:end].strip())
start = end
return [c for c in chunks if c]
def summarize_long_text(text: str) -> str:
"""Суммаризирует длинный текст по частям и склеивает результат."""
summarizer = get_summarizer()
chunks = split_into_chunks(text)
summaries = []
for chunk in chunks:
# подстрахуемся от совсем коротких кусков
if len(chunk) < 50:
continue
result = summarizer(
chunk,
max_length=200,
min_length=50,
do_sample=False
)
summaries.append(result[0]["summary_text"].strip())
if not summaries:
return "⚠️ Не удалось создать осмысленное резюме (слишком мало текста)."
return "\n\n".join(summaries)
def save_docx(summary_text: str) -> str:
"""Сохраняет резюме в DOCX и возвращает путь к файлу."""
filename = f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
doc = Document()
doc.add_heading("Резюме документа", level=1)
for paragraph in summary_text.split("\n\n"):
doc.add_paragraph(paragraph)
doc.save(filename)
return filename
def save_pdf(summary_text: str) -> str | None:
"""
Сохраняет резюме в PDF и возвращает путь к файлу.
Если шрифт не найден или не подключился — возвращает None,
чтобы не падать с Unicode ошибкой.
"""
if not os.path.exists(FONT_PATH):
# Шрифт не найден — лучше вернуть None, чем падать
return None
filename = f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
pdf = FPDF()
pdf.add_page()
# Регистрируем Unicode-шрифт
try:
pdf.add_font(FONT_FAMILY, "", FONT_PATH, uni=True)
except Exception as e:
# Если даже тут что-то пошло не так — не ломаем всё приложение
print(f"Ошибка подключения шрифта для PDF: {e}")
return None
pdf.set_font(FONT_FAMILY, size=12)
# Пишем текст резюме
for line in summary_text.split("\n"):
pdf.multi_cell(0, 8, line)
pdf.ln(0.5)
pdf.output(filename)
return filename
# ===== ОСНОВНАЯ ФУНКЦИЯ ДЛЯ GRADIO =====
def summarize_file(file) -> tuple[str, str | None, str | None]:
"""
Основной обработчик:
1) читает файл,
2) делает суммаризацию,
3) сохраняет DOCX и PDF.
Возвращает: (текстовое резюме, путь к DOCX, путь к PDF).
"""
if file is None:
return "⚠️ Пожалуйста, загрузите файл.", None, None
try:
text = read_text_from_file(file.name)
if len(text.strip()) < 50:
return "⚠️ Слишком короткий текст для суммаризации.", None, None
summary_text = summarize_long_text(text)
docx_path = save_docx(summary_text)
pdf_path = save_pdf(summary_text)
# Если PDF не создался (нет шрифта) — просто не отдаём файл
return summary_text, docx_path, pdf_path
except Exception as e:
# Логируем в консоль Space, а пользователю — аккуратное сообщение
print(f"Ошибка при суммаризации: {e}")
return f"❌ Ошибка суммаризации: {e}", None, None
# ===== ИНТЕРФЕЙС GRADIO =====
demo = gr.Interface(
fn=summarize_file,
inputs=gr.File(label="Загрузите файл (.pdf или .txt)"),
outputs=[
gr.Textbox(label="Результат суммаризации"),
gr.File(label="Скачать DOCX"),
gr.File(label="Скачать PDF"),
],
title="Eroha Summarizer 🧠",
description=(
"Загрузите документ (PDF или TXT), модель создаст краткое резюме. "
"Результат можно скачать в DOCX и PDF. Для корректного PDF нужен файл шрифта "
f"{FONT_PATH} в корне Space."
),
)
if __name__ == "__main__":
demo.launch()