Spaces:
Running
Running
Commit
·
6fe0751
1
Parent(s):
186bbf3
4.9
Browse files
app.py
CHANGED
@@ -977,185 +977,110 @@ def process_file(uploaded_file, model_choice, translation_method=None):
|
|
977 |
'Заголовок': '',
|
978 |
'Выдержки из текста': '',
|
979 |
'Translated': '',
|
980 |
-
'Sentiment': 'Neutral',
|
981 |
-
'Impact': 'Неопределенный эффект',
|
982 |
-
'Reasoning': 'Не проанализировано',
|
983 |
-
'Event_Type': 'Нет',
|
984 |
-
'Event_Summary': ''
|
985 |
}
|
986 |
|
987 |
# Ensure all required columns exist in DataFrame
|
988 |
for col, default_value in required_columns.items():
|
989 |
if col not in df.columns:
|
990 |
df[col] = default_value
|
991 |
-
|
992 |
-
# Copy all columns to processed_rows_df
|
993 |
-
processed_rows_df = pd.DataFrame(columns=list(required_columns.keys()))
|
994 |
-
#processed_rows_df = pd.DataFrame(columns=df.columns)
|
995 |
|
996 |
-
#
|
997 |
-
|
998 |
-
|
999 |
-
lambda x: fuzzy_deduplicate(x, 'Выдержки из текста', 65)
|
1000 |
-
).reset_index(drop=True)
|
1001 |
-
st.write(f"Из {original_count} сообщений удалено {original_count - len(df)} дубликатов.")
|
1002 |
|
1003 |
# Process rows
|
1004 |
total_rows = len(df)
|
1005 |
processed_rows = 0
|
1006 |
|
1007 |
for idx, row in df.iterrows():
|
1008 |
-
# Check for stop/pause
|
1009 |
-
# In process_file function, replace the stop handling section:
|
1010 |
if st.session_state.control.is_stopped():
|
1011 |
st.warning("Обработку остановили")
|
1012 |
if not processed_rows_df.empty:
|
1013 |
try:
|
1014 |
-
#
|
1015 |
-
|
1016 |
-
|
1017 |
-
|
1018 |
-
|
1019 |
-
|
1020 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1021 |
|
1022 |
-
|
1023 |
-
original_df = pd.read_excel(uploaded_file, sheet_name='Публикации')
|
1024 |
-
for col in original_df.columns:
|
1025 |
-
if col not in processed_rows_df.columns:
|
1026 |
-
processed_rows_df[col] = ''
|
1027 |
-
|
1028 |
-
# Create output file
|
1029 |
-
output = create_output_file(processed_rows_df, uploaded_file, llm)
|
1030 |
if output is not None:
|
1031 |
st.download_button(
|
1032 |
-
label=f"📊 Скачать результат ({
|
1033 |
data=output,
|
1034 |
file_name="partial_analysis.xlsx",
|
1035 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
1036 |
key="partial_download"
|
1037 |
)
|
1038 |
-
else:
|
1039 |
-
st.error("��е удалось создать файл с частичными результатами")
|
1040 |
except Exception as e:
|
1041 |
-
st.error(f"Ошибка при создании
|
1042 |
-
st.error(f"Processed rows: {len(processed_rows_df)}")
|
1043 |
|
1044 |
return processed_rows_df
|
1045 |
-
|
1046 |
|
1047 |
st.session_state.control.wait_if_paused()
|
1048 |
if st.session_state.control.is_paused():
|
1049 |
-
st.info("Обработка на паузе. Можно возобновить.")
|
1050 |
-
if not processed_rows_df.empty: # Only offer download if we have processed rows
|
1051 |
-
output = create_output_file(processed_rows_df, uploaded_file, llm)
|
1052 |
-
if output is not None:
|
1053 |
-
st.download_button(
|
1054 |
-
label=f"📊 Скачать результат ({processed_rows} из {total_rows} строк)",
|
1055 |
-
data=output,
|
1056 |
-
file_name="partial_analysis.xlsx",
|
1057 |
-
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
1058 |
-
key="partial_download"
|
1059 |
-
)
|
1060 |
-
break
|
1061 |
continue
|
1062 |
|
1063 |
try:
|
|
|
|
|
|
|
1064 |
# Translation
|
1065 |
translated_text = translator.translate_text(row['Выдержки из текста'])
|
1066 |
-
|
1067 |
|
1068 |
# Sentiment analysis
|
1069 |
sentiment = analyze_sentiment(translated_text)
|
1070 |
-
|
1071 |
|
1072 |
-
# Event detection
|
1073 |
event_type, event_summary = event_detector.detect_event_type(
|
1074 |
row['Выдержки из текста'],
|
1075 |
row['Объект']
|
1076 |
)
|
1077 |
-
|
1078 |
-
|
1079 |
-
|
1080 |
-
|
1081 |
-
# Show events in real-time
|
1082 |
-
#if event_type != "Нет":
|
1083 |
-
# ui.show_event(
|
1084 |
-
# row['Объект'],
|
1085 |
-
# event_type,
|
1086 |
-
# row['Заголовок']
|
1087 |
-
# )
|
1088 |
-
|
1089 |
-
#Calculate processing speed (items per second)
|
1090 |
-
current_time = time.time()
|
1091 |
-
|
1092 |
-
time_delta = current_time - last_update_time
|
1093 |
-
if time_delta > 0:
|
1094 |
-
processing_speed = 1 / time_delta # items per second
|
1095 |
-
else:
|
1096 |
-
processing_speed = 0
|
1097 |
-
|
1098 |
-
# Update live statistics
|
1099 |
-
ui.update_stats(row, sentiment, event_type, processing_speed)
|
1100 |
-
|
1101 |
-
|
1102 |
-
# Handle negative sentiment
|
1103 |
|
1104 |
# Handle negative sentiment
|
1105 |
if sentiment == "Negative":
|
1106 |
try:
|
1107 |
-
# Validate translated text
|
1108 |
if translated_text and len(translated_text.strip()) > 0:
|
1109 |
-
# Initialize Groq LLM if not already done
|
1110 |
-
if 'groq_llm' not in locals():
|
1111 |
-
groq_llm = ensure_groq_llm()
|
1112 |
-
|
1113 |
impact, reasoning = estimate_impact(
|
1114 |
groq_llm if groq_llm is not None else llm,
|
1115 |
translated_text,
|
1116 |
row['Объект']
|
1117 |
)
|
1118 |
-
|
1119 |
-
|
1120 |
-
original_text = row['Выдержки из текста']
|
1121 |
-
if original_text and len(original_text.strip()) > 0:
|
1122 |
-
impact, reasoning = estimate_impact(
|
1123 |
-
groq_llm if groq_llm is not None else llm,
|
1124 |
-
original_text,
|
1125 |
-
row['Объект']
|
1126 |
-
)
|
1127 |
-
else:
|
1128 |
-
impact = "Неопределенный эффект"
|
1129 |
-
reasoning = "Текст новости отсутствует"
|
1130 |
-
st.warning(f"Empty news text for {row['Объект']}")
|
1131 |
-
|
1132 |
except Exception as e:
|
1133 |
-
|
1134 |
-
|
1135 |
-
|
1136 |
-
|
1137 |
-
|
1138 |
-
|
1139 |
-
df.at[idx, 'Reasoning'] = reasoning
|
1140 |
-
|
1141 |
-
|
1142 |
-
row_data = {col: row.get(col, default_val) for col, default_val in required_columns.items()}
|
1143 |
-
processed_rows_df = pd.concat([processed_rows_df, pd.DataFrame([row_data])], ignore_index=True)
|
1144 |
-
#processed_rows_df = pd.concat([processed_rows_df, df.iloc[[idx]]], ignore_index=True)
|
1145 |
-
|
1146 |
# Update progress
|
1147 |
processed_rows += 1
|
1148 |
ui.update_progress(processed_rows, total_rows)
|
1149 |
-
|
1150 |
-
|
1151 |
except Exception as e:
|
1152 |
st.warning(f"Ошибка в обработке ряда {idx + 1}: {str(e)}")
|
1153 |
continue
|
1154 |
-
|
1155 |
-
time.sleep(0.1)
|
1156 |
-
|
1157 |
|
1158 |
-
return processed_rows_df
|
1159 |
|
1160 |
except Exception as e:
|
1161 |
st.error(f"Ошибка в обработке файла: {str(e)}")
|
@@ -1481,129 +1406,34 @@ def translate_reasoning_to_russian(llm, text):
|
|
1481 |
else:
|
1482 |
return str(response).strip()
|
1483 |
|
|
|
1484 |
def create_output_file(df, uploaded_file, llm):
|
|
|
1485 |
try:
|
1486 |
wb = load_workbook("sample_file.xlsx")
|
1487 |
|
1488 |
-
#
|
1489 |
-
|
1490 |
-
|
1491 |
-
|
1492 |
-
|
1493 |
-
|
1494 |
-
|
1495 |
-
ws.cell(row=row_idx, column=7, value=row['Event_Type'])
|
1496 |
-
ws.cell(row=row_idx, column=8, value=row['Event_Summary'])
|
1497 |
-
ws.cell(row=row_idx, column=9, value=row['Выдержки из текста'])
|
1498 |
-
row_idx += 1
|
1499 |
-
|
1500 |
-
# Calculate statistics safely
|
1501 |
-
try:
|
1502 |
-
entity_stats = pd.DataFrame({
|
1503 |
-
'Объект': df['Объект'].unique(),
|
1504 |
-
'Всего': df.groupby('Объект').size(),
|
1505 |
-
'Негативные': df[df['Sentiment'] == 'Negative'].groupby('Объект').size().fillna(0).astype(int),
|
1506 |
-
'Позитивные': df[df['Sentiment'] == 'Positive'].groupby('Объект').size().fillna(0).astype(int)
|
1507 |
-
}).sort_values('Негативные', ascending=False)
|
1508 |
-
except Exception as e:
|
1509 |
-
st.warning(f"Error calculating entity stats: {str(e)}")
|
1510 |
-
entity_stats = pd.DataFrame(columns=['Объект', 'Всего', 'Негативные', 'Позитивные'])
|
1511 |
|
1512 |
-
# Calculate impacts safely
|
1513 |
-
entity_impacts = {}
|
1514 |
-
for entity in df['Объект'].unique():
|
1515 |
-
try:
|
1516 |
-
entity_df = df[df['Объект'] == entity]
|
1517 |
-
negative_df = entity_df[entity_df['Sentiment'] == 'Negative']
|
1518 |
-
if len(negative_df) > 0 and 'Impact' in negative_df.columns:
|
1519 |
-
impacts = negative_df['Impact'].dropna()
|
1520 |
-
entity_impacts[entity] = impacts.iloc[0] if len(impacts) > 0 else 'Неопределенный эффект'
|
1521 |
-
else:
|
1522 |
-
entity_impacts[entity] = 'Неопределенный эффект'
|
1523 |
-
except Exception as e:
|
1524 |
-
st.warning(f"Error calculating impact for {entity}: {str(e)}")
|
1525 |
-
entity_impacts[entity] = 'Неопределенный эффект'
|
1526 |
-
|
1527 |
-
# Update 'Сводка' sheet
|
1528 |
-
ws = wb['Сводка']
|
1529 |
-
for idx, (entity, row) in enumerate(entity_stats.iterrows(), start=4):
|
1530 |
-
ws.cell(row=idx, column=5, value=entity)
|
1531 |
-
ws.cell(row=idx, column=6, value=row['Всего'])
|
1532 |
-
ws.cell(row=idx, column=7, value=row['Негативные'])
|
1533 |
-
ws.cell(row=idx, column=8, value=row['Позитивные'])
|
1534 |
-
ws.cell(row=idx, column=9, value=entity_impacts.get(entity, 'Неопределенный эффект'))
|
1535 |
-
|
1536 |
-
# Update 'Значимые' sheet with both negative and positive
|
1537 |
-
ws = wb['Значимые']
|
1538 |
-
row_idx = 3
|
1539 |
-
sentiment_df = df[df['Sentiment'].isin(['Negative', 'Positive'])].copy()
|
1540 |
-
for _, row in sentiment_df.iterrows():
|
1541 |
-
cols = ['Объект', 'Заголовок', 'Sentiment', 'Impact', 'Выдержки из текста']
|
1542 |
-
for col in cols:
|
1543 |
-
if col not in row:
|
1544 |
-
row[col] = '' # Handle missing columns
|
1545 |
-
|
1546 |
-
ws.cell(row=row_idx, column=3, value=row['Объект'])
|
1547 |
-
ws.cell(row=row_idx, column=4, value='релевантно')
|
1548 |
-
ws.cell(row=row_idx, column=5, value=row['Sentiment'])
|
1549 |
-
ws.cell(row=row_idx, column=6, value=row.get('Impact', ''))
|
1550 |
-
ws.cell(row=row_idx, column=7, value=row['Заголовок'])
|
1551 |
-
ws.cell(row=row_idx, column=8, value=row['Выдержки из текста'])
|
1552 |
-
row_idx += 1
|
1553 |
-
|
1554 |
-
# Copy processed rows to 'Публикации' sheet
|
1555 |
-
ws = wb['Публикации']
|
1556 |
-
for r_idx, row in enumerate(dataframe_to_rows(df, index=False, header=True), start=1):
|
1557 |
-
for c_idx, value in enumerate(row, start=1):
|
1558 |
-
ws.cell(row=r_idx, column=c_idx, value=value)
|
1559 |
-
|
1560 |
-
# Update 'Анализ' sheet safely
|
1561 |
-
ws = wb['Анализ']
|
1562 |
-
row_idx = 4
|
1563 |
-
negative_df = df[df['Sentiment'] == 'Negative'].copy()
|
1564 |
-
for _, row in negative_df.iterrows():
|
1565 |
-
ws.cell(row=row_idx, column=5, value=row['Объект'])
|
1566 |
-
ws.cell(row=row_idx, column=6, value=row['Заголовок'])
|
1567 |
-
ws.cell(row=row_idx, column=7, value="Риск убытка")
|
1568 |
-
|
1569 |
-
reasoning = row.get('Reasoning', '')
|
1570 |
-
if reasoning and pd.notna(reasoning):
|
1571 |
-
try:
|
1572 |
-
grlm = init_langchain_llm("Groq (llama-3.1-70b)")
|
1573 |
-
translated_reasoning = translate_reasoning_to_russian(grlm, reasoning)
|
1574 |
-
ws.cell(row=row_idx, column=8, value=translated_reasoning)
|
1575 |
-
except Exception as e:
|
1576 |
-
ws.cell(row=row_idx, column=8, value=reasoning)
|
1577 |
-
|
1578 |
-
ws.cell(row=row_idx, column=9, value=row['Выдержки из текста'])
|
1579 |
-
row_idx += 1
|
1580 |
-
|
1581 |
-
# Update 'Тех.приложение' sheet
|
1582 |
-
tech_cols = ['Объект', 'Заголовок', 'Выдержки из текста', 'Translated', 'Sentiment', 'Impact', 'Reasoning']
|
1583 |
-
tech_df = df[[col for col in tech_cols if col in df.columns]].copy()
|
1584 |
-
|
1585 |
-
if 'Тех.приложение' not in wb.sheetnames:
|
1586 |
-
wb.create_sheet('Тех.приложение')
|
1587 |
-
ws = wb['Тех.приложение']
|
1588 |
-
|
1589 |
-
for r_idx, row in enumerate(dataframe_to_rows(tech_df, index=False, header=True), start=1):
|
1590 |
-
for c_idx, value in enumerate(row, start=1):
|
1591 |
-
ws.cell(row=r_idx, column=c_idx, value=value)
|
1592 |
-
|
1593 |
output = io.BytesIO()
|
1594 |
wb.save(output)
|
1595 |
output.seek(0)
|
1596 |
return output
|
1597 |
-
|
1598 |
except Exception as e:
|
1599 |
-
st.error(f"Error
|
1600 |
return None
|
1601 |
|
|
|
1602 |
def main():
|
1603 |
st.set_page_config(layout="wide")
|
1604 |
|
1605 |
with st.sidebar:
|
1606 |
-
st.title("::: AI-анализ мониторинга новостей (v.4.
|
1607 |
st.subheader("по материалам СКАН-ИНТЕРФАКС")
|
1608 |
|
1609 |
model_choice = st.radio(
|
@@ -1635,7 +1465,7 @@ def main():
|
|
1635 |
.signature {
|
1636 |
position: fixed;
|
1637 |
right: 12px;
|
1638 |
-
|
1639 |
font-size: 14px;
|
1640 |
color: #FF0000;
|
1641 |
opacity: 0.9;
|
|
|
977 |
'Заголовок': '',
|
978 |
'Выдержки из текста': '',
|
979 |
'Translated': '',
|
980 |
+
'Sentiment': 'Neutral',
|
981 |
+
'Impact': 'Неопределенный эффект',
|
982 |
+
'Reasoning': 'Не проанализировано',
|
983 |
+
'Event_Type': 'Нет',
|
984 |
+
'Event_Summary': ''
|
985 |
}
|
986 |
|
987 |
# Ensure all required columns exist in DataFrame
|
988 |
for col, default_value in required_columns.items():
|
989 |
if col not in df.columns:
|
990 |
df[col] = default_value
|
|
|
|
|
|
|
|
|
991 |
|
992 |
+
# Create processed_rows_df with all columns from original df and required columns
|
993 |
+
all_columns = list(set(list(df.columns) + list(required_columns.keys())))
|
994 |
+
processed_rows_df = pd.DataFrame(columns=all_columns)
|
|
|
|
|
|
|
995 |
|
996 |
# Process rows
|
997 |
total_rows = len(df)
|
998 |
processed_rows = 0
|
999 |
|
1000 |
for idx, row in df.iterrows():
|
|
|
|
|
1001 |
if st.session_state.control.is_stopped():
|
1002 |
st.warning("Обработку остановили")
|
1003 |
if not processed_rows_df.empty:
|
1004 |
try:
|
1005 |
+
# Create the output files for each sheet
|
1006 |
+
monitoring_df = processed_rows_df[processed_rows_df['Event_Type'] != 'Нет'].copy()
|
1007 |
+
svodka_df = processed_rows_df.groupby('Объект').agg({
|
1008 |
+
'Объект': 'first',
|
1009 |
+
'Sentiment': lambda x: sum(x == 'Negative'),
|
1010 |
+
'Event_Type': lambda x: sum(x != 'Нет')
|
1011 |
+
}).reset_index()
|
1012 |
+
|
1013 |
+
# Prepare final DataFrame for file creation
|
1014 |
+
result_df = pd.DataFrame()
|
1015 |
+
result_df['Мониторинг'] = monitoring_df.to_dict('records')
|
1016 |
+
result_df['Сводка'] = svodka_df.to_dict('records')
|
1017 |
+
result_df['Публикации'] = processed_rows_df.to_dict('records')
|
1018 |
|
1019 |
+
output = create_output_file(result_df, uploaded_file, llm)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1020 |
if output is not None:
|
1021 |
st.download_button(
|
1022 |
+
label=f"📊 Скачать результат ({processed_rows} из {total_rows} строк)",
|
1023 |
data=output,
|
1024 |
file_name="partial_analysis.xlsx",
|
1025 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
1026 |
key="partial_download"
|
1027 |
)
|
|
|
|
|
1028 |
except Exception as e:
|
1029 |
+
st.error(f"Ошибка при создании файла: {str(e)}")
|
|
|
1030 |
|
1031 |
return processed_rows_df
|
|
|
1032 |
|
1033 |
st.session_state.control.wait_if_paused()
|
1034 |
if st.session_state.control.is_paused():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1035 |
continue
|
1036 |
|
1037 |
try:
|
1038 |
+
# Copy original row data
|
1039 |
+
new_row = row.copy()
|
1040 |
+
|
1041 |
# Translation
|
1042 |
translated_text = translator.translate_text(row['Выдержки из текста'])
|
1043 |
+
new_row['Translated'] = translated_text
|
1044 |
|
1045 |
# Sentiment analysis
|
1046 |
sentiment = analyze_sentiment(translated_text)
|
1047 |
+
new_row['Sentiment'] = sentiment
|
1048 |
|
1049 |
+
# Event detection
|
1050 |
event_type, event_summary = event_detector.detect_event_type(
|
1051 |
row['Выдержки из текста'],
|
1052 |
row['Объект']
|
1053 |
)
|
1054 |
+
new_row['Event_Type'] = event_type
|
1055 |
+
new_row['Event_Summary'] = event_summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1056 |
|
1057 |
# Handle negative sentiment
|
1058 |
if sentiment == "Negative":
|
1059 |
try:
|
|
|
1060 |
if translated_text and len(translated_text.strip()) > 0:
|
|
|
|
|
|
|
|
|
1061 |
impact, reasoning = estimate_impact(
|
1062 |
groq_llm if groq_llm is not None else llm,
|
1063 |
translated_text,
|
1064 |
row['Объект']
|
1065 |
)
|
1066 |
+
new_row['Impact'] = impact
|
1067 |
+
new_row['Reasoning'] = reasoning
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1068 |
except Exception as e:
|
1069 |
+
new_row['Impact'] = "Неопределенный эффект"
|
1070 |
+
new_row['Reasoning'] = "Ошибка анализа"
|
1071 |
+
|
1072 |
+
# Add processed row to DataFrame
|
1073 |
+
processed_rows_df = pd.concat([processed_rows_df, pd.DataFrame([new_row])], ignore_index=True)
|
1074 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1075 |
# Update progress
|
1076 |
processed_rows += 1
|
1077 |
ui.update_progress(processed_rows, total_rows)
|
1078 |
+
|
|
|
1079 |
except Exception as e:
|
1080 |
st.warning(f"Ошибка в обработке ряда {idx + 1}: {str(e)}")
|
1081 |
continue
|
|
|
|
|
|
|
1082 |
|
1083 |
+
return processed_rows_df
|
1084 |
|
1085 |
except Exception as e:
|
1086 |
st.error(f"Ошибка в обработке файла: {str(e)}")
|
|
|
1406 |
else:
|
1407 |
return str(response).strip()
|
1408 |
|
1409 |
+
|
1410 |
def create_output_file(df, uploaded_file, llm):
|
1411 |
+
"""Simple function to write prepared DataFrame to Excel file"""
|
1412 |
try:
|
1413 |
wb = load_workbook("sample_file.xlsx")
|
1414 |
|
1415 |
+
# Copy all sheets from processed DataFrame
|
1416 |
+
for sheet_name in wb.sheetnames:
|
1417 |
+
ws = wb[sheet_name]
|
1418 |
+
if sheet_name == 'Публикации':
|
1419 |
+
for r_idx, row in enumerate(dataframe_to_rows(df, index=False, header=True), start=1):
|
1420 |
+
for c_idx, value in enumerate(row, start=1):
|
1421 |
+
ws.cell(row=r_idx, column=c_idx, value=value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1422 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1423 |
output = io.BytesIO()
|
1424 |
wb.save(output)
|
1425 |
output.seek(0)
|
1426 |
return output
|
|
|
1427 |
except Exception as e:
|
1428 |
+
st.error(f"Error saving file: {str(e)}")
|
1429 |
return None
|
1430 |
|
1431 |
+
|
1432 |
def main():
|
1433 |
st.set_page_config(layout="wide")
|
1434 |
|
1435 |
with st.sidebar:
|
1436 |
+
st.title("::: AI-анализ мониторинга новостей (v.4.9):::")
|
1437 |
st.subheader("по материалам СКАН-ИНТЕРФАКС")
|
1438 |
|
1439 |
model_choice = st.radio(
|
|
|
1465 |
.signature {
|
1466 |
position: fixed;
|
1467 |
right: 12px;
|
1468 |
+
down: 12px;
|
1469 |
font-size: 14px;
|
1470 |
color: #FF0000;
|
1471 |
opacity: 0.9;
|