asasasaasasa commited on
Commit
7e7e897
·
verified ·
1 Parent(s): a15e3eb

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +261 -319
main.py CHANGED
@@ -1,46 +1,51 @@
1
  import os
2
- import io
3
- import uuid
4
- import tempfile
5
- import logging
6
- import threading
7
- import traceback
8
- from datetime import datetime
9
- from docx import Document
10
-
11
  import streamlit as st
 
12
  from dotenv import load_dotenv
13
 
14
- # ====== БАЗОВАЯ НАСТРОЙКА ======
15
  load_dotenv()
16
- from config import ENV_DEFAULTS, DEFAULT_CONFIG # после .env, чтобы приоритет был у env
17
 
18
- # Логирование
 
 
 
19
  log_level = os.environ.get('LOGLEVEL', DEFAULT_CONFIG['LOGLEVEL']).upper()
20
  logging.basicConfig(
21
  level=getattr(logging, log_level),
22
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
23
- handlers=[logging.StreamHandler() if log_level != 'WARNING' else logging.NullHandler()]
 
 
 
24
  )
25
 
26
- # Конфиг приложения
27
- st.set_page_config(page_title="Translator & Readability", page_icon="🗨️", layout="wide")
28
 
29
- # Подставляем дефолты переменных окружения при их отсутствии
30
  for var, default in ENV_DEFAULTS.items():
31
  if var not in os.environ:
32
  logging.debug(f"Environment variable {var} not found, using default: {default}")
33
  os.environ[var] = default
34
 
 
35
  MODEL_CONFIG = {
36
  "max_parallel_models": DEFAULT_CONFIG["MAX_PARALLEL_MODELS"],
37
  "session_timeout": DEFAULT_CONFIG["SESSION_TIMEOUT"],
38
  "allow_gpu": DEFAULT_CONFIG["ALLOW_GPU"]
39
  }
40
 
 
 
41
  model_semaphore = threading.Semaphore(MODEL_CONFIG["max_parallel_models"])
42
 
43
- # ====== ИМПОРТЫ МОДЕЛЕЙ/УТИЛИТ ======
 
 
 
 
 
44
  from models.nltk_resources import setup_nltk
45
  from utils.file_readers import read_file
46
  from utils.text_processing import detect_language
@@ -52,351 +57,273 @@ from utils.readability_indices import (
52
  highlight_complex_text
53
  )
54
  from utils.formatting import color_code_index
55
- from utils.tilmash_translation import tilmash_translate, display_tilmash_streaming_translation, TilmashTranslator
56
 
57
-
58
- # ====== СЕССИИ ======
59
  if 'session_id' not in st.session_state:
60
  st.session_state.session_id = str(uuid.uuid4())
 
61
  if 'translation_lock' not in st.session_state:
62
  st.session_state.translation_lock = False
63
- if 'analysis_lock' not in st.session_state:
64
- st.session_state.analysis_lock = False
65
-
66
-
67
- # ====== UI-ХЕЛПЕРЫ ======
68
- def columns_safe(spec, **kwargs):
69
- """Совместимый вызов st.columns: игнорирует неподдерживаемые аргументы (например, vertical_alignment)."""
70
- try:
71
- return st.columns(spec, **kwargs)
72
- except TypeError:
73
- return st.columns(spec)
74
-
75
- def badge(text: str, kind: str = "neutral"):
76
- palette = {
77
- "ok": "#16a34a",
78
- "warn": "#f59e0b",
79
- "err": "#ef4444",
80
- "info": "#3b82f6",
81
- "neutral": "#6b7280"
82
- }
83
- color = palette.get(kind, palette["neutral"])
84
- st.markdown(
85
- f"""
86
- <span style="
87
- display:inline-block;padding:2px 8px;border-radius:999px;
88
- font-size:12px;line-height:18px;background:{color}20;color:{color};
89
- border:1px solid {color}50;
90
- ">{text}</span>
91
- """,
92
- unsafe_allow_html=True
93
- )
94
-
95
- def section_title(title: str, subtitle: str = ""):
96
- st.markdown(
97
- f"""
98
- <div style="margin:8px 0 2px 0;font-weight:700;font-size:20px;">{title}</div>
99
- {"<div style='color:#6b7280;margin-bottom:12px;'>"+subtitle+"</div>" if subtitle else ""}
100
- """,
101
- unsafe_allow_html=True
102
- )
103
-
104
- def horizontal_rule():
105
- st.markdown('<hr style="margin:12px 0;opacity:.2;">', unsafe_allow_html=True)
106
-
107
-
108
- # ====== БОКОВАЯ ПАНЕЛЬ ======
109
- with st.sidebar:
110
- st.header("Панель")
111
- with st.expander("Сведения о сессии", expanded=False):
112
- st.code(f"Session: {st.session_state.session_id}")
113
- st.caption(datetime.now().strftime("Started: %Y-%m-%d %H:%M:%S"))
114
-
115
- # GPU-параметр
116
- if MODEL_CONFIG["allow_gpu"]:
117
- st.session_state.use_gpu = st.checkbox("Использовать GPU (если доступен)", value=True)
118
- try:
119
- import torch
120
- if st.session_state.use_gpu:
121
- if torch.cuda.is_available():
122
- badge(f"CUDA: {torch.cuda.get_device_name(0)}", "ok")
123
- elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
124
- badge("Apple Silicon (MPS) доступен", "ok")
125
- else:
126
- badge("GPU не обнаружен → CPU", "warn")
127
- st.session_state.use_gpu = False
128
- except ImportError:
129
- badge("PyTorch не установлен → CPU", "warn")
130
- st.session_state.use_gpu = False
131
- else:
132
- st.session_state.use_gpu = False
133
- st.caption("GPU отключён в конфигурации.")
134
 
