Spaces:
Running
Running
pentarosarium
commited on
Commit
•
8a9c71f
1
Parent(s):
8f0749d
3.68
Browse files
app.py
CHANGED
@@ -32,6 +32,9 @@ from queue import Queue
|
|
32 |
|
33 |
from deep_translator import GoogleTranslator
|
34 |
from googletrans import Translator as LegacyTranslator
|
|
|
|
|
|
|
35 |
|
36 |
|
37 |
class ProcessControl:
|
@@ -431,54 +434,272 @@ class ProcessingUI:
|
|
431 |
def __init__(self):
|
432 |
if 'control' not in st.session_state:
|
433 |
st.session_state.control = ProcessControl()
|
434 |
-
if 'negative_container' not in st.session_state:
|
435 |
-
st.session_state.negative_container = st.empty()
|
436 |
-
if 'events_container' not in st.session_state:
|
437 |
-
st.session_state.events_container = st.empty()
|
438 |
|
439 |
-
#
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
452 |
self.progress_bar = st.progress(0)
|
453 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
|
455 |
def update_progress(self, current, total):
|
|
|
456 |
progress = current / total
|
457 |
self.progress_bar.progress(progress)
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
<div style='background-color: #ffebee; padding: 10px; border-radius: 5px; margin: 5px 0;'>
|
464 |
-
<strong style='color: #d32f2f;'>⚠️ Внимание: негатив!:</strong><br>
|
465 |
-
<strong>Entity:</strong> {entity}<br>
|
466 |
-
<strong>News:</strong> {headline}<br>
|
467 |
-
<strong>Analysis:</strong> {analysis}<br>
|
468 |
-
{f"<strong>Impact:</strong> {impact}<br>" if impact else ""}
|
469 |
-
</div>
|
470 |
-
""", unsafe_allow_html=True)
|
471 |
-
|
472 |
-
def show_event(self, entity, event_type, headline):
|
473 |
-
with st.session_state.events_container:
|
474 |
-
st.markdown(f"""
|
475 |
-
<div style='background-color: #e3f2fd; padding: 10px; border-radius: 5px; margin: 5px 0;'>
|
476 |
-
<strong style='color: #1976d2;'>🔔 Возможно, речь о важном факте:</strong><br>
|
477 |
-
<strong>Entity:</strong> {entity}<br>
|
478 |
-
<strong>Type:</strong> {event_type}<br>
|
479 |
-
<strong>News:</strong> {headline}
|
480 |
-
</div>
|
481 |
-
""", unsafe_allow_html=True)
|
482 |
|
483 |
class EventDetectionSystem:
|
484 |
def __init__(self):
|
@@ -656,6 +877,8 @@ class TranslationSystem:
|
|
656 |
|
657 |
def process_file(uploaded_file, model_choice, translation_method=None):
|
658 |
df = None
|
|
|
|
|
659 |
try:
|
660 |
# Initialize UI and control systems
|
661 |
ui = ProcessingUI()
|
@@ -699,11 +922,32 @@ def process_file(uploaded_file, model_choice, translation_method=None):
|
|
699 |
# Check for stop/pause
|
700 |
if st.session_state.control.is_stopped():
|
701 |
st.warning("Обработку остановили")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
break
|
703 |
|
704 |
st.session_state.control.wait_if_paused()
|
705 |
if st.session_state.control.is_paused():
|
706 |
st.info("Обработка на паузе. Можно возобновить.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
707 |
continue
|
708 |
|
709 |
try:
|
@@ -723,6 +967,9 @@ def process_file(uploaded_file, model_choice, translation_method=None):
|
|
723 |
df.at[idx, 'Event_Type'] = event_type
|
724 |
df.at[idx, 'Event_Summary'] = event_summary
|
725 |
|
|
|
|
|
|
|
726 |
# Show events in real-time
|
727 |
if event_type != "Нет":
|
728 |
ui.show_event(
|
@@ -756,6 +1003,8 @@ def process_file(uploaded_file, model_choice, translation_method=None):
|
|
756 |
impact
|
757 |
)
|
758 |
|
|
|
|
|
759 |
# Update progress
|
760 |
processed_rows += 1
|
761 |
ui.update_progress(processed_rows, total_rows)
|
@@ -766,20 +1015,8 @@ def process_file(uploaded_file, model_choice, translation_method=None):
|
|
766 |
|
767 |
time.sleep(0.1)
|
768 |
|
769 |
-
|
770 |
-
if st.session_state.control.is_stopped()
|
771 |
-
st.warning("Обработку остановили. Показываю частичные результаты.")
|
772 |
-
output = create_output_file(df, uploaded_file, llm)
|
773 |
-
if output is not None:
|
774 |
-
st.download_button(
|
775 |
-
label="📊 Скачать частичный результат",
|
776 |
-
data=output,
|
777 |
-
file_name="partial_analysis.xlsx",
|
778 |
-
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
779 |
-
key="partial_download"
|
780 |
-
)
|
781 |
-
|
782 |
-
return df
|
783 |
|
784 |
except Exception as e:
|
785 |
st.error(f"Ошибка в обработке файла: {str(e)}")
|
@@ -1165,7 +1402,7 @@ def main():
|
|
1165 |
st.set_page_config(layout="wide")
|
1166 |
|
1167 |
with st.sidebar:
|
1168 |
-
st.title("::: AI-анализ мониторинга новостей (v.3.
|
1169 |
st.subheader("по материалам СКАН-ИНТЕРФАКС")
|
1170 |
|
1171 |
model_choice = st.radio(
|
|
|
32 |
|
33 |
from deep_translator import GoogleTranslator
|
34 |
from googletrans import Translator as LegacyTranslator
|
35 |
+
import plotly.graph_objects as go
|
36 |
+
from datetime import datetime
|
37 |
+
import plotly.express as px
|
38 |
|
39 |
|
40 |
class ProcessControl:
|
|
|
434 |
def __init__(self):
|
435 |
if 'control' not in st.session_state:
|
436 |
st.session_state.control = ProcessControl()
|
|
|
|
|
|
|
|
|
437 |
|
438 |
+
# Initialize processing stats in session state if not exists
|
439 |
+
if 'processing_stats' not in st.session_state:
|
440 |
+
st.session_state.processing_stats = {
|
441 |
+
'start_time': time.time(),
|
442 |
+
'entities': {},
|
443 |
+
'events_timeline': [],
|
444 |
+
'negative_alerts': [],
|
445 |
+
'processing_speed': []
|
446 |
+
}
|
447 |
+
|
448 |
+
# Create main layout
|
449 |
+
self.setup_layout()
|
450 |
+
|
451 |
+
def setup_layout(self):
|
452 |
+
"""Setup the main UI layout with tabs and sections"""
|
453 |
+
# Control Panel
|
454 |
+
with st.container():
|
455 |
+
col1, col2, col3 = st.columns([2,2,1])
|
456 |
+
with col1:
|
457 |
+
if st.button(
|
458 |
+
"⏸️ Пауза" if not st.session_state.control.is_paused() else "▶️ Продолжить",
|
459 |
+
use_container_width=True
|
460 |
+
):
|
461 |
+
if st.session_state.control.is_paused():
|
462 |
+
st.session_state.control.resume()
|
463 |
+
else:
|
464 |
+
st.session_state.control.pause()
|
465 |
+
with col2:
|
466 |
+
if st.button("⏹️ Остановить", use_container_width=True):
|
467 |
+
st.session_state.control.stop()
|
468 |
+
with col3:
|
469 |
+
self.timer_display = st.empty()
|
470 |
+
|
471 |
+
# Progress Bar with custom styling
|
472 |
+
st.markdown("""
|
473 |
+
<style>
|
474 |
+
.stProgress > div > div > div > div {
|
475 |
+
background-image: linear-gradient(to right, #FF6B6B, #4ECDC4);
|
476 |
+
}
|
477 |
+
</style>""",
|
478 |
+
unsafe_allow_html=True
|
479 |
+
)
|
480 |
self.progress_bar = st.progress(0)
|
481 |
+
|
482 |
+
# Create tabs for different views
|
483 |
+
tab1, tab2, tab3, tab4 = st.tabs([
|
484 |
+
"📊 Основные метрики",
|
485 |
+
"🏢 По организациям",
|
486 |
+
"⚠️ Важные события",
|
487 |
+
"📈 Аналитика"
|
488 |
+
])
|
489 |
+
|
490 |
+
with tab1:
|
491 |
+
self.setup_main_metrics_tab()
|
492 |
+
|
493 |
+
with tab2:
|
494 |
+
self.setup_entity_tab()
|
495 |
+
|
496 |
+
with tab3:
|
497 |
+
self.setup_events_tab()
|
498 |
+
|
499 |
+
with tab4:
|
500 |
+
self.setup_analytics_tab()
|
501 |
+
|
502 |
+
def setup_main_metrics_tab(self):
|
503 |
+
"""Setup the main metrics display"""
|
504 |
+
# Create metrics containers
|
505 |
+
metrics_cols = st.columns(4)
|
506 |
+
self.total_processed = metrics_cols[0].empty()
|
507 |
+
self.negative_count = metrics_cols[1].empty()
|
508 |
+
self.events_count = metrics_cols[2].empty()
|
509 |
+
self.speed_metric = metrics_cols[3].empty()
|
510 |
+
|
511 |
+
# Recent items container with custom styling
|
512 |
+
st.markdown("""
|
513 |
+
<style>
|
514 |
+
.recent-item {
|
515 |
+
padding: 10px;
|
516 |
+
margin: 5px 0;
|
517 |
+
border-radius: 5px;
|
518 |
+
background-color: #f0f2f6;
|
519 |
+
}
|
520 |
+
.negative-item {
|
521 |
+
border-left: 4px solid #FF6B6B;
|
522 |
+
}
|
523 |
+
.positive-item {
|
524 |
+
border-left: 4px solid #4ECDC4;
|
525 |
+
}
|
526 |
+
.event-item {
|
527 |
+
border-left: 4px solid #FFE66D;
|
528 |
+
}
|
529 |
+
</style>
|
530 |
+
""", unsafe_allow_html=True)
|
531 |
+
|
532 |
+
self.recent_items_container = st.empty()
|
533 |
+
|
534 |
+
def setup_entity_tab(self):
|
535 |
+
"""Setup the entity-wise analysis display"""
|
536 |
+
# Entity filter
|
537 |
+
self.entity_filter = st.multiselect(
|
538 |
+
"Фильтр по организациям:",
|
539 |
+
options=[], # Will be populated as entities are processed
|
540 |
+
default=None
|
541 |
+
)
|
542 |
+
|
543 |
+
# Entity metrics
|
544 |
+
self.entity_cols = st.columns([2,1,1,1])
|
545 |
+
self.entity_chart = st.empty()
|
546 |
+
self.entity_table = st.empty()
|
547 |
+
|
548 |
+
def setup_events_tab(self):
|
549 |
+
"""Setup the events timeline display"""
|
550 |
+
# Event type filter
|
551 |
+
self.event_filter = st.multiselect(
|
552 |
+
"Тип события:",
|
553 |
+
options=["Отчетность", "РЦБ", "Суд"],
|
554 |
+
default=None
|
555 |
+
)
|
556 |
+
|
557 |
+
# Timeline container
|
558 |
+
self.timeline_container = st.container()
|
559 |
+
|
560 |
+
def setup_analytics_tab(self):
|
561 |
+
"""Setup the analytics display"""
|
562 |
+
# Processing speed chart
|
563 |
+
self.speed_chart = st.empty()
|
564 |
+
|
565 |
+
# Sentiment distribution pie chart
|
566 |
+
self.sentiment_chart = st.empty()
|
567 |
+
|
568 |
+
# Entity correlation matrix
|
569 |
+
self.correlation_chart = st.empty()
|
570 |
+
|
571 |
+
def update_stats(self, row, sentiment, event_type, processing_speed):
|
572 |
+
"""Update all statistics and displays"""
|
573 |
+
# Update session state stats
|
574 |
+
stats = st.session_state.processing_stats
|
575 |
+
entity = row['Объект']
|
576 |
+
|
577 |
+
# Update entity stats
|
578 |
+
if entity not in stats['entities']:
|
579 |
+
stats['entities'][entity] = {
|
580 |
+
'total': 0,
|
581 |
+
'negative': 0,
|
582 |
+
'events': 0,
|
583 |
+
'timeline': []
|
584 |
+
}
|
585 |
+
|
586 |
+
stats['entities'][entity]['total'] += 1
|
587 |
+
if sentiment == 'Negative':
|
588 |
+
stats['entities'][entity]['negative'] += 1
|
589 |
+
if event_type != 'Нет':
|
590 |
+
stats['entities'][entity]['events'] += 1
|
591 |
+
|
592 |
+
# Update processing speed
|
593 |
+
stats['processing_speed'].append(processing_speed)
|
594 |
+
|
595 |
+
# Update UI components
|
596 |
+
self._update_main_metrics(row, sentiment, event_type, processing_speed)
|
597 |
+
self._update_entity_view()
|
598 |
+
self._update_events_view(row, event_type)
|
599 |
+
self._update_analytics()
|
600 |
+
|
601 |
+
def _update_main_metrics(self, row, sentiment, event_type, speed):
|
602 |
+
"""Update main metrics tab"""
|
603 |
+
total = sum(e['total'] for e in st.session_state.processing_stats['entities'].values())
|
604 |
+
total_negative = sum(e['negative'] for e in st.session_state.processing_stats['entities'].values())
|
605 |
+
total_events = sum(e['events'] for e in st.session_state.processing_stats['entities'].values())
|
606 |
+
|
607 |
+
# Update metrics
|
608 |
+
self.total_processed.metric("Обработано", total)
|
609 |
+
self.negative_count.metric("Негативных", total_negative)
|
610 |
+
self.events_count.metric("Событий", total_events)
|
611 |
+
self.speed_metric.metric("Скорость", f"{speed:.1f} сообщ/сек")
|
612 |
+
|
613 |
+
# Update recent items
|
614 |
+
self._update_recent_items(row, sentiment, event_type)
|
615 |
+
|
616 |
+
def _update_recent_items(self, row, sentiment, event_type):
|
617 |
+
"""Update recent items display with custom styling"""
|
618 |
+
items_html = "<div style='max-height: 300px; overflow-y: auto;'>"
|
619 |
+
|
620 |
+
# Add new item to the beginning of the list
|
621 |
+
item_class = 'recent-item '
|
622 |
+
if sentiment == 'Negative':
|
623 |
+
item_class += 'negative-item'
|
624 |
+
elif sentiment == 'Positive':
|
625 |
+
item_class += 'positive-item'
|
626 |
+
if event_type != 'Нет':
|
627 |
+
item_class += ' event-item'
|
628 |
+
|
629 |
+
items_html += f"""
|
630 |
+
<div class='{item_class}'>
|
631 |
+
<strong>{row['Объект']}</strong><br>
|
632 |
+
{row['Заголовок']}<br>
|
633 |
+
<small>
|
634 |
+
Тональность: {sentiment}
|
635 |
+
{f" | Событие: {event_type}" if event_type != 'Нет' else ""}
|
636 |
+
</small>
|
637 |
+
</div>
|
638 |
+
"""
|
639 |
+
|
640 |
+
items_html += "</div>"
|
641 |
+
self.recent_items_container.markdown(items_html, unsafe_allow_html=True)
|
642 |
+
|
643 |
+
def _update_entity_view(self):
|
644 |
+
"""Update entity tab visualizations"""
|
645 |
+
stats = st.session_state.processing_stats['entities']
|
646 |
+
if not stats:
|
647 |
+
return
|
648 |
+
|
649 |
+
# Update entity filter options
|
650 |
+
current_options = set(self.entity_filter)
|
651 |
+
all_entities = set(stats.keys())
|
652 |
+
if current_options != all_entities:
|
653 |
+
self.entity_filter = list(all_entities)
|
654 |
+
|
655 |
+
# Create entity comparison chart using Plotly
|
656 |
+
df_entities = pd.DataFrame.from_dict(stats, orient='index')
|
657 |
+
fig = go.Figure(data=[
|
658 |
+
go.Bar(name='Total', x=df_entities.index, y=df_entities['total']),
|
659 |
+
go.Bar(name='Negative', x=df_entities.index, y=df_entities['negative']),
|
660 |
+
go.Bar(name='Events', x=df_entities.index, y=df_entities['events'])
|
661 |
+
])
|
662 |
+
fig.update_layout(barmode='group', title='Entity Statistics')
|
663 |
+
self.entity_chart.plotly_chart(fig, use_container_width=True)
|
664 |
+
|
665 |
+
def _update_events_view(self, row, event_type):
|
666 |
+
"""Update events timeline"""
|
667 |
+
if event_type != 'Нет':
|
668 |
+
event_html = f"""
|
669 |
+
<div class='timeline-item'>
|
670 |
+
<div class='timeline-marker'></div>
|
671 |
+
<div class='timeline-content'>
|
672 |
+
<h3>{event_type}</h3>
|
673 |
+
<p><strong>{row['Объект']}</strong></p>
|
674 |
+
<p>{row['Заголовок']}</p>
|
675 |
+
<small>{datetime.now().strftime('%H:%M:%S')}</small>
|
676 |
+
</div>
|
677 |
+
</div>
|
678 |
+
"""
|
679 |
+
with self.timeline_container:
|
680 |
+
st.markdown(event_html, unsafe_allow_html=True)
|
681 |
+
|
682 |
+
def _update_analytics(self):
|
683 |
+
"""Update analytics tab visualizations"""
|
684 |
+
stats = st.session_state.processing_stats
|
685 |
+
|
686 |
+
# Processing speed chart
|
687 |
+
speeds = stats['processing_speed'][-50:] # Last 50 measurements
|
688 |
+
fig_speed = go.Figure(data=go.Scatter(y=speeds, mode='lines'))
|
689 |
+
fig_speed.update_layout(title='Processing Speed Over Time')
|
690 |
+
self.speed_chart.plotly_chart(fig_speed, use_container_width=True)
|
691 |
+
|
692 |
+
# Add other analytics visualizations as needed
|
693 |
|
694 |
def update_progress(self, current, total):
|
695 |
+
"""Update progress bar and elapsed time"""
|
696 |
progress = current / total
|
697 |
self.progress_bar.progress(progress)
|
698 |
+
|
699 |
+
# Update elapsed time
|
700 |
+
elapsed = time.time() - st.session_state.processing_stats['start_time']
|
701 |
+
self.timer_display.markdown(f"⏱️ {format_elapsed_time(elapsed)}")
|
702 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
703 |
|
704 |
class EventDetectionSystem:
|
705 |
def __init__(self):
|
|
|
877 |
|
878 |
def process_file(uploaded_file, model_choice, translation_method=None):
|
879 |
df = None
|
880 |
+
processed_rows_df = pd.DataFrame()
|
881 |
+
|
882 |
try:
|
883 |
# Initialize UI and control systems
|
884 |
ui = ProcessingUI()
|
|
|
922 |
# Check for stop/pause
|
923 |
if st.session_state.control.is_stopped():
|
924 |
st.warning("Обработку остановили")
|
925 |
+
if not processed_rows_df.empty: # Only offer download if we have processed rows
|
926 |
+
output = create_output_file(processed_rows_df, uploaded_file, llm)
|
927 |
+
if output is not None:
|
928 |
+
st.download_button(
|
929 |
+
label=f"📊 Скачать результат ({processed_rows} из {total_rows} строк)",
|
930 |
+
data=output,
|
931 |
+
file_name="partial_analysis.xlsx",
|
932 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
933 |
+
key="partial_download"
|
934 |
+
)
|
935 |
break
|
936 |
|
937 |
st.session_state.control.wait_if_paused()
|
938 |
if st.session_state.control.is_paused():
|
939 |
st.info("Обработка на паузе. Можно возобновить.")
|
940 |
+
if not processed_rows_df.empty: # Only offer download if we have processed rows
|
941 |
+
output = create_output_file(processed_rows_df, uploaded_file, llm)
|
942 |
+
if output is not None:
|
943 |
+
st.download_button(
|
944 |
+
label=f"📊 Скачать результат ({processed_rows} из {total_rows} строк)",
|
945 |
+
data=output,
|
946 |
+
file_name="partial_analysis.xlsx",
|
947 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
948 |
+
key="partial_download"
|
949 |
+
)
|
950 |
+
break
|
951 |
continue
|
952 |
|
953 |
try:
|
|
|
967 |
df.at[idx, 'Event_Type'] = event_type
|
968 |
df.at[idx, 'Event_Summary'] = event_summary
|
969 |
|
970 |
+
# Update live statistics
|
971 |
+
ui.update_stats(row, sentiment, event_type)
|
972 |
+
|
973 |
# Show events in real-time
|
974 |
if event_type != "Нет":
|
975 |
ui.show_event(
|
|
|
1003 |
impact
|
1004 |
)
|
1005 |
|
1006 |
+
processed_rows_df = pd.concat([processed_rows_df, df.iloc[[idx]]], ignore_index=True)
|
1007 |
+
|
1008 |
# Update progress
|
1009 |
processed_rows += 1
|
1010 |
ui.update_progress(processed_rows, total_rows)
|
|
|
1015 |
|
1016 |
time.sleep(0.1)
|
1017 |
|
1018 |
+
|
1019 |
+
return processed_rows_df if st.session_state.control.is_stopped() else df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1020 |
|
1021 |
except Exception as e:
|
1022 |
st.error(f"Ошибка в обработке файла: {str(e)}")
|
|
|
1402 |
st.set_page_config(layout="wide")
|
1403 |
|
1404 |
with st.sidebar:
|
1405 |
+
st.title("::: AI-анализ мониторинга новостей (v.3.68!):::")
|
1406 |
st.subheader("по материалам СКАН-ИНТЕРФАКС")
|
1407 |
|
1408 |
model_choice = st.radio(
|