Yermek68 commited on
Commit
9c815d5
·
verified ·
1 Parent(s): b4f7ed9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -193
app.py CHANGED
@@ -1,207 +1,61 @@
1
- import os
2
- from datetime import datetime
3
-
4
  import gradio as gr
5
  from transformers import pipeline
6
  import pdfplumber
7
- from docx import Document
8
- from fpdf import FPDF
9
-
10
- # ===== НАСТРОЙКИ =====
11
-
12
- # Имя шрифта TTF, который лежит в корне Space (Files → root)
13
- FONT_PATH = "DejaVuSans.ttf"
14
- FONT_FAMILY = "DejaVu"
15
-
16
- # Максимальная длина текста в одном заходе в модель (по символам)
17
- # Это грубая оценка, чтобы не превышать лимит ~1024 токена у BART
18
- CHUNK_SIZE = 2000
19
-
20
- # Ленивая инициализация summarizer, чтобы не грузить модель при импортe
21
- _summarizer = None
22
-
23
 
24
- def get_summarizer():
25
- global _summarizer
26
- if _summarizer is None:
27
- _summarizer = pipeline(
28
- "summarization",
29
- model="facebook/bart-large-cnn"
30
- )
31
- return _summarizer
32
 
33
-
34
- # ===== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ =====
35
-
36
- def read_text_from_file(file_path: str) -> str:
37
- """Читает текст из PDF или TXT."""
38
- if not file_path:
39
  return ""
40
-
41
- path_lower = file_path.lower()
42
-
43
- # PDF
44
- if path_lower.endswith(".pdf"):
45
- text = []
46
- with pdfplumber.open(file_path) as pdf:
47
  for page in pdf.pages:
48
- page_text = page.extract_text() or ""
49
- text.append(page_text)
50
- return "\n".join(text)
51
-
52
- # TXT (или любой другой текстовый)
53
- with open(file_path, "rb") as f:
54
- raw = f.read()
55
- return raw.decode("utf-8", errors="ignore")
56
-
57
-
58
- def split_into_chunks(text: str, chunk_size: int = CHUNK_SIZE):
59
- """Режет длинный текст на куски по chunk_size символов."""
60
- text = text.strip()
61
- if len(text) <= chunk_size:
62
- return [text]
63
-
64
- chunks = []
65
- start = 0
66
- while start < len(text):
67
- end = start + chunk_size
68
-
69
- # стараемся резать по границе предложения/абзаца
70
- if end < len(text):
71
- dot_pos = text.rfind(".", start, end)
72
- newline_pos = text.rfind("\n", start, end)
73
- sep_pos = max(dot_pos, newline_pos)
74
- if sep_pos > start + chunk_size * 0.3:
75
- end = sep_pos + 1
76
-
77
- chunks.append(text[start:end].strip())
78
- start = end
79
-
80
- return [c for c in chunks if c]
81
-
82
-
83
- def summarize_long_text(text: str) -> str:
84
- """Суммаризирует длинный текст по частям и склеивает результат."""
85
- summarizer = get_summarizer()
86
- chunks = split_into_chunks(text)
87
-
88
  summaries = []
89
  for chunk in chunks:
90
- # подстрахуемся от совсем коротких кусков
91
- if len(chunk) < 50:
92
- continue
93
- result = summarizer(
94
- chunk,
95
- max_length=200,
96
- min_length=50,
97
- do_sample=False
98
- )
99
- summaries.append(result[0]["summary_text"].strip())
100
-
101
- if not summaries:
102
- return "⚠️ Не удалось создать осмысленное резюме (слишком мало текста)."
103
-
104
- return "\n\n".join(summaries)
105
-
106
-
107
- def save_docx(summary_text: str) -> str:
108
- """Сохраняет резюме в DOCX и возвращает путь к файлу."""
109
- filename = f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
110
- doc = Document()
111
- doc.add_heading("Резюме документа", level=1)
112
- for paragraph in summary_text.split("\n\n"):
113
- doc.add_paragraph(paragraph)
114
- doc.save(filename)
115
- return filename
116
-
117
-
118
- def save_pdf(summary_text: str) -> str | None:
119
- """
120
- Создаёт PDF и возвращает путь к файлу.
121
- """
122
-
123
- FONT_PATH = "DejaVuSans.ttf"
124
- FONT_FAMILY = "DejaVu"
125
-
126
- # Проверяем что шрифт точно есть
127
- if not os.path.exists(FONT_PATH):
128
- print("‼ Шрифт не найден в корне проекта.")
129
- return None
130
-
131
- # Формируем имя файла
132
- filename = f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
133
-
134
- pdf = FPDF()
135
- pdf.add_page()
136
-
137
- # Подключаем Unicode-шрифт
138
- try:
139
- pdf.add_font(FONT_FAMILY, "", FONT_PATH, uni=True)
140
- pdf.set_font(FONT_FAMILY, size=12)
141
- except Exception as e:
142
- print(f"Ошибка подключения шрифта: {e}")
143
- return None
144
-
145
- # Записываем текст
146
- for line in summary_text.split("\n"):
147
- pdf.multi_cell(0, 8, line)
148
- pdf.ln(0.5)
149
-
150
- pdf.output(filename)
151
- return filename
152
-
153
-
154
-
155
- # ===== ОСНОВНАЯ ФУНКЦИЯ ДЛЯ GRADIO =====
156
-
157
- def summarize_file(file) -> tuple[str, str | None, str | None]:
158
- """
159
- Основной обработчик:
160
- 1) читает файл,
161
- 2) делает суммаризацию,
162
- 3) сохраняет DOCX и PDF.
163
- Возвращает: (текстовое резюме, путь к DOCX, путь к PDF).
164
- """
165
- if file is None:
166
- return "⚠️ Пожалуйста, загрузите файл.", None, None
167
-
168
- try:
169
- text = read_text_from_file(file.name)
170
-
171
- if len(text.strip()) < 50:
172
- return "⚠️ Слишком короткий текст для суммаризации.", None, None
173
-
174
- summary_text = summarize_long_text(text)
175
-
176
- docx_path = save_docx(summary_text)
177
- pdf_path = save_pdf(summary_text)
178
-
179
- # Если PDF не создался (нет шрифта) — просто не отдаём файл
180
- return summary_text, docx_path, pdf_path
181
-
182
- except Exception as e:
183
- # Логируем в консоль Space, а пользователю — аккуратное сообщение
184
- print(f"Ошибка при суммаризации: {e}")
185
- return f"❌ Ошибка суммаризации: {e}", None, None
186
-
187
-
188
- # ===== ИНТЕРФЕЙС GRADIO =====
189
 