135
- horizontal_rule()
136
- st.caption("Подсказка: используйте вкладки сверху для переключения режимов.")
137
 
 
 
138
 
139
- # ====== ВЕРХНИЙ ХЕДЕР ======
140
- st.title("Translation & Readability Analysis")
141
- st.caption("Перевод между 🇰🇿 🇷🇺 🇬🇧 и анализ удобочитаемости текста. Без сторонних библиотек — только Streamlit.")
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- # ====== ВКЛАДКИ ======
145
- tab_translate, tab_readability = st.tabs(["📝 Перевод", "📊 Удобочитуемость"])
146
-
147
-
148
- # ====== ПЕРЕВОД ======
149
- with tab_translate:
150
- section_title("Перевод (Kazakh, Russian, English)",
151
- "Загрузите файл или вставьте текст. Язык определится автоматически (можно задать вручную).")
152
-
153
- c1, c2 = columns_safe([1, 2]) # удалён vertical_alignment
154
- with c1:
155
- input_mode = st.radio("Источник текста:", ["Загрузить файл", "Вставить текст"], horizontal=True)
156
- with c2:
157
- clear_input = st.button("Очистить ввод", use_container_width=True)
158
-
159
  input_text = ""
160
- file_name = None
161
 
162
- if input_mode == "Загрузить файл":
163
  uploaded_file = st.file_uploader("Выберите файл (.txt, .docx, .pdf)", type=["txt", "docx", "pdf"])
164
  if uploaded_file is not None:
165
- file_name = uploaded_file.name
166
- suffix = os.path.splitext(file_name)[1]
167
- with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
168
- tmp.write(uploaded_file.getbuffer())
169
- temp_path = tmp.name
170
- input_text = read_file(temp_path)
171
- os.remove(temp_path)
172
-
173
- with st.expander("Предпросмотр файла (только чтение)", expanded=False):
174
- st.text_area("Содержимое", value=input_text, height=160, disabled=True)
 
 
 
 
 
175
  else:
176
- default_val = "" if clear_input else st.session_state.get("last_input_text", "")
177
- input_text = st.text_area("Вставьте ваш текст здесь", height=220, value=default_val)
178
- st.session_state.last_input_text = input_text
179
 
180
  if input_text:
181
- horizontal_rule()
182
- st.write("Параметры")
183
- col_lang1, col_lang2, col_meta = st.columns([1, 1, 1.2])
184
- with col_lang1:
185
- auto_detect = st.checkbox("Автоопределение языка", value=True)
186
- if auto_detect:
187
- detected = detect_language(input_text)
188
- if detected in ["ru", "en", "kk"]:
189
- badge(f"Язык: {detected}", "info")
190
- src_lang = detected
191
- else:
192
- badge("Не удалось однозначно определить язык", "warn")
193
- src_lang = st.selectbox("Язык текста", ["ru", "en", "kk"])
194
  else:
 
