Aleksmorshen commited on
Commit
5758652
·
verified ·
1 Parent(s): 8472c70

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +126 -477
app.py CHANGED
@@ -11,17 +11,15 @@ import time
11
  from datetime import datetime
12
  import logging
13
  import threading
 
14
  from huggingface_hub import HfApi, hf_hub_download
15
  from huggingface_hub.utils import RepositoryNotFoundError
16
 
17
- from aiogram import Bot, Dispatcher, executor, types
18
- import asyncio
19
-
20
- import base64
21
  from google import genai
22
  from google.genai import types
23
 
24
- BOT_TOKEN = os.getenv("BOT_TOKEN", "7566834146:AAGiG4MaTZZvvbTVsqEJVG5SYK5hUlc_Ewo")
25
  HOST = '0.0.0.0'
26
  PORT = 7860
27
  DATA_FILE = 'data.json'
@@ -31,6 +29,7 @@ HF_DATA_FILE_PATH = "data.json"
31
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN_WRITE")
32
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
33
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
 
34
 
35
  app = Flask(__name__)
36
  logging.basicConfig(level=logging.INFO)
@@ -38,79 +37,9 @@ app.secret_key = os.urandom(24)
38
 
39
  _data_lock = threading.Lock()
40
  visitor_data_cache = {}
41
- chat_history_cache = []
42
-
43
- bot = Bot(token=BOT_TOKEN)
44
- dp = Dispatcher(bot)
45
-
46
- APP_CONTEXT_TEXT = """
47
- Morshen Group: Международный IT холдинг.
48
- Объединяем передовые технологические компании для создания инновационных
49
- решений мирового уровня. Мы строим будущее технологий сегодня.
50
- Лидер инноваций 2025.
51
- Контакт: Telegram @morshenkhan, Телефон +996 500 398 754.
52
-
53
- Экосистема инноваций: В состав холдинга входят компании, специализирующиеся на различных
54
- направлениях передовых технологий, создавая синергию для прорывных решений.
55
-
56
- Morshen Alpha: Флагманская компания холдинга.
57
- Специализация: Искусственный интеллект, Квантовые технологии, Бизнес-решения.
58
- Деятельность: Разрабатываем передовые бизнес-решения, проводим R&D в сфере AI
59
- и квантовых технологий. Наши инновации формируют будущее индустрии.
60
- Статистика: 3+ Страны присутствия, 3K+ Готовых клиентов, 5+ Лет на рынке.
61
-
62
- Holmgard Studio: Инновационная студия разработки.
63
- Специализация: Веб-разработка, Мобильные приложения, ПО на заказ.
64
- Деятельность: Создает высокотехнологичные веб-сайты,
65
- мобильные приложения и ПО для бизнеса любого масштаба.
66
- Использует передовые технологии и гибкие методологии.
67
- Статистика: 10+ Лет опыта, PRO Любая сложность, FAST Высокая скорость.
68
- Веб-сайт: https://holmgard.ru. Контакт через @morshenkhan.
69
-
70
- Глобальное присутствие: Наши инновационные решения и экспертиза доступны в странах Центральной Азии и за ее пределами:
71
- Узбекистан
72
- Казахстан
73
- Кыргызстан
74
- Расширяем горизонты...
75
-
76
- Сохранить визитку: Телефон +996 500 398 754, Morshen Group, Международный IT Холдинг. Сделайте скриншот экрана.
77
- """
78
-
79
- def generate_ai_response(prompt_text):
80
- if not GEMINI_API_KEY:
81
- logging.error("GEMINI_API_KEY not set. Cannot generate AI response.")
82
- return "Ошибка: Сервер не настроен для ответа на вопросы (отсутствует API ключ)."
83
- try:
84
- client = genai.Client(api_key=GEMINI_API_KEY)
85
- model = "learnlm-2.0-flash-experimental"
86
- contextualized_prompt = f"""
87
- Основываясь на следующей информации о Morshen Group и ее дочерних компаниях, ответь на вопрос пользователя.
88
- Если информация отсутствует, так и скажи. Не придумывай информацию, которой нет в тексте.
89
- Отвечай на русском языке.
90
-
91
- Информация о Morshen Group:
92
- {APP_CONTEXT_TEXT}
93
-
94
- Вопрос пользователя: {prompt_text}
95
-
96
- Ответ:
97
- """
98
- contents = [types.Content(role="user", parts=[types.Part.from_text(text=contextualized_prompt)])]
99
- generate_content_config = types.GenerateContentConfig(response_mime_type="text/plain")
100
-
101
- response = client.models.generate_content(
102
- model=model,
103
- contents=contents,
104
- config=generate_content_config,
105
- stream=False # Use non-streaming for simplicity in this context
106
- )
107
- return response.text.strip()
108
- except Exception as e:
109
- logging.error(f"Error generating AI response: {e}")
110
- return f"Произошла ошибка при генерации ответа: {e}"
111
 
112
  def download_data_from_hf():
113
- global visitor_data_cache, chat_history_cache
114
  if not HF_TOKEN_READ:
115
  logging.warning("HF_TOKEN_READ not set. Skipping Hugging Face download.")
116
  return False
@@ -130,14 +59,11 @@ def download_data_from_hf():
130
  with _data_lock:
131
  try:
132
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
133
- full_data = json.load(f)
134
- visitor_data_cache = full_data.get("visitors", {})
135
- chat_history_cache = full_data.get("chats", [])
136
  logging.info("Successfully loaded downloaded data into cache.")
137
  except (FileNotFoundError, json.JSONDecodeError) as e:
138
  logging.error(f"Error reading downloaded data file: {e}. Starting with empty cache.")
139
  visitor_data_cache = {}
140
- chat_history_cache = []
141
  return True
142
  except RepositoryNotFoundError:
143
  logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.")
@@ -146,53 +72,34 @@ def download_data_from_hf():
146
  return False
147
 
148
  def load_visitor_data():
149
- global visitor_data_cache, chat_history_cache
150
  with _data_lock:
151
- if not visitor_data_cache or not chat_history_cache:
152
  try:
153
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
154
- full_data = json.load(f)
155
- visitor_data_cache = full_data.get("visitors", {})
156
- chat_history_cache = full_data.get("chats", [])
157
- logging.info("Data loaded from local JSON.")
158
  except FileNotFoundError:
159
  logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.")
160
  visitor_data_cache = {}
161
- chat_history_cache = []
162
  except json.JSONDecodeError:
163
  logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.")
164
  visitor_data_cache = {}
165
- chat_history_cache = []
166
  except Exception as e:
167
- logging.error(f"Unexpected error loading data: {e}")
168
  visitor_data_cache = {}
169
- chat_history_cache = []
170
- return {"visitors": visitor_data_cache, "chats": chat_history_cache}
171
-
172
 
173
- def save_visitor_data(data_to_update):
174
  with _data_lock:
175
  try:
176
- visitor_data_cache.update(data_to_update)
177
- full_data = {"visitors": visitor_data_cache, "chats": chat_history_cache}
178
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
179
- json.dump(full_data, f, ensure_ascii=False, indent=4)
180
- logging.info(f"Data successfully saved to {DATA_FILE}.")
181
  upload_data_to_hf_async()
182
  except Exception as e:
183
- logging.error(f"Error saving data: {e}")
184
-
185
- def save_chat_message(chat_entry):
186
- with _data_lock:
187
- try:
188
- chat_history_cache.append(chat_entry)
189
- full_data = {"visitors": visitor_data_cache, "chats": chat_history_cache}
190
- with open(DATA_FILE, 'w', encoding='utf-8') as f:
191
- json.dump(full_data, f, ensure_ascii=False, indent=4)
192
- logging.info(f"Chat message successfully saved to {DATA_FILE}.")
193
- upload_data_to_hf_async()
194
- except Exception as e:
195
- logging.error(f"Error saving chat message: {e}")
196
 
197
  def upload_data_to_hf():
198
  if not HF_TOKEN_WRITE:
@@ -217,9 +124,9 @@ def upload_data_to_hf():
217
  repo_id=REPO_ID,
218
  repo_type="dataset",
219
  token=HF_TOKEN_WRITE,
220
- commit_message=f"Update data {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
221
  )
222
- logging.info("Data successfully uploaded to Hugging Face.")
223
  except Exception as e:
224
  logging.error(f"Error uploading data to Hugging Face: {e}")
225
 
@@ -256,7 +163,7 @@ def verify_telegram_data(init_data_str):
256
  auth_date = int(parsed_data.get('auth_date', [0])[0])
257
  current_time = int(time.time())
258
  if current_time - auth_date > 86400:
259
- logging.warning(f"Telegram InitData is older than 24 hours (Auth Date: {auth_date}, Current: {current_time}).")
260
  return parsed_data, True
261
  else:
262
  logging.warning(f"Data verification failed. Calculated: {calculated_hash}, Received: {received_hash}")
@@ -286,7 +193,7 @@ TEMPLATE = """
286
  --tg-theme-button-text-color: {{ theme.button_text_color | default('#ffffff') }};
287
  --tg-theme-secondary-bg-color: {{ theme.secondary_bg_color | default('#1e1e1e') }};
288
 
289
- --bg-gradient: linear-gradient(160deg, #1a232f 0%, var(--tg-theme-bg-color, #121212) 100%);
290
  --card-bg: rgba(44, 44, 46, 0.8);
291
  --card-bg-solid: #2c2c2e;
292
  --text-color: var(--tg-theme-text-color);
@@ -465,7 +372,6 @@ TEMPLATE = """
465
  }
466
  .save-card-button i { font-size: 1.2em; }
467
 
468
- /* Modal Styles */
469
  .modal {
470
  display: none; position: fixed; z-index: 1001;
471
  left: 0; top: 0; width: 100%; height: 100%;
@@ -497,7 +403,6 @@ TEMPLATE = """
497
  .modal-text b { color: var(--tg-theme-link-color); font-weight: 600; }
498
  .modal-instruction { font-size: 1em; color: var(--text-secondary-color); margin-top: var(--padding-m); }
499
 
500
- /* Icons */
501
  .icon { display: inline-block; width: 1.2em; text-align: center; margin-right: 8px; opacity: 0.9; }
502
  .icon-save::before { content: '💾'; }
503
  .icon-web::before { content: '🌐'; }
@@ -519,7 +424,6 @@ TEMPLATE = """
519
  .icon-leader::before { content: '🏆'; }
520
  .icon-company::before { content: '🏢'; }
521
 
522
- /* Responsive adjustments */
523
  @media (max-width: 480px) {
524
  .section-title { font-size: 1.8em; }
525
  .logo span { font-size: 1.4em; }
@@ -649,7 +553,6 @@ TEMPLATE = """
649
  <i class="icon icon-save"></i>Сохранить визитку
650
  </button>
651
 
652
- <!-- The Modal -->
653
  <div id="saveModal" class="modal">
654
  <div class="modal-content">
655
  <span class="modal-close" id="modal-close-btn">×</span>
@@ -660,7 +563,6 @@ TEMPLATE = """
660
  </div>
661
  </div>
662
 
663
-
664
  <script>
665
  const tg = window.Telegram.WebApp;
666
 
@@ -796,7 +698,7 @@ ADMIN_TEMPLATE = """
796
  <head>
797
  <meta charset="UTF-8">
798
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
799
- <title>Admin - Посетители и Чаты</title>
800
  <link rel="preconnect" href="https://fonts.googleapis.com">
801
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
802
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
@@ -812,7 +714,6 @@ ADMIN_TEMPLATE = """
812
  --admin-success: #198754;
813
  --admin-danger: #dc3545;
814
  --admin-warning: #ffc107;
815
- --admin-info: #0dcaf0;
816
  --border-radius: 12px;
817
  --padding: 1.5rem;
818
  --font-family: 'Inter', sans-serif;
@@ -826,28 +727,25 @@ ADMIN_TEMPLATE = """
826
  line-height: 1.6;
827
  }
828
  .container { max-width: 1140px; margin: 0 auto; }
829
- h1, h2 { text-align: center; color: var(--admin-secondary); margin-bottom: var(--padding); font-weight: 600; }
830
- h2 { margin-top: var(--padding); border-top: 1px solid var(--admin-border); padding-top: var(--padding); }
831
  .user-grid {
832
  display: grid;
833
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
834
  gap: var(--padding);
835
  margin-top: var(--padding);
836
  }
837
- .user-card, .chat-card {
838
  background-color: var(--admin-card-bg);
839
  border-radius: var(--border-radius);
840
  padding: var(--padding);
841
  box-shadow: 0 4px 15px var(--admin-shadow);
842
  border: 1px solid var(--admin-border);
 
 
 
 
843
  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
844
  }
845
- .user-card {
846
- display: flex;
847
- flex-direction: column;
848
- align-items: center;
849
- text-align: center;
850
- }
851
  .user-card:hover {
852
  transform: translateY(-5px);
853
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
@@ -864,7 +762,7 @@ ADMIN_TEMPLATE = """
864
  .user-card .detail-item { margin-bottom: 0.3rem; }
865
  .user-card .detail-item strong { color: var(--admin-text); }
866
  .user-card .timestamp { font-size: 0.8em; color: var(--admin-secondary); margin-top: 1rem; }
867
- .no-users, .no-chats { text-align: center; color: var(--admin-secondary); margin-top: 2rem; font-size: 1.1em; }
868
  .alert {
869
  background-color: #fff3cd; border-left: 6px solid var(--admin-warning);
870
  margin-bottom: var(--padding); padding: 1rem 1.5rem;
@@ -885,7 +783,6 @@ ADMIN_TEMPLATE = """
885
  }
886
  .refresh-btn:hover { background-color: #0b5ed7; }
887
 
888
- /* Admin Controls */
889
  .admin-controls {
890
  background: var(--admin-card-bg);
891
  padding: var(--padding);
@@ -918,102 +815,11 @@ ADMIN_TEMPLATE = """
918
  display: none;
919
  }
920
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
921
-
922
- /* Chat History Styles */
923
- .chat-history { margin-top: var(--padding); }
924
- .chat-message {
925
- margin-bottom: var(--padding-s);
926
- padding: var(--padding-s) var(--padding-m);
927
- border-radius: var(--border-radius-s);
928
- max-width: 85%;
929
- word-wrap: break-word;
930
- }
931
- .chat-message.user {
932
- background-color: var(--admin-primary);
933
- color: white;
934
- align-self: flex-start;
935
- margin-right: auto;
936
- }
937
- .chat-message.bot {
938
- background-color: #e9ecef;
939
- color: var(--admin-text);
940
- align-self: flex-end;
941
- margin-left: auto;
942
- }
943
- .chat-message .sender { font-weight: 600; margin-bottom: 4px; font-size: 0.9em;}
944
- .chat-message .text { margin-bottom: 4px; font-size: 1em;}
945
- .chat-message .time { font-size: 0.75em; color: rgba(255, 255, 255, 0.7); text-align: right; display: block;}
946
- .chat-message.bot .time { color: #495057; }
947
-
948
- .chat-thread {
949
- background-color: var(--admin-card-bg);
950
- border-radius: var(--border-radius);
951
- padding: var(--padding);
952
- box-shadow: 0 4px 15px var(--admin-shadow);
953
- border: 1px solid var(--admin-border);
954
- margin-bottom: var(--padding);
955
- }
956
- .chat-thread-header {
957
- font-weight: 600;
958
- font-size: 1.1em;
959
- margin-bottom: var(--padding-m);
960
- padding-bottom: var(--padding-s);
961
- border-bottom: 1px solid var(--admin-border);
962
- display: flex;
963
- justify-content: space-between;
964
- align-items: center;
965
- }
966
- .chat-thread-header span { color: var(--admin-primary); }
967
- .chat-thread-messages {
968
- display: flex;
969
- flex-direction: column;
970
- gap: var(--padding-s);
971
- }
972
-
973
- /* Send Message Form */
974
- .send-message-form {
975
- margin-top: var(--padding);
976
- padding: var(--padding);
977
- background: var(--admin-card-bg);
978
- border-radius: var(--border-radius);
979
- box-shadow: 0 4px 15px var(--admin-shadow);
980
- border: 1px solid var(--admin-border);
981
- }
982
- .send-message-form h3 { margin-top: 0; margin-bottom: 1rem; color: var(--admin-secondary); font-weight: 600;}
983
- .send-message-form label { display: block; margin-bottom: 0.5rem; font-weight: 500;}
984
- .send-message-form select, .send-message-form textarea {
985
- width: 100%;
986
- padding: 0.75rem;
987
- margin-bottom: 1rem;
988
- border: 1px solid var(--admin-border);
989
- border-radius: 8px;
990
- font-family: var(--font-family);
991
- font-size: 1em;
992
- box-sizing: border-box;
993
- }
994
- .send-message-form textarea { min-height: 100px; resize: vertical; }
995
- .send-message-form button {
996
- display: inline-block;
997
- padding: 10px 20px;
998
- font-size: 1em;
999
- font-weight: 500;
1000
- color: #fff;
1001
- background-color: var(--admin-success);
1002
- border: none;
1003
- border-radius: 8px;
1004
- cursor: pointer;
1005
- transition: background-color 0.2s ease;
1006
- }
1007
- .send-message-form button:hover { background-color: #157347; }
1008
- .send-message-status { margin-top: 1rem; text-align: center; font-weight: 500;}
1009
- .status-success { color: var(--admin-success); }
1010
- .status-error { color: var(--admin-danger); }
1011
- .status-info { color: var(--admin-info); }
1012
  </style>
1013
  </head>
1014
  <body>
1015
  <div class="container">
1016
- <h1>Admin Panel</h1>
1017
  <div class="alert">ВНИМАНИЕ: Этот раздел не защищен! Добавьте аутентификацию для реального использования.</div>
1018
 
1019
  <div class="admin-controls">
@@ -1024,9 +830,8 @@ ADMIN_TEMPLATE = """
1024
  <div class="status" id="status-message"></div>
1025
  </div>
1026
 
1027
- <button class="refresh-btn" onclick="location.reload()">Обновить страницу</button>
1028
 
1029
- <h2>Посетители Mini App</h2>
1030
  {% if users %}
1031
  <div class="user-grid">
1032
  {% for user in users|sort(attribute='visited_at', reverse=true) %}
@@ -1051,75 +856,11 @@ ADMIN_TEMPLATE = """
1051
  {% else %}
1052
  <p class="no-users">Данных о посетителях пока нет.</p>
1053
  {% endif %}
1054
-
1055
- <h2>История чатов с ботом</h2>
1056
- {% if chats %}
1057
- <div class="chat-history">
1058
- {% for chat_id, messages in chats|dictsort %}
1059
- <div class="chat-thread">
1060
- <div class="chat-thread-header">
1061
- <span>Чат ID: {{ chat_id }}</span>
1062
- {% set chat_user = users_by_id.get(chat_id|int) %}
1063
- {% if chat_user %}
1064
- <span>Пользователь:
1065
- {% if chat_user.username %}
1066
- <a href="https://t.me/{{ chat_user.username }}" target="_blank" style="color: var(--admin-secondary); text-decoration: none;">@{{ chat_user.username }}</a>
1067
- {% else %}
1068
- {{ chat_user.first_name or 'Неизвестный' }}
1069
- {% endif %}
1070
- ({{ chat_user.id }})
1071
- </span>
1072
- {% else %}
1073
- <span>Пользователь: Неизвестен ({{ chat_id }})</span>
1074
- {% endif %}
1075
- </div>
1076
- <div class="chat-thread-messages">
1077
- {% for message in messages %}
1078
- <div class="chat-message {{ 'user' if message.role == 'user' else 'bot' }}">
1079
- <div class="sender">{{ message.sender_name }}</div>
1080
- <div class="text">{{ message.text }}</div>
1081
- <div class="time">{{ message.timestamp_str }}</div>
1082
- </div>
1083
- {% endfor %}
1084
- </div>
1085
- </div>
1086
- {% endfor %}
1087
- </div>
1088
- {% else %}
1089
- <p class="no-chats">История чатов пуста.</p>
1090
- {% endif %}
1091
-
1092
- <div class="send-message-form">
1093
- <h3>Отправить сообщение пользователю</h3>
1094
- <form id="sendMessageForm">
1095
- <label for="chat_id">Выберите пользователя (Chat ID):</label>
1096
- <select id="chat_id" name="chat_id" required>
1097
- <option value="">-- Выберите --</option>
1098
- {% for user in users|sort(attribute='first_name') %}
1099
- <option value="{{ user.id }}">
1100
- {{ user.first_name or '' }} {{ user.last_name or '' }}
1101
- {% if user.username %} (@{{ user.username }}) {% endif %}
1102
- (ID: {{ user.id }})
1103
- </option>
1104
- {% endfor %}
1105
- </select>
1106
-
1107
- <label for="message_text">Текст сообщения:</label>
1108
- <textarea id="message_text" name="message_text" required></textarea>
1109
-
1110
- <button type="submit">Отправить</button>
1111
- </form>
1112
- <div class="send-message-status" id="sendMessageStatus"></div>
1113
- </div>
1114
-
1115
-
1116
  </div>
1117
 
1118
  <script>
1119
  const loader = document.getElementById('loader');
1120
  const statusMessage = document.getElementById('status-message');
1121
- const sendMessageForm = document.getElementById('sendMessageForm');
1122
- const sendMessageStatus = document.getElementById('sendMessageStatus');
1123
 
1124
  async function handleFetch(url, action) {
1125
  loader.style.display = 'inline-block';
@@ -1133,8 +874,6 @@ ADMIN_TEMPLATE = """
1133
  statusMessage.style.color = 'var(--admin-success)';
1134
  if (action === 'скачивание') {
1135
  setTimeout(() => location.reload(), 1500);
1136
- } else {
1137
- setTimeout(() => statusMessage.textContent = '', 3000);
1138
  }
1139
  } else {
1140
  throw new Error(data.message || 'Произошла ошибка');
@@ -1143,7 +882,6 @@ ADMIN_TEMPLATE = """
1143
  statusMessage.textContent = `Ошибка ${action}: ${error.message}`;
1144
  statusMessage.style.color = 'var(--admin-danger)';
1145
  console.error(`Error during ${action}:`, error);
1146
- setTimeout(() => statusMessage.textContent = '', 5000);
1147
  } finally {
1148
  loader.style.display = 'none';
1149
  }
@@ -1156,111 +894,77 @@ ADMIN_TEMPLATE = """
1156
  function triggerUpload() {
1157
  handleFetch('/admin/upload_data', 'загрузка');
1158
  }
1159
-
1160
- sendMessageForm.addEventListener('submit', async function(event) {
1161
- event.preventDefault();
1162
- const chatId = document.getElementById('chat_id').value;
1163
- const messageText = document.getElementById('message_text').value;
1164
-
1165
- if (!chatId || !messageText.trim()) {
1166
- sendMessageStatus.textContent = 'Выберите пользователя и введите текст сообщения.';
1167
- sendMessageStatus.className = 'send-message-status status-warning';
1168
- return;
1169
- }
1170
-
1171
- sendMessageStatus.textContent = 'Отправка сообщения...';
1172
- sendMessageStatus.className = 'send-message-status status-info';
1173
-
1174
- try {
1175
- const response = await fetch('/admin/send_message', {
1176
- method: 'POST',
1177
- headers: { 'Content-Type': 'application/json' },
1178
- body: JSON.stringify({ chat_id: chatId, message: messageText })
1179
- });
1180
- const data = await response.json();
1181
-
1182
- if (response.ok && data.status === 'ok') {
1183
- sendMessageStatus.textContent = 'Сообщение успешно отправлено!';
1184
- sendMessageStatus.className = 'send-message-status status-success';
1185
- document.getElementById('message_text').value = '';
1186
- setTimeout(() => location.reload(), 1500); // Reload to show sent message
1187
- } else {
1188
- throw new Error(data.message || 'Неизвестная ошибка');
1189
- }
1190
- } catch (error) {
1191
- sendMessageStatus.textContent = `Ошибка отправки: ${error.message}`;
1192
- sendMessageStatus.className = 'send-message-status status-error';
1193
- console.error('Error sending message:', error);
1194
- }
1195
- });
1196
-
1197
  </script>
1198
  </body>
1199
  </html>
1200
  """
1201
 
1202
- @dp.message_handler()
1203
- async def handle_messages(message: types.Message):
1204
- user_id = message.from_user.id
1205
- user_full_name = message.from_user.full_name or f"User {user_id}"
1206
- chat_id = message.chat.id
1207
- message_text = message.text
1208
-
1209
- now = time.time()
1210
- timestamp_str = datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
1211
 
1212
- save_chat_message({
1213
- 'chat_id': chat_id,
1214
- 'role': 'user',
1215
- 'sender_id': user_id,
1216
- 'sender_name': user_full_name,
1217
- 'text': message_text,
1218
- 'timestamp': now,
1219
- 'timestamp_str': timestamp_str
1220
- })
1221
 
1222
- logging.info(f"Received message from user {user_id} ({user_full_name}): {message_text}")
 
 
 
1223
 
1224
  try:
1225
- ai_response_text = generate_ai_response(message_text)
1226
- await message.answer(ai_response_text)
1227
-
1228
- now_bot = time.time()
1229
- timestamp_str_bot = datetime.fromtimestamp(now_bot).strftime('%Y-%m-%d %H:%M:%S')
1230
- save_chat_message({
1231
- 'chat_id': chat_id,
1232
- 'role': 'bot',
1233
- 'sender_id': bot.id,
1234
- 'sender_name': "Bot",
1235
- 'text': ai_response_text,
1236
- 'timestamp': now_bot,
1237
- 'timestamp_str': timestamp_str_bot
1238
- })
1239
- logging.info(f"Sent AI response to user {user_id}: {ai_response_text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1240
 
1241
  except Exception as e:
1242
- logging.error(f"Error handling message for user {user_id}: {e}")
1243
- await message.answer("Произошла ошибка при обработке вашего запроса.")
1244
- now_bot = time.time()
1245
- timestamp_str_bot = datetime.fromtimestamp(now_bot).strftime('%Y-%m-%d %H:%M:%S')
1246
- save_chat_message({
1247
- 'chat_id': chat_id,
1248
- 'role': 'bot',
1249
- 'sender_id': bot.id,
1250
- 'sender_name': "Bot",
1251
- 'text': "Произошла ошибка при обработке вашего запроса.",
1252
- 'timestamp': now_bot,
1253
- 'timestamp_str': timestamp_str_bot
1254
- })
1255
-
1256
-
1257
- def run_bot():
1258
- try:
1259
- logging.info("Starting aiogram bot polling...")
1260
- executor.start_polling(dp, skip_updates=True)
1261
- except Exception as e:
1262
- logging.error(f"Error starting aiogram bot polling: {e}")
1263
 
 
1264
 
1265
  @app.route('/')
1266
  def index():
@@ -1316,26 +1020,9 @@ def verify_data():
1316
 
1317
  @app.route('/admin')
1318
  def admin_panel():
1319
- full_data = load_visitor_data()
1320
- users_list = list(full_data.get("visitors", {}).values())
1321
- chat_entries = full_data.get("chats", [])
1322
-
1323
- chats_by_id = {}
1324
- for chat in chat_entries:
1325
- chat_id = str(chat.get('chat_id'))
1326
- if chat_id not in chats_by_id:
1327
- chats_by_id[chat_id] = []
1328
- chats_by_id[chat_id].append(chat)
1329
-
1330
- for chat_id in chats_by_id:
1331
- chats_by_id[chat_id].sort(key=lambda x: x.get('timestamp', 0))
1332
-
1333
- users_by_id = {user['id']: user for user in users_list}
1334
-
1335
- return render_template_string(ADMIN_TEMPLATE,
1336
- users=users_list,
1337
- chats=chats_by_id,
1338
- users_by_id=users_by_id)
1339
 
1340
  @app.route('/admin/download_data', methods=['POST'])
1341
  def admin_trigger_download():
@@ -1352,96 +1039,58 @@ def admin_trigger_upload():
1352
  upload_data_to_hf_async()
1353
  return jsonify({"status": "ok", "message": "Загрузка данных на Hugging Face запущена в фоновом режиме."})
1354
 
1355
- @app.route('/admin/send_message', methods=['POST'])
1356
- def admin_send_message():
1357
  try:
1358
- req_data = request.get_json()
1359
- chat_id = req_data.get('chat_id')
1360
- message_text = req_data.get('message')
1361
-
1362
- if not chat_id or not message_text:
1363
- return jsonify({"status": "error", "message": "Missing chat_id or message text"}), 400
1364
-
1365
- try:
1366
- chat_id = int(chat_id)
1367
- except ValueError:
1368
- return jsonify({"status": "error", "message": "Invalid chat ID"}), 400
1369
-
1370
- loop = asyncio.new_event_loop()
1371
- asyncio.set_event_loop(loop)
1372
- try:
1373
- loop.run_until_complete(bot.send_message(chat_id=chat_id, text=message_text))
1374
-
1375
- now = time.time()
1376
- timestamp_str = datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
1377
- save_chat_message({
1378
- 'chat_id': chat_id,
1379
- 'role': 'admin_sent',
1380
- 'sender_id': 'admin', # Or a specific admin identifier
1381
- 'sender_name': 'Admin',
1382
- 'text': message_text,
1383
- 'timestamp': now,
1384
- 'timestamp_str': timestamp_str
1385
- })
1386
-
1387
- return jsonify({"status": "ok", "message": "Сообщение успешно отправлено!"}), 200
1388
- except Exception as e:
1389
- logging.error(f"Error sending message via bot API: {e}")
1390
- return jsonify({"status": "error", "message": f"Ошибка отправки сообщения через Bot API: {e}"}), 500
1391
- finally:
1392
- loop.close()
1393
-
1394
-
1395
  except Exception as e:
1396
- logging.exception("Error in /admin/send_message endpoint")
1397
- return jsonify({"status": "error", "message": "Internal server error"}), 500
1398
-
1399
 
1400
  if __name__ == '__main__':
1401
- logging.info("---")
1402
- logging.info("--- MORSHEN GROUP MINI APP & BOT SERVER ---")
1403
- logging.info("---")
1404
- logging.info(f"Flask server starting on http://{HOST}:{PORT}")
1405
- logging.info(f"Using Bot Token ID: {BOT_TOKEN.split(':')[0]}")
1406
- logging.info(f"Visitor data file: {DATA_FILE}")
1407
- logging.info(f"Hugging Face Repo: {REPO_ID}")
1408
- logging.info(f"HF Data Path: {HF_DATA_FILE_PATH}")
1409
  if not HF_TOKEN_READ or not HF_TOKEN_WRITE:
1410
- logging.warning("---")
1411
- logging.warning("--- WARNING: HUGGING FACE TOKEN(S) NOT SET ---")
1412
- logging.warning("--- Backup/restore functionality will be limited. Set HF_TOKEN_READ and HF_TOKEN_WRITE environment variables.")
1413
- logging.warning("---")
1414
  else:
1415
- logging.info("--- Hugging Face tokens found.")
1416
- logging.info("--- Attempting initial data download from Hugging Face...")
1417
  download_data_from_hf()
1418
 
1419
  if not GEMINI_API_KEY:
1420
- logging.warning("---")
1421
- logging.warning("--- WARNING: GEMINI_API_KEY NOT SET ---")
1422
- logging.warning("--- AI response functionality will be disabled. Set GEMINI_API_KEY environment variable.")
1423
- logging.warning("---")
1424
  else:
1425
- logging.info("--- GEMINI_API_KEY found.")
 
1426
 
1427
  load_visitor_data()
1428
 
1429
- logging.warning("---")
1430
- logging.warning("--- SECURITY WARNING ---")
1431
- logging.warning("--- The /admin route and its sub-routes are NOT protected.")
1432
- logging.warning("--- Implement proper authentication before deploying.")
1433
- logging.warning("---")
1434
 
1435
  if HF_TOKEN_WRITE:
1436
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
1437
  backup_thread.start()
1438
- logging.info("--- Periodic backup thread started (every hour).")
1439
  else:
1440
- logging.info("--- Periodic backup disabled (HF_TOKEN_WRITE missing).")
1441
 
1442
- bot_thread = threading.Thread(target=run_bot, daemon=True)
1443
  bot_thread.start()
1444
- logging.info("--- Telegram bot polling thread started.")
1445
 
1446
- logging.info("--- Server Ready ---")
1447
  app.run(host=HOST, port=PORT, debug=False)
 
11
  from datetime import datetime
12
  import logging
13
  import threading
14
+ import asyncio
15
  from huggingface_hub import HfApi, hf_hub_download
16
  from huggingface_hub.utils import RepositoryNotFoundError
17
 
18
+ from aiogram import Bot, Dispatcher, types, F
 
 
 
19
  from google import genai
20
  from google.genai import types
21
 
22
+ BOT_TOKEN = os.getenv("BOT_TOKEN", "7566834146:AAGiG4MaTZZvvtVsqEJVG5SYK5hUlc_Ewo")
23
  HOST = '0.0.0.0'
24
  PORT = 7860
25
  DATA_FILE = 'data.json'
 
29
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN_WRITE")
30
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
31
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
32
+ GENAI_MODEL = "learnlm-2.0-flash-experimental"
33
 
34
  app = Flask(__name__)
35
  logging.basicConfig(level=logging.INFO)
 
37
 
38
  _data_lock = threading.Lock()
39
  visitor_data_cache = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  def download_data_from_hf():
42
+ global visitor_data_cache
43
  if not HF_TOKEN_READ:
44
  logging.warning("HF_TOKEN_READ not set. Skipping Hugging Face download.")
45
  return False
 
59
  with _data_lock:
60
  try:
61
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
62
+ visitor_data_cache = json.load(f)
 
 
63
  logging.info("Successfully loaded downloaded data into cache.")
64
  except (FileNotFoundError, json.JSONDecodeError) as e:
65
  logging.error(f"Error reading downloaded data file: {e}. Starting with empty cache.")
66
  visitor_data_cache = {}
 
67
  return True
68
  except RepositoryNotFoundError:
69
  logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.")
 
72
  return False
73
 
74
  def load_visitor_data():
75
+ global visitor_data_cache
76
  with _data_lock:
77
+ if not visitor_data_cache:
78
  try:
79
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
80
+ visitor_data_cache = json.load(f)
81
+ logging.info("Visitor data loaded from local JSON.")
 
 
82
  except FileNotFoundError:
83
  logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.")
84
  visitor_data_cache = {}
 
85
  except json.JSONDecodeError:
86
  logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.")
87
  visitor_data_cache = {}
 
88
  except Exception as e:
89
+ logging.error(f"Unexpected error loading visitor data: {e}")
90
  visitor_data_cache = {}
91
+ return visitor_data_cache
 
 
92
 
93
+ def save_visitor_data(data):
94
  with _data_lock:
95
  try:
96
+ visitor_data_cache.update(data)
 
97
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
98
+ json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4)
99
+ logging.info(f"Visitor data successfully saved to {DATA_FILE}.")
100
  upload_data_to_hf_async()
101
  except Exception as e:
102
+ logging.error(f"Error saving visitor data: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  def upload_data_to_hf():
105
  if not HF_TOKEN_WRITE:
 
124
  repo_id=REPO_ID,
125
  repo_type="dataset",
126
  token=HF_TOKEN_WRITE,
127
+ commit_message=f"Update visitor data {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
128
  )
129
+ logging.info("Visitor data successfully uploaded to Hugging Face.")
130
  except Exception as e:
131
  logging.error(f"Error uploading data to Hugging Face: {e}")
132
 
 
163
  auth_date = int(parsed_data.get('auth_date', [0])[0])
164
  current_time = int(time.time())
165
  if current_time - auth_date > 86400:
166
+ logging.warning(f"Telegram InitData is older than 1 hour (Auth Date: {auth_date}, Current: {current_time}).")
167
  return parsed_data, True
168
  else:
169
  logging.warning(f"Data verification failed. Calculated: {calculated_hash}, Received: {received_hash}")
 
193
  --tg-theme-button-text-color: {{ theme.button_text_color | default('#ffffff') }};
194
  --tg-theme-secondary-bg-color: {{ theme.secondary_bg_color | default('#1e1e1e') }};
195
 
196
+ --bg-gradient: linear-gradient(160deg, #1a232f 0%, #121212 100%);
197
  --card-bg: rgba(44, 44, 46, 0.8);
198
  --card-bg-solid: #2c2c2e;
199
  --text-color: var(--tg-theme-text-color);
 
372
  }
373
  .save-card-button i { font-size: 1.2em; }
374
 
 
375
  .modal {
376
  display: none; position: fixed; z-index: 1001;
377
  left: 0; top: 0; width: 100%; height: 100%;
 
403
  .modal-text b { color: var(--tg-theme-link-color); font-weight: 600; }
404
  .modal-instruction { font-size: 1em; color: var(--text-secondary-color); margin-top: var(--padding-m); }
405
 
 
406
  .icon { display: inline-block; width: 1.2em; text-align: center; margin-right: 8px; opacity: 0.9; }
407
  .icon-save::before { content: '💾'; }
408
  .icon-web::before { content: '🌐'; }
 
424
  .icon-leader::before { content: '🏆'; }
425
  .icon-company::before { content: '🏢'; }
426
 
 
427
  @media (max-width: 480px) {
428
  .section-title { font-size: 1.8em; }
429
  .logo span { font-size: 1.4em; }
 
553
  <i class="icon icon-save"></i>Сохранить визитку
554
  </button>
555
 
 
556
  <div id="saveModal" class="modal">
557
  <div class="modal-content">
558
  <span class="modal-close" id="modal-close-btn">×</span>
 
563
  </div>
564
  </div>
565
 
 
566
  <script>
567
  const tg = window.Telegram.WebApp;
568
 
 
698
  <head>
699
  <meta charset="UTF-8">
700
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
701
+ <title>Admin - Посетители</title>
702
  <link rel="preconnect" href="https://fonts.googleapis.com">
703
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
704
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
714
  --admin-success: #198754;
715
  --admin-danger: #dc3545;
716
  --admin-warning: #ffc107;
 
717
  --border-radius: 12px;
718
  --padding: 1.5rem;
719
  --font-family: 'Inter', sans-serif;
 
727
  line-height: 1.6;
728
  }
729
  .container { max-width: 1140px; margin: 0 auto; }
730
+ h1 { text-align: center; color: var(--admin-secondary); margin-bottom: var(--padding); font-weight: 600; }
 
731
  .user-grid {
732
  display: grid;
733
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
734
  gap: var(--padding);
735
  margin-top: var(--padding);
736
  }
737
+ .user-card {
738
  background-color: var(--admin-card-bg);
739
  border-radius: var(--border-radius);
740
  padding: var(--padding);
741
  box-shadow: 0 4px 15px var(--admin-shadow);
742
  border: 1px solid var(--admin-border);
743
+ display: flex;
744
+ flex-direction: column;
745
+ align-items: center;
746
+ text-align: center;
747
  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
748
  }
 
 
 
 
 
 
749
  .user-card:hover {
750
  transform: translateY(-5px);
751
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
 
762
  .user-card .detail-item { margin-bottom: 0.3rem; }
763
  .user-card .detail-item strong { color: var(--admin-text); }
764
  .user-card .timestamp { font-size: 0.8em; color: var(--admin-secondary); margin-top: 1rem; }
765
+ .no-users { text-align: center; color: var(--admin-secondary); margin-top: 2rem; font-size: 1.1em; }
766
  .alert {
767
  background-color: #fff3cd; border-left: 6px solid var(--admin-warning);
768
  margin-bottom: var(--padding); padding: 1rem 1.5rem;
 
783
  }
784
  .refresh-btn:hover { background-color: #0b5ed7; }
785
 
 
786
  .admin-controls {
787
  background: var(--admin-card-bg);
788
  padding: var(--padding);
 
815
  display: none;
816
  }
817
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
  </style>
819
  </head>
820
  <body>
821
  <div class="container">
822
+ <h1>Посетители Mini App</h1>
823
  <div class="alert">ВНИМАНИЕ: Этот раздел не защищен! Добавьте аутентификацию для реального использования.</div>
824
 
825
  <div class="admin-controls">
 
830
  <div class="status" id="status-message"></div>
831
  </div>
832
 
833
+ <button class="refresh-btn" onclick="location.reload()">Обновить список</button>
834
 
 
835
  {% if users %}
836
  <div class="user-grid">
837
  {% for user in users|sort(attribute='visited_at', reverse=true) %}
 
856
  {% else %}
857
  <p class="no-users">Данных о посетителях пока нет.</p>
858
  {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
859
  </div>
860
 
861
  <script>
862
  const loader = document.getElementById('loader');
863
  const statusMessage = document.getElementById('status-message');
 
 
864
 
865
  async function handleFetch(url, action) {
866
  loader.style.display = 'inline-block';
 
874
  statusMessage.style.color = 'var(--admin-success)';
875
  if (action === 'скачивание') {
876
  setTimeout(() => location.reload(), 1500);
 
 
877
  }
878
  } else {
879
  throw new Error(data.message || 'Произошла ошибка');
 
882
  statusMessage.textContent = `Ошибка ${action}: ${error.message}`;
883
  statusMessage.style.color = 'var(--admin-danger)';
884
  console.error(`Error during ${action}:`, error);
 
885
  } finally {
886
  loader.style.display = 'none';
887
  }
 
894
  function triggerUpload() {
895
  handleFetch('/admin/upload_data', 'загрузка');
896
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
897
  </script>
898
  </body>
899
  </html>
900
  """
901
 
902
+ bot = Bot(token=BOT_TOKEN)
903
+ dp = Dispatcher()
 
 
 
 
 
 
 
904
 
905
+ def generate_bot_response(query: str, context: str):
906
+ if not GEMINI_API_KEY:
907
+ logging.warning("GEMINI_API_KEY not set. Cannot use AI.")
908
+ return "Извините, функция ответа с использованием AI временно недоступна."
 
 
 
 
 
909
 
910
+ lower_query = query.lower()
911
+ code_keywords = ["код", "скрипт", "программа", "напиши", "сделай", "код на", "пример кода"]
912
+ if any(keyword in lower_query for keyword in code_keywords):
913
+ return "Извините, я не могу предоставить вам код. Могу ли я ответить на другие вопросы о Morshen Group или Holmgard Studio?"
914
 
915
  try:
916
+ client = genai.Client(api_key=GEMINI_API_KEY)
917
+
918
+ prompt = f"""You are a helpful assistant answering questions about the following website content for "Morshen Group" and "Holmgard Studio".
919
+ Only use information explicitly provided in the CONTENT below. Do not invent or assume information not present in the content.
920
+ If the user asks for code, *you must refuse* and explain that you cannot provide code.
921
+ Keep responses concise and directly related to the query and content.
922
+
923
+ CONTENT:
924
+ {context}
925
+
926
+ USER QUERY: {query}
927
+
928
+ ASSISTANT RESPONSE:"""
929
+
930
+ contents = [
931
+ types.Content(
932
+ role="user",
933
+ parts=[
934
+ types.Part.from_text(text=prompt),
935
+ ],
936
+ ),
937
+ ]
938
+ generate_content_config = types.GenerateContentConfig(
939
+ response_mime_type="text/plain",
940
+ temperature=0.1,
941
+ max_output_tokens=500
942
+ )
943
+
944
+ response = client.models.generate_content(
945
+ model=GENAI_MODEL,
946
+ contents=contents,
947
+ config=generate_content_config,
948
+ )
949
+ return response.text.strip()
950
 
951
  except Exception as e:
952
+ logging.error(f"Error generating AI response: {e}")
953
+ return "Извините, произошла ошибка при обработке вашего запроса."
954
+
955
+ @dp.message()
956
+ async def handle_message(message: types.Message):
957
+ user_query = message.text
958
+ if not user_query:
959
+ return
960
+
961
+ await message.answer("Думаю...")
962
+
963
+ context = TEMPLATE
964
+
965
+ response_text = generate_bot_response(user_query, context)
 
 
 
 
 
 
 
966
 
967
+ await message.answer(response_text)
968
 
969
  @app.route('/')
970
  def index():
 
1020
 
1021
  @app.route('/admin')
1022
  def admin_panel():
1023
+ current_data = load_visitor_data()
1024
+ users_list = list(current_data.values())
1025
+ return render_template_string(ADMIN_TEMPLATE, users=users_list)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
 
1027
  @app.route('/admin/download_data', methods=['POST'])
1028
  def admin_trigger_download():
 
1039
  upload_data_to_hf_async()
1040
  return jsonify({"status": "ok", "message": "Загрузка данных на Hugging Face запущена в фоновом режиме."})
1041
 
1042
+ def start_bot_polling():
 
1043
  try:
1044
+ asyncio.run(dp.start_polling(bot))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1045
  except Exception as e:
1046
+ logging.error(f"Error in bot polling thread: {e}")
 
 
1047
 
1048
  if __name__ == '__main__':
1049
+ print("---")
1050
+ print("--- MORSHEN GROUP SERVER (FLASK + AIOGRAM) ---")
1051
+ print("---")
1052
+ print(f"Flask server starting on http://{HOST}:{PORT}")
1053
+ print(f"Using Bot Token ID: {BOT_TOKEN.split(':')[0]}")
1054
+ print(f"Visitor data file: {DATA_FILE}")
1055
+ print(f"Hugging Face Repo: {REPO_ID}")
1056
+ print(f"HF Data Path: {HF_DATA_FILE_PATH}")
1057
  if not HF_TOKEN_READ or not HF_TOKEN_WRITE:
1058
+ print("---")
1059
+ print("--- WARNING: HUGGING FACE TOKEN(S) NOT SET ---")
1060
+ print("--- Backup/restore functionality will be limited. Set HF_TOKEN_READ and HF_TOKEN_WRITE environment variables.")
1061
+ print("---")
1062
  else:
1063
+ print("--- Hugging Face tokens found.")
1064
+ print("--- Attempting initial data download from Hugging Face...")
1065
  download_data_from_hf()
1066
 
1067
  if not GEMINI_API_KEY:
1068
+ print("---")
1069
+ print("--- WARNING: GEMINI_API_KEY NOT SET ---")
1070
+ print("--- AI response functionality will be limited or unavailable. Set GEMINI_API_KEY environment variable.")
1071
+ print("---")
1072
  else:
1073
+ print("--- GEMINI_API_KEY found.")
1074
+ print(f"--- Using AI model: {GENAI_MODEL}")
1075
 
1076
  load_visitor_data()
1077
 
1078
+ print("---")
1079
+ print("--- SECURITY WARNING ---")
1080
+ print("--- The /admin route and its sub-routes are NOT protected.")
1081
+ print("--- Implement proper authentication before deploying.")
1082
+ print("---")
1083
 
1084
  if HF_TOKEN_WRITE:
1085
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
1086
  backup_thread.start()
1087
+ print("--- Periodic backup thread started (every hour).")
1088
  else:
1089
+ print("--- Periodic backup disabled (HF_TOKEN_WRITE missing).")
1090
 
1091
+ bot_thread = threading.Thread(target=start_bot_polling, daemon=True)
1092
  bot_thread.start()
1093
+ print("--- Telegram bot polling thread started.")
1094
 
1095
+ print("--- Server Ready ---")
1096
  app.run(host=HOST, port=PORT, debug=False)