190
  demo = gr.Interface(
191
  fn=summarize_file,
192
- inputs=gr.File(label="Загрузите файл (.pdf или .txt)"),
193
- outputs=[
194
- gr.Textbox(label="Результат суммаризации"),
195
- gr.File(label="Скачать DOCX"),
196
- gr.File(label="Скачать PDF"),
197
- ],
198
- title="Eroha Summarizer 🧠",
199
- description=(
200
- "Загрузите документ (PDF или TXT), модель создаст краткое резюме. "
201
- "Результат можно скачать в DOCX и PDF. Для корректного PDF нужен файл шрифта "
202
- f"{FONT_PATH} в корне Space."
203
- ),
204
  )
205
 
206
- if __name__ == "__main__":
207
- demo.launch()
 
 
 
 
1
  import gradio as gr
2
  from transformers import pipeline
3
  import pdfplumber
4
+ import docx2txt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
+ # Загружаем модель
7
+ summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
 
 
 
 
 
 
8
 
9
+ def read_file(file):
10
+ """Чтение PDF, DOCX и TXT"""
11
+ if file is None:
 
 
 
12
  return ""
13
+
14
+ if file.name.endswith(".pdf"):
15
+ text = ""
16
+ with pdfplumber.open(file.name) as pdf:
 
 
 
17
  for page in pdf.pages:
18
+ text += page.extract_text() or ""
19
+ return text
20
+
21
+ elif file.name.endswith(".docx"):
22
+ return docx2txt.process(file.name)
23
+
24
+ else:
25
+ return file.read().decode("utf-8", errors="ignore")
26
+
27
+ def summarize_text(text):
28
+ """Суммаризация текста по частям (чтобы избежать ошибок с длинными документами)"""
29
+ if len(text.strip()) == 0:
30
+ return "⚠️ Пустой документ."
31
+
32
+ # Ограничение модели — 1024 токена ≈ 3000 символов
33
+ chunk_size = 2500
34
+ chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
35
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  summaries = []
37
  for chunk in chunks:
38
+ try:
39
+ summary = summarizer(chunk, max_length=150, min_length=40, do_sample=False)
40
+ summaries.append(summary[0]["summary_text"])
41
+ except Exception as e:
42
+ summaries.append(f"[Ошибка в части: {str(e)}]")
43
+
44
+ final_summary = "\n\n".join(summaries)
45
+ return final_summary.strip()
46
+
47
+ def summarize_file(file):
48
+ text = read_file(file)
49
+ if not text:
50
+ return "⚠️ Не удалось прочитать файл. Поддерживаются PDF, DOCX и TXT."
51
+ return summarize_text(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  demo = gr.Interface(
54
  fn=summarize_file,
55
+ inputs=gr.File(label="Загрузите документ (.pdf, .docx или .txt)"),
56
+ outputs=gr.Textbox(label="Краткое резюме"),
57
+ title="🧠 Eroha Summarizer",
58
+ description="Загрузите PDF, Word или текстовый файл. Модель создаст краткое резюме по содержанию документа.",
 
 
 
 
 
 
 
 
59
  )
60
 
61
+ demo.launch(server_name="0.0.0.0", server_port=7860)