195
  src_lang = st.selectbox("Язык текста", ["ru", "en", "kk"])
 
 
 
 
 
 
 
 
 
196
 
197
- with col_lang2:
198
- tgt_options = {"ru": ["en", "kk"], "en": ["ru", "kk"], "kk": ["ru", "en"]}[src_lang]
199
- tgt_lang = st.selectbox("Перевести на", tgt_options)
200
-
201
- with col_meta:
202
- # Простая статистика
203
- words = len(input_text.split())
204
- chars = len(input_text)
205
- st.metric("Символов", f"{chars:,}".replace(",", " "))
206
- st.metric("Слов", f"{words:,}".replace(",", " "))
207
- if file_name:
208
- st.caption(f"Файл: **{file_name}**")
209
-
210
- horizontal_rule()
211
- translate_btn = st.button("🚀 Перевести", type="primary")
212
- if translate_btn:
213
  if st.session_state.translation_lock:
214
  st.warning("Перевод уже выполняется. Пожалуйста, дождитесь завершения.")
215
- else:
216
- st.session_state.translation_lock = True
 
 
 
 
 
 
 
 
 
 
 
217
  try:
218
- acquired = model_semaphore.acquire(blocking=False)
219
- if not acquired:
220
- st.warning("Максимум параллельных процессов достигнут. Попробуйте позже.")
221
- else:
222
- # Оценка прогресса: наивно по доле сгенерированного текста
223
- progress = st.progress(0)
224
- placeholder = st.empty()
225
- result = ""
226
 
 
 
 
 
 
 
 
227
  try:
228
- # Инициализируем переводчик (учитываем GPU-параметр)
229
- translator = TilmashTranslator(use_gpu=st.session_state.get('use_gpu', False))
230
-
231
- approx_len = max(1, len(input_text))
232
- with st.spinner("Выполняется перевод..."):
233
- for chunk in translator.translate_streaming(input_text, src_lang, tgt_lang):
234
- result += chunk
235
- # Обновляем UI "по ходу"
236
- placeholder.markdown(result)
237
- # Обновляем прогресс до 95%, финал уже после
238
- done_ratio = min(0.95, len(result) / approx_len)
239
- progress.progress(done_ratio)
240
 
241
  except Exception as e:
242
- st.error(f"Ошибка перевода: {str(e)}")
243
  logging.error(f"Tilmash translation error: {traceback.format_exc()}")
244
  result = None
245
 
246
- # Финализация
247
- if result:
248
- progress.progress(1.0)
249
- horizontal_rule()
250
- section_title("Результат перевода")
251
- st.markdown(result)
252
-
253
- # Скачать DOCX
254
- doc = Document()
255
- doc.add_paragraph(result)
256
- doc_io = io.BytesIO()
257
- doc.save(doc_io)
258
- doc_io.seek(0)
259
- st.download_button(
260
- label="⬇️ Скачать переведённый текст (.docx)",
261
- data=doc_io,
262
- file_name="translated_text.docx",
263
- mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
264
- use_container_width=True
265
- )
266
- else:
267
- st.warning("Не удалось выполнить перевод.")
268
-
269
- # Освобождаем ресурсы модели
270
- try:
271
- if 'translator' in locals() and getattr(translator, "initialized", False):
272
- translator.unload_model()
273
- except Exception as unload_error:
274
- logging.error(f"Error unloading Tilmash model: {str(unload_error)}")
275
- except Exception as outer_error:
276
- st.error(f"Unexpected error: {str(outer_error)}")
277
- logging.error(f"Unexpected error: {traceback.format_exc()}")
278
  finally:
279
- if model_semaphore._value < MODEL_CONFIG["max_parallel_models"]:
280
- model_semaphore.release()
281
- st.session_state.translation_lock = False
282
-
283
-
284
- # ====== УДОБОЧИТАЕМОСТЬ ======
285
- with tab_readability:
286
- section_title("Анализ удобочитаемости текста",
287
- "Поддерживаются 🇰🇿 🇷🇺 🇬🇧. Индексы: Flesch, F-K Grade, Gunning Fog, SMOG.")
288
-
289
- r1, r2 = columns_safe([1, 2]) # удалён vertical_alignment
290
- with r1:
291
- read_mode = st.radio("Источник текста:", ["Загрузить файл", "Вставить текст"], horizontal=True)
292
- with r2:
293
- clear_read = st.button("Очистить ввод (анализ)", use_container_width=True)
294
-
295
  text = ""
296
- if read_mode == "Загрузить файл":
297
- uploaded_file = st.file_uploader("Выберите файл (.txt, .docx, .pdf)", type=["txt", "docx", "pdf"], key="read_upl")
 
298
  if uploaded_file is not None:
299
  suffix = os.path.splitext(uploaded_file.name)[1]
300
- with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
301
- tmp.write(uploaded_file.getbuffer())
302
- temp_path = tmp.name
303
- text = read_file(temp_path)
304
- os.remove(temp_path)
305
-
306
- with st.expander("Предпросмотр (только чтение)", expanded=False):
307
- st.text_area("Содержимое", value=text, height=160, disabled=True, key="read_preview")
 
 
 
 
 
 
308
  else:
309
- default_val = "" if clear_read else st.session_state.get("last_read_text", "")
310
- text = st.text_area("Вставьте текст для анализа", height=220, value=default_val, key="read_text")
311
- st.session_state.last_read_text = text
312
 
313
  if text:
314
- horizontal_rule()
315
- cdet, csel, cstats = st.columns([1.2, 1, 1.2])
316
-
317
- with cdet:
318
- auto_detect_r = st.checkbox("Определить язык автоматически", value=True, key="read_auto")
319
- if auto_detect_r:
320
- detected_lang = detect_language(text)
321
- badge(f"Язык: {detected_lang}", "info")
322
- lang_code = detected_lang if detected_lang in ['ru', 'en', 'kk'] else 'en'
323
- if lang_code not in ['ru', 'en', 'kk']:
324
- badge("Неподдерживаемый язык выбран en по умолчанию", "warn")
325
- else:
326
- lang_code = st.selectbox("Язык текста", ["ru", "en", "kk"], key="lang_select")
327
 
328
- with csel:
329
- words = len(text.split())
330
- chars = len(text)
331
- st.metric("Символов", f"{chars:,}".replace(",", " "))
332
- st.metric("Слов", f"{words:,}".replace(",", " "))
333
 
334
- with cstats:
335
- analyze_btn = st.button("🔎 Анализировать", type="primary", use_container_width=True)
 
 
 
 
 
336
 
337
- if analyze_btn:
338
- if st.session_state.analysis_lock:
339
- st.warning("Анализ уже выполняется. Пожалуйста, дождитесь завершения.")
340
- else:
341
- st.session_state.analysis_lock = True
342
  try:
343
- acquired = model_semaphore.acquire(blocking=False)
344
- if not acquired:
345
- st.warning("Система загружена. Попробуйте позже.")
346
- else:
347
- with st.spinner("Выполняется анализ..."):
348
- fre = flesch_reading_ease(text, lang_code)
349
- fkgl = flesch_kincaid_grade_level(text, lang_code)
350
- fog = gunning_fog_index(text, lang_code)
351
- smg = smog_index(text, lang_code)
352
- highlighted_text, complex_words_list = highlight_complex_text(text, lang_code)
353
-
354
- horizontal_rule()
355
- section_title("Результаты")
356
- # Компактные карточки-метрики
357
- m1, m2, m3, m4 = st.columns(4)
358
- with m1:
359
- st.markdown(
360
- f"**Flesch Reading Ease**<br>{color_code_index('Flesch Reading Ease', fre)}",
361
- unsafe_allow_html=True
362
- )
363
- with m2:
364
- st.markdown(
365
- f"**Flesch-Kincaid Grade**<br>{color_code_index('Flesch-Kincaid Grade Level', fkgl)}",
366
- unsafe_allow_html=True
367
- )
368
- with m3:
369
- st.markdown(
370
- f"**Gunning Fog**<br>{color_code_index('Gunning Fog Index', fog)}",
371
- unsafe_allow_html=True
372
- )
373
- with m4:
374
- st.markdown(
375
- f"**SMOG**<br>{color_code_index('SMOG Index', smg)}",
376
- unsafe_allow_html=True
377
- )
378
-
379
- horizontal_rule()
380
- with st.expander(f"Сложные слова ({len(set(complex_words_list))})", expanded=False):
381
- if complex_words_list:
382
- st.write(", ".join(sorted(set(complex_words_list))))
383
- else:
384
- st.write("Не выявлены.")
385
-
386
- with st.expander("Подсветка сложных фрагментов (HTML)", expanded=False):
387
- st.markdown(highlighted_text, unsafe_allow_html=True)
388
  finally:
389
- if model_semaphore._value < MODEL_CONFIG["max_parallel_models"]:
390
- model_semaphore.release()
391
- st.session_state.analysis_lock = False
 
 
392
 
393
 
394
- # ====== ИНИЦ И ДИАГНОСТИКА NLTK/GPU (один раз на сессию) ======
395
- def _startup_once():
396
  setup_nltk()
 
 
397
  if 'model_config_logged' not in st.session_state:
398
  logging.info(f"Using model configuration: {MODEL_CONFIG}")
399
  st.session_state.model_config_logged = True
 
 
400
  try:
401
  import torch
402
  if torch.cuda.is_available():
@@ -407,13 +334,28 @@ def _startup_once():
407
  logging.info("Обнаружен Apple Silicon GPU (MPS)")
408
  else:
409
  logging.warning("GPU не обнаружен. Устанавливаем устройство на CPU")
410
- logging.warning("Диагностика CUDA:")
411
- logging.warning(f"torch.__version__: {getattr(__import__('torch'), '__version__', 'N/A')}")
412
- if hasattr(__import__('torch').version, "cuda"):
413
- logging.warning(f"torch.version.cuda: {__import__('torch').version.cuda}")
 
 
 
 
414
  except ImportError:
415
  logging.warning("PyTorch не установлен, будет использован CPU")
416
  except Exception as e:
417
  logging.warning(f"Ошибка при проверке GPU: {str(e)}")
418
 
419
- _startup_once()
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
 
 
 
 
 
 
 
 
2
  import streamlit as st
3
+ import logging
4
  from dotenv import load_dotenv
5
 
6
+ # Load environment variables first, before any other code
7
  load_dotenv()
 
8
 
9
+ # Import configuration defaults (after loading .env to prioritize environment variables)
10
+ from config import ENV_DEFAULTS, DEFAULT_CONFIG
11
+
12
+ # Configure logging based on configuration
13
  log_level = os.environ.get('LOGLEVEL', DEFAULT_CONFIG['LOGLEVEL']).upper()
14
  logging.basicConfig(
15
  level=getattr(logging, log_level),
16
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
17
+ handlers=[
18
+ # Only log to console if level is INFO or higher
19
+ logging.StreamHandler() if log_level != 'WARNING' else logging.NullHandler()
20
+ ]
21
  )
22
 
23
+ # Configure app
24
+ st.set_page_config(page_title="Translator & Readability", layout="wide")
25
 
26
+ # Check for missing environment variables and use defaults from config
27
  for var, default in ENV_DEFAULTS.items():
28
  if var not in os.environ:
29
  logging.debug(f"Environment variable {var} not found, using default: {default}")
30
  os.environ[var] = default
31
 
32
+ # Model configuration from default config
33
  MODEL_CONFIG = {
34
  "max_parallel_models": DEFAULT_CONFIG["MAX_PARALLEL_MODELS"],
35
  "session_timeout": DEFAULT_CONFIG["SESSION_TIMEOUT"],
36
  "allow_gpu": DEFAULT_CONFIG["ALLOW_GPU"]
37
  }
38
 
39
+ # Initialize model semaphore for limiting concurrent model usage
40
+ import threading
41
  model_semaphore = threading.Semaphore(MODEL_CONFIG["max_parallel_models"])
42
 
43
+ import tempfile
44
+ import io
45
+ from docx import Document
46
+ import uuid
47
+ import traceback
48
+
49
  from models.nltk_resources import setup_nltk
50
  from utils.file_readers import read_file
51
  from utils.text_processing import detect_language
 
57
  highlight_complex_text
58
  )
59
  from utils.formatting import color_code_index
60
+ from utils.tilmash_translation import tilmash_translate, display_tilmash_streaming_translation
61
 
62
+ # Initialize session state for user identification
 
63
  if 'session_id' not in st.session_state:
64
  st.session_state.session_id = str(uuid.uuid4())
65
+
66
  if 'translation_lock' not in st.session_state:
67
  st.session_state.translation_lock = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
 
 
69
 
70
+ def handle_translation():
71
+ st.header("Перевод (Kazakh, Russian, English)")
72
 
73
+ # Show session ID in sidebar for debugging
74
+ with st.sidebar.expander("Session Info", expanded=False):
75
+ st.write(f"Session ID: {st.session_state.session_id}")
76
 
77
+ # Add GPU usage option
78
+ if MODEL_CONFIG["allow_gpu"]:
79
+ st.session_state.use_gpu = st.checkbox("Использовать GPU (быстрее)", value=True)
80
+ if st.session_state.use_gpu:
81
+ try:
82
+ import torch
83
+ if torch.cuda.is_available():
84
+ gpu_info = f"CUDA: {torch.cuda.get_device_name(0)}"
85
+ st.success(f"Доступен GPU: {gpu_info}")
86
+ elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
87
+ st.success("Доступен Apple Silicon GPU (MPS)")
88
+ else:
89
+ st.warning("GPU не обнаружен, будет использован CPU")
90
+ st.session_state.use_gpu = False
91
+ except ImportError:
92
+ st.warning("PyTorch не установлен, будет использован CPU")
93
+ st.session_state.use_gpu = False
94
+ else:
95
+ st.session_state.use_gpu = False
96
+ st.write("GPU отключен в конфигурации")
97
 
98
+ translate_input_method = st.radio("Способ ввода текста:", ["Загрузить файл", "Вставить текст"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  input_text = ""
 
100
 
101
+ if translate_input_method == "Загрузить файл":
102
  uploaded_file = st.file_uploader("Выберите файл (.txt, .docx, .pdf)", type=["txt", "docx", "pdf"])
103
  if uploaded_file is not None:
104
+ suffix = os.path.splitext(uploaded_file.name)[1]
105
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_file:
106
+ tmp_file.write(uploaded_file.getbuffer())
107
+ temp_file_path = tmp_file.name
108
+ input_text = read_file(temp_file_path)
109
+ os.remove(temp_file_path)
110
+
111
+ # Скрытый предпросмотр: показываем только по клику
112
+ with st.expander("Показать предварительный просмотр файла", expanded=False):
113
+ st.text_area(
114
+ "Содержимое (��олько просмотр)",
115
+ value=input_text,
116
+ height=160,
117
+ disabled=True
118
+ )
119
  else:
120
+ input_text = st.text_area("Вставьте ваш текст здесь", height=200)
 
 
121
 
122
  if input_text:
123
+ auto_detect = st.checkbox("Автоматически определить язык", value=True)
124
+ src_lang = None
125
+ if auto_detect:
126
+ detected_lang = detect_language(input_text)
127
+ if detected_lang in ['ru', 'en', 'kk']:
128
+ st.info(f"Определён язык: {detected_lang}")
129
+ src_lang = detected_lang
 
 
 
 
 
 
130
  else:
131
+ st.warning("Не удалось определить язык. Выберите вручную.")
132
  src_lang = st.selectbox("Язык текста", ["ru", "en", "kk"])
133
+ else:
134
+ src_lang = st.selectbox("Язык текста", ["ru", "en", "kk"])
135
+
136
+ if src_lang == "ru":
137
+ tgt_options = ["en", "kk"]
138
+ elif src_lang == "en":
139
+ tgt_options = ["ru", "kk"]
140
+ else:
141
+ tgt_options = ["ru", "en"]
142
 
143
+ tgt_lang = st.selectbox("Перевод на:", tgt_options)
144
+
145
+ if st.button("Перевести"):
146
+ # Prevent multiple concurrent translations from same session
 
 
 
 
 
 
 
 
 
 
 
 
147
  if st.session_state.translation_lock:
148
  st.warning("Перевод уже выполняется. Пожалуйста, дождитесь завершения.")
149
+ return
150
+
151
+ # Set translation lock
152
+ st.session_state.translation_lock = True
153
+
154
+ try:
155
+ # Use the model semaphore to limit concurrent model access
156
+ acquired = model_semaphore.acquire(blocking=False)
157
+ if not acquired:
158
+ st.warning("Максимальное количество параллельных моделей достигнуто. Пожалуйста, попробуйте позже.")
159
+ st.session_state.translation_lock = False
160
+ return
161
+
162
  try:
163
+ st.subheader("Результат перевода:")
164
+ # Get the approximate size of the text to determine if chunking is needed
165
+ approx_text_size = len(input_text) / 4 # rough approximation (4 chars ≈ 1 token)
166
+ needs_chunking = approx_text_size > 500 # If text is likely over 500 tokens
167
+
168
+ # Display appropriate spinner message
169
+ spinner_message = "Processing text in chunks..." if needs_chunking else "Processing translation..."
 
170
 
171
+ # Create a dedicated translator instance for this session
172
+ from utils.tilmash_translation import TilmashTranslator
173
+ # Используем GPU если включено в настройках
174
+ use_gpu = getattr(st.session_state, 'use_gpu', False)
175
+ translator = TilmashTranslator(use_gpu=use_gpu)
176
+
177
+ with st.spinner(spinner_message):
178
  try:
179
+ # Use direct streaming approach with session-specific translator
180
+ result = ""
181
+ translation_placeholder = st.empty()
182
+
183
+ # Stream translation
184
+ for chunk in translator.translate_streaming(input_text, src_lang, tgt_lang):
185
+ result += chunk
186
+ translation_placeholder.markdown(result)
 
 
 
 
187
 
188
  except Exception as e:
189
+ st.error(f"Translation error: {str(e)}")
190
  logging.error(f"Tilmash translation error: {traceback.format_exc()}")
191
  result = None
192
 
193
+ if result:
194
+ # Prepare download capability
195
+ doc = Document()
196
+ doc.add_paragraph(result)
197
+ doc_io = io.BytesIO()
198
+ doc.save(doc_io)
199
+ doc_io.seek(0)
200
+
201
+ st.download_button(
202
+ label="Скачать переведённый текст (.docx)",
203
+ data=doc_io,
204
+ file_name="translated_text.docx",
205
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
206
+ )
207
+ else:
208
+ st.warning("Не удалось выполнить перевод.")
209
+
210
+ # Unload Tilmash model after use
211
+ try:
212
+ if translator.initialized:
213
+ translator.unload_model()
214
+ except Exception as unload_error:
215
+ logging.error(f"Error unloading Tilmash model: {str(unload_error)}")
216
+ except Exception as tilmash_error:
217
+ st.error(f"Tilmash model error: {str(tilmash_error)}")
218
+ logging.error(f"Tilmash model error: {traceback.format_exc()}")
 
 
 
 
 
 
219
  finally:
220
+ # Release the semaphore
221
+ model_semaphore.release()
222
+ except Exception as outer_error:
223
+ st.error(f"Unexpected error: {str(outer_error)}")
224
+ logging.error(f"Unexpected error: {traceback.format_exc()}")
225
+ finally:
226
+ # Release translation lock
227
+ st.session_state.translation_lock = False
228
+
229
+
230
+ def handle_readability_analysis():
231
+ st.header("Анализ удобочитаемости текста")
232
+ input_method = st.radio("Способ ввода текста:", ["Загрузить файл", "Вставить текст"])
 
 
 
233
  text = ""
234
+
235
+ if input_method == "Загрузить файл":
236
+ uploaded_file = st.file_uploader("Выберите файл (.txt, .docx, .pdf)", type=["txt", "docx", "pdf"])
237
  if uploaded_file is not None:
238
  suffix = os.path.splitext(uploaded_file.name)[1]
239
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_file:
240
+ tmp_file.write(uploaded_file.getbuffer())
241
+ temp_file_path = tmp_file.name
242
+ text = read_file(temp_file_path)
243
+ os.remove(temp_file_path)
244
+
245
+ # Скрытый предпросмотр: показываем только по клику
246
+ with st.expander("Показать предварительный просмотр файла", expanded=False):
247
+ st.text_area(
248
+ "Содержимое (только просмотр)",
249
+ value=text,
250
+ height=160,
251
+ disabled=True
252
+ )
253
  else:
254
+ text = st.text_area("Вставьте ваш текст здесь", height=200)
 
 
255
 
256
  if text:
257
+ auto_detect = st.checkbox("Определить язык автоматически", value=True)
258
+ if auto_detect:
259
+ detected_lang = detect_language(text)
260
+ st.info(f"Определён язык: {detected_lang}")
261
+ lang_code = detected_lang if detected_lang in ['ru', 'en', 'kk'] else 'en'
262
+ else:
263
+ lang_code = st.selectbox("Язык текста", ["ru", "en", "kk"])
264
+
265
+ if st.button("Анализировать"):
266
+ # Prevent multiple concurrent analyses
267
+ if 'analysis_lock' in st.session_state and st.session_state.analysis_lock:
268
+ st.warning("Анализ уже выполняется. Пожалуйста, дождитесь завершения.")
269
+ return
270
 
271
+ # Set analysis lock
272
+ st.session_state.analysis_lock = True
 
 
 
273
 
274
+ try:
275
+ # Use the model semaphore for consistency with translation
276
+ acquired = model_semaphore.acquire(blocking=False)
277
+ if not acquired:
278
+ st.warning("Система загружена. Пожалуйста, попробуйте позже.")
279
+ st.session_state.analysis_lock = False
280
+ return
281
 
 
 
 
 
 
282
  try:
283
+ with st.spinner("Выполняется анализ..."):
284
+ fre = flesch_reading_ease(text, lang_code)
285
+ fkgl = flesch_kincaid_grade_level(text, lang_code)
286
+ fog = gunning_fog_index(text, lang_code)
287
+ smog = smog_index(text, lang_code)
288
+ highlighted_text, complex_words_list = highlight_complex_text(text, lang_code)
289
+
290
+ st.subheader("Результаты удобочитаемости")
291
+ st.markdown(
292
+ f"**Индекс удобочитаемости Флеша:** {color_code_index('Flesch Reading Ease', fre)}",
293
+ unsafe_allow_html=True
294
+ )
295
+ st.markdown(
296
+ f"**Индекс Флеша-Кинкейда:** {color_code_index('Flesch-Kincaid Grade Level', fkgl)}",
297
+ unsafe_allow_html=True
298
+ )
299
+ st.markdown(
300
+ f"**Индекс тумана Ганнинга:** {color_code_index('Gunning Fog Index', fog)}",
301
+ unsafe_allow_html=True
302
+ )
303
+ st.markdown(
304
+ f"**Индекс SMOG:** {color_code_index('SMOG Index', smog)}",
305
+ unsafe_allow_html=True
306
+ )
307
+
308
+ st.subheader("Сложные слова")
309
+ st.write(", ".join(set(complex_words_list)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  finally:
311
+ # Release the semaphore
312
+ model_semaphore.release()
313
+ finally:
314
+ # Release analysis lock
315
+ st.session_state.analysis_lock = False
316
 
317
 
318
+ def main():
 
319
  setup_nltk()
320
+
321
+ # Log the model configuration only once per session
322
  if 'model_config_logged' not in st.session_state:
323
  logging.info(f"Using model configuration: {MODEL_CONFIG}")
324
  st.session_state.model_config_logged = True
325
+
326
+ # Проверка доступности GPU при запуске
327
  try:
328
  import torch
329
  if torch.cuda.is_available():
 
334
  logging.info("Обнаружен Apple Silicon GPU (MPS)")
335
  else:
336
  logging.warning("GPU не обнаружен. Устанавливаем устройство на CPU")
337
+ if not torch.cuda.is_available():
338
+ # Вывод диагностической информации
339
+ logging.warning("Диагностика CUDA:")
340
+ logging.warning(f"torch.__version__: {torch.__version__}")
341
+ if hasattr(torch.version, "cuda"):
342
+ logging.warning(f"torch.version.cuda: {torch.version.cuda}")
343
+ if hasattr(torch.cuda, "is_available"):
344
+ logging.warning(f"torch.cuda.is_available(): {torch.cuda.is_available()}")
345
  except ImportError:
346
  logging.warning("PyTorch не установлен, будет использован CPU")
347
  except Exception as e:
348
  logging.warning(f"Ошибка при проверке GPU: {str(e)}")
349
 
350
+ st.title("Translation & Readability Analysis")
351
+ st.sidebar.header("Функциональность")
352
+ functionality = st.sidebar.radio("Выберите режим:", ["Перевод", "Анализ удобочитаемости"])
353
+
354
+ if functionality == "Перевод":
355
+ handle_translation()
356
+ elif functionality == "Анализ удобочитаемости":
357
+ handle_readability_analysis()
358
+
359
+
360
+ if __name__ == "__main__":
361
+ main()