Kgshop commited on
Commit
9768e46
·
verified ·
1 Parent(s): a8c75d8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -111
app.py CHANGED
@@ -84,45 +84,38 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN
84
  force_download=True,
85
  resume_download=False
86
  )
87
- logging.info(f"Successfully downloaded {file_name} to {local_path}")
88
  success = True
89
  break
90
  except RepositoryNotFoundError:
91
- logging.error(f"Repository {REPO_ID} not found. Cannot download file.")
92
  return False
93
  except HfHubHTTPError as e:
94
  if e.response.status_code == 404:
95
- logging.warning(f"{file_name} not found in repo. Will try to create a local empty one if needed.")
96
  if attempt == 0 and not os.path.exists(file_name):
97
  try:
98
  if file_name == DATA_FILE:
99
  with open(file_name, 'w', encoding='utf-8') as f:
100
  json.dump({}, f)
101
- logging.info(f"Created empty local file: {file_name}")
102
  except Exception as create_e:
103
- logging.error(f"Could not create local empty file {file_name}: {create_e}")
104
  success = False
105
  break
106
  else:
107
- logging.error(f"HTTP error downloading {file_name} on attempt {attempt+1}: {e}")
108
  except requests.exceptions.RequestException as e:
109
- logging.error(f"Network error downloading {file_name} on attempt {attempt+1}: {e}")
110
  except Exception as e:
111
- logging.error(f"An unexpected error occurred downloading {file_name} on attempt {attempt+1}: {e}")
112
 
113
  if attempt < retries:
114
- logging.info(f"Retrying download of {file_name} in {delay} seconds...")
115
  time.sleep(delay)
116
 
117
  if not success:
118
- logging.error(f"Failed to download {file_name} after {retries+1} attempts.")
119
  all_successful = False
120
 
121
  return all_successful
122
 
123
  def upload_db_to_hf(specific_file=None):
124
  if not HF_TOKEN_WRITE:
125
- logging.warning("HF_TOKEN_WRITE not set. Skipping upload to Hugging Face.")
126
  return
127
  try:
128
  api = HfApi()
@@ -139,19 +132,17 @@ def upload_db_to_hf(specific_file=None):
139
  token=HF_TOKEN_WRITE,
140
  commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
141
  )
142
- logging.info(f"Successfully uploaded {file_name} to Hugging Face.")
143
  except Exception as e:
144
- logging.error(f"Failed to upload {file_name} to Hugging Face: {e}")
145
  else:
146
- logging.warning(f"File {file_name} not found locally. Skipping upload.")
147
  except Exception as e:
148
- logging.error(f"An unexpected error occurred during the upload process: {e}")
149
 
150
  def periodic_backup():
151
  backup_interval = 1800
152
  while True:
153
  time.sleep(backup_interval)
154
- logging.info("Starting periodic backup...")
155
  upload_db_to_hf()
156
 
157
  def load_data():
@@ -159,19 +150,15 @@ def load_data():
159
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
160
  data = json.load(f)
161
  if not isinstance(data, dict):
162
- logging.warning(f"{DATA_FILE} does not contain a dictionary. Resetting to empty.")
163
  data = {}
164
  except (FileNotFoundError, json.JSONDecodeError):
165
- logging.warning(f"{DATA_FILE} not found or corrupted. Attempting to download from Hugging Face.")
166
  if download_db_from_hf(specific_file=DATA_FILE):
167
  try:
168
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
169
  data = json.load(f)
170
  if not isinstance(data, dict):
171
- logging.warning(f"Downloaded {DATA_FILE} is not a dictionary. Resetting to empty.")
172
  data = {}
173
  except (FileNotFoundError, json.JSONDecodeError):
174
- logging.error(f"Failed to load {DATA_FILE} even after download. Using empty data.")
175
  data = {}
176
  else:
177
  data = {}
@@ -181,11 +168,9 @@ def save_data(data):
181
  try:
182
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
183
  json.dump(data, file, ensure_ascii=False, indent=4)
184
- upload_thread = threading.Thread(target=upload_db_to_hf, kwargs={'specific_file': DATA_FILE})
185
- upload_thread.daemon = True
186
- upload_thread.start()
187
  except Exception as e:
188
- logging.error(f"Error in save_data: {e}")
189
 
190
  def is_chat_active(env_id):
191
  data = get_env_data(env_id)
@@ -266,14 +251,11 @@ def save_env_data(env_id, env_data):
266
 
267
  def configure_gemini():
268
  if not GOOGLE_API_KEY:
269
- logging.warning("GOOGLE_API_KEY is not set. Gemini AI features will be disabled.")
270
  return False
271
  try:
272
  genai.configure(api_key=GOOGLE_API_KEY)
273
- logging.info("Google Gemini AI configured successfully.")
274
  return True
275
  except Exception as e:
276
- logging.error(f"Failed to configure Google Gemini AI: {e}")
277
  return False
278
 
279
  def generate_ai_description_from_image(image_data, language):
@@ -286,7 +268,6 @@ def generate_ai_description_from_image(image_data, language):
286
  image_stream = io.BytesIO(image_data)
287
  image = Image.open(image_stream).convert('RGB')
288
  except Exception as e:
289
- logging.error(f"Error processing image for AI description: {e}")
290
  raise ValueError(f"Не удалось обработать изображение. Убедитесь, что это действительный файл изображения.")
291
 
292
  base_prompt = "Напиши большой и красивый, содержательный рекламный пост минимум на 1000 символов со смайликами и 25 тематических хэштегов с ключевыми словами разных вариантов, чтобы мои клиенты могли найти меня в поиске Instagram, Google и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона."
@@ -304,7 +285,7 @@ def generate_ai_description_from_image(image_data, language):
304
  final_prompt = f"{base_prompt}{lang_suffix}"
305
 
306
  try:
307
- model = genai.GenerativeModel('gemma-2-9b-it')
308
 
309
  response = model.generate_content([final_prompt, image])
310
 
@@ -318,13 +299,12 @@ def generate_ai_description_from_image(image_data, language):
318
  return response.text
319
 
320
  except Exception as e:
321
- logging.error(f"Error during AI content generation: {e}")
322
  if "API key not valid" in str(e):
323
  raise ValueError("Внутренняя ошибка конфигурации API.")
324
  elif " Billing account not found" in str(e):
325
  raise ValueError("Проблема с биллингом аккаунта Google Cloud. Проверьте ваш аккаунт.")
326
  elif "Could not find model" in str(e):
327
- raise ValueError(f"Модель 'gemma-2-9b-it' не найдена или недоступна.")
328
  elif "resource has been exhausted" in str(e).lower():
329
  raise ValueError("Квота запросов исчерпана. Попробуйте позже.")
330
  elif "content has been blocked" in str(e).lower():
@@ -394,7 +374,7 @@ def generate_chat_response(message, chat_history_from_client, env_id):
394
  response = None
395
 
396
  try:
397
- model = genai.GenerativeModel('gemma-2-9b-it')
398
 
399
  model_chat_history_for_gemini = [
400
  {'role': 'user', 'parts': [{'text': system_instruction_content}]}
@@ -424,7 +404,6 @@ def generate_chat_response(message, chat_history_from_client, env_id):
424
  return generated_text
425
 
426
  except Exception as e:
427
- logging.error(f"Error generating chat response: {e}")
428
  if "API key not valid" in str(e):
429
  return "Внутренняя ошибка конфигурации API."
430
  elif " Billing account not found" in str(e):
@@ -465,6 +444,15 @@ ADMHOSTO_TEMPLATE = '''
465
  h1 { font-weight: 600; color: var(--bg-medium); margin-bottom: 25px; text-align: center; }
466
  .section { margin-bottom: 30px; }
467
  .add-env-form { margin-bottom: 20px; text-align: center; }
 
 
 
 
 
 
 
 
 
468
  .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: var(--accent); color: var(--text-on-accent); font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; }
469
  .button:hover { background-color: var(--accent-hover); }
470
  .button:disabled { background-color: #ccc; cursor: not-allowed; }
@@ -501,10 +489,7 @@ ADMHOSTO_TEMPLATE = '''
501
  </div>
502
 
503
  <div class="section">
504
- <div class="search-wrapper" style="margin-bottom: 20px; position: relative;">
505
- <i class="fas fa-search" style="position: absolute; top: 50%; left: 15px; transform: translateY(-50%); color: #aaa;"></i>
506
- <input type="text" id="env-search-input" onkeyup="filterEnvironments()" placeholder="Поиск по ID среды..." style="width: 100%; padding: 10px 15px 10px 40px; border-radius: 6px; border: 1px solid #ddd; font-size: 1rem; box-sizing: border-box;">
507
- </div>
508
  </div>
509
 
510
  <div class="section">
@@ -549,40 +534,18 @@ ADMHOSTO_TEMPLATE = '''
549
  </div>
550
  </div>
551
  <script>
552
- function filterEnvironments() {
553
- const input = document.getElementById('env-search-input');
554
- const filter = input.value.toLowerCase();
555
- const envList = document.querySelector('.env-list');
556
- const items = envList.getElementsByTagName('li');
557
- let found = false;
558
-
559
- for (let i = 0; i < items.length; i++) {
560
- const envIdElement = items[i].querySelector('.env-id');
561
- if (envIdElement) {
562
- const txtValue = envIdElement.textContent || envIdElement.innerText;
563
- if (txtValue.toLowerCase().indexOf(filter) > -1) {
564
- items[i].style.display = "";
565
- found = true;
566
- } else {
567
- items[i].style.display = "none";
568
- }
569
- }
570
- }
571
-
572
- let noResultsMsg = document.getElementById('no-results');
573
- if (!found && filter !== '') {
574
- if (!noResultsMsg) {
575
- noResultsMsg = document.createElement('p');
576
- noResultsMsg.id = 'no-results';
577
- noResultsMsg.textContent = 'Среда с таким ID не найдена.';
578
- noResultsMsg.style.textAlign = 'center';
579
- noResultsMsg.style.marginTop = '20px';
580
- envList.parentNode.appendChild(noResultsMsg);
581
  }
582
- } else if (noResultsMsg) {
583
- noResultsMsg.remove();
584
- }
585
- }
586
  </script>
587
  </body>
588
  </html>
@@ -2119,13 +2082,12 @@ ADMIN_TEMPLATE = '''
2119
  .item .description { font-size: 0.85rem; color: #999; max-height: 60px; overflow: hidden; text-overflow: ellipsis; }
2120
  .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
2121
  .edit-form-container { margin-top: 15px; padding: 20px; background: #E0F2F1; border: 1px dashed #B2DFDB; border-radius: 6px; display: none; }
2122
- details { background-color: #fdfdff; border-radius: 8px; margin-bottom: 10px; }
2123
- details:not(.section) { border: 1px solid #e0e0e0; }
2124
- details > summary { cursor: pointer; font-weight: 600; color: var(--bg-medium); display: block; padding: 15px; list-style: none; position: relative; }
2125
- details:not(.section) > summary { border-bottom: 1px solid #e0e0e0; }
2126
- details[open] > summary { border-bottom: 1px solid #e0e0e0; }
2127
  details > summary::after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); transition: transform 0.2s ease; color: var(--bg-medium); }
2128
  details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
 
2129
  details .form-content { padding: 20px; }
2130
  .color-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
2131
  .color-input-group input { flex-grow: 1; margin: 0; }
@@ -2187,8 +2149,8 @@ ADMIN_TEMPLATE = '''
2187
  <p>Чат <strong>активен</strong>. Срок действия истекает: <strong class="{{ 'status-indicator expires-soon' if chat_status.expires_soon else '' }}">{{ chat_status.expires_date }}</strong></p>
2188
  {% else %}
2189
  <p>Чат <strong>неактивен</strong>. {% if chat_status.expires_date %}Срок действия истек: {{ chat_status.expires_date }}{% endif %}</p>
2190
- <p>Для активации или продления чат-бота, пожа��уйста, свяжитесь с администратором.</p>
2191
- <a href="https://wa.me/77472479197" class="button add-button" target="_blank" style="margin-top: 10px;"><i class="fab fa-whatsapp"></i> Написать администратору</a>
2192
  {% endif %}
2193
  </div>
2194
 
@@ -2206,9 +2168,8 @@ ADMIN_TEMPLATE = '''
2206
  </div>
2207
 
2208
  <div class="section">
2209
- <h2><i class="fas fa-cog"></i> Настройки магазина и чата</h2>
2210
  <details>
2211
- <summary><i class="fas fa-chevron-down"></i> Развернуть/Свернуть</summary>
2212
  <div class="form-content">
2213
  <form method="POST" enctype="multipart/form-data">
2214
  <input type="hidden" name="action" value="update_settings">
@@ -2251,19 +2212,21 @@ ADMIN_TEMPLATE = '''
2251
 
2252
  <div class="section">
2253
  <details>
2254
- <summary><h2><i class="fas fa-comments"></i> Диалоги с {{ settings.chat_name }}</h2></summary>
2255
- <div class="item-list" style="margin-top: 20px;">
2256
- {% if chats %}
2257
- {% for chat_id, chat_data in chats.items()|sort(reverse=True) %}
2258
- <div class="chat-log-item" onclick="viewChat('{{ chat_id }}')">
2259
- <strong>ID Диалога:</strong> {{ chat_id }}
2260
- <br>
2261
- <small>Сообщений: {{ chat_data|length }} | Последнее сообщение: {{ chat_data[-1].timestamp if chat_data and 'timestamp' in chat_data[-1] else 'N/A' }}</small>
2262
- </div>
2263
- {% endfor %}
2264
- {% else %}
2265
- <p>Пока не было ни одного диалога.</p>
2266
- {% endif %}
 
 
2267
  </div>
2268
  </details>
2269
  </div>
@@ -2635,7 +2598,7 @@ ADMIN_TEMPLATE = '''
2635
  modalBody.innerHTML = 'Загрузка...';
2636
  openModal('chat-modal');
2637
  try {
2638
- const response = await fetch(`/${envId}/get_chat/${chatId}`);
2639
  if(!response.ok) throw new Error('Chat not found');
2640
  const chatHistory = await response.json();
2641
 
@@ -2889,7 +2852,6 @@ def create_order(env_id):
2889
  })
2890
  total_price += price * quantity
2891
  except (ValueError, TypeError) as e:
2892
- logging.error(f"Invalid cart item format: {e}")
2893
  return jsonify({"error": "Неверная цена или количество в товаре."}), 400
2894
 
2895
  order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}"
@@ -2914,7 +2876,6 @@ def create_order(env_id):
2914
  return jsonify({"order_id": order_id}), 201
2915
 
2916
  except Exception as e:
2917
- logging.error(f"Server error while creating order: {e}")
2918
  return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
2919
 
2920
  @app.route('/<env_id>/order/<order_id>')
@@ -3002,8 +2963,8 @@ def admin(env_id):
3002
  if old_avatar:
3003
  try:
3004
  api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
3005
- except Exception as e:
3006
- logging.warning(f"Could not delete old avatar {old_avatar}: {e}")
3007
 
3008
  ext = os.path.splitext(avatar_file.filename)[1].lower()
3009
  avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}"
@@ -3023,7 +2984,6 @@ def admin(env_id):
3023
  flash("Аватар чата успешно обновлен.", 'success')
3024
  except Exception as e:
3025
  flash(f"Ошибка при загрузке аватара: {e}", 'error')
3026
- logging.error(f"Error uploading avatar: {e}")
3027
  else:
3028
  flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
3029
 
@@ -3087,7 +3047,6 @@ def admin(env_id):
3087
  uploaded_count += 1
3088
  except Exception as e:
3089
  flash(f"Ошибка при загрузке фото {photo.filename}.", 'error')
3090
- logging.error(f"Error uploading photo {photo.filename}: {e}")
3091
  if os.path.exists(temp_path):
3092
  try: os.remove(temp_path)
3093
  except OSError: pass
@@ -3097,7 +3056,7 @@ def admin(env_id):
3097
  if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
3098
  os.rmdir(uploads_dir)
3099
  except OSError as e:
3100
- logging.warning(f"Could not remove temp upload directory: {e}")
3101
  elif not HF_TOKEN_WRITE and photos_files and any(f.filename for f in photos_files):
3102
  flash("HF_TOKEN (write) не настроен. Фотографии не были загружены.", "warning")
3103
 
@@ -3171,7 +3130,6 @@ def admin(env_id):
3171
  uploaded_count += 1
3172
  except Exception as e:
3173
  flash(f"Ошибка при загрузке нового фото {photo.filename}.", 'error')
3174
- logging.error(f"Error uploading new photo {photo.filename}: {e}")
3175
  if os.path.exists(temp_path):
3176
  try: os.remove(temp_path)
3177
  except OSError: pass
@@ -3179,7 +3137,7 @@ def admin(env_id):
3179
  if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
3180
  os.rmdir(uploads_dir)
3181
  except OSError as e:
3182
- logging.warning(f"Could not remove temp upload directory: {e}")
3183
 
3184
  if new_photos_list:
3185
  old_photos = product_to_edit.get('photos', [])
@@ -3195,7 +3153,6 @@ def admin(env_id):
3195
  )
3196
  except Exception as e:
3197
  flash("Не удалось удалить старые фотографии с сервера. Новые фото загружены.", "warning")
3198
- logging.warning(f"Could not delete old photos for product {product_to_edit['name']}: {e}")
3199
  product_to_edit['photos'] = new_photos_list
3200
  flash("Фотографии товара успешно обновлены.", "success")
3201
  elif uploaded_count == 0 and any(f.filename for f in photos_files):
@@ -3230,7 +3187,6 @@ def admin(env_id):
3230
  )
3231
  except Exception as e:
3232
  flash(f"Не удалось удалить фото для товара '{product_name}' с сервера. Товар удален локально.", "warning")
3233
- logging.warning(f"Could not delete photos for product {product_name}: {e}")
3234
  elif photos_to_delete and not HF_TOKEN_WRITE:
3235
  flash(f"Товар '{product_name}' удален локально, но фото не удалены с сервера (токен не задан).", "warning")
3236
 
@@ -3244,7 +3200,6 @@ def admin(env_id):
3244
  return redirect(url_for('admin', env_id=env_id))
3245
 
3246
  except Exception as e:
3247
- logging.error(f"An internal error occurred in admin POST action '{action}': {e}")
3248
  flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
3249
  return redirect(url_for('admin', env_id=env_id))
3250
 
@@ -3305,7 +3260,6 @@ def handle_generate_description_ai():
3305
  except ValueError as ve:
3306
  return jsonify({"error": str(ve)}), 400
3307
  except Exception as e:
3308
- logging.error(f"Internal server error during AI description generation: {e}")
3309
  return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
3310
 
3311
  @app.route('/<env_id>/chat_with_ai', methods=['POST'])
@@ -3339,7 +3293,6 @@ def handle_chat_with_ai(env_id):
3339
 
3340
  return jsonify({"text": ai_response_text})
3341
  except Exception as e:
3342
- logging.error(f"Error in chat handler: {e}")
3343
  return jsonify({"error": f"Ошибка чата: {e}"}), 500
3344
 
3345
  @app.route('/<env_id>/chat')
@@ -3381,7 +3334,6 @@ def force_upload(env_id):
3381
  flash("Данные успешно загружены на Hugging Face.", 'success')
3382
  except Exception as e:
3383
  flash(f"Ошибка при загрузке на Hugging Face: {e}", 'error')
3384
- logging.error(f"Error during forced upload: {e}")
3385
  return redirect(url_for('admin', env_id=env_id))
3386
 
3387
  @app.route('/<env_id>/force_download', methods=['POST'])
@@ -3393,7 +3345,6 @@ def force_download(env_id):
3393
  flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
3394
  except Exception as e:
3395
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
3396
- logging.error(f"Error during forced download: {e}")
3397
  return redirect(url_for('admin', env_id=env_id))
3398
 
3399
  if __name__ == '__main__':
@@ -3404,9 +3355,8 @@ if __name__ == '__main__':
3404
  if HF_TOKEN_WRITE:
3405
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
3406
  backup_thread.start()
3407
- logging.info("Periodic backup thread started.")
3408
  else:
3409
- logging.warning("HF_TOKEN_WRITE not set, periodic backups are disabled.")
3410
 
3411
  port = int(os.environ.get('PORT', 7860))
3412
  app.run(debug=False, host='0.0.0.0', port=port)
 
84
  force_download=True,
85
  resume_download=False
86
  )
 
87
  success = True
88
  break
89
  except RepositoryNotFoundError:
 
90
  return False
91
  except HfHubHTTPError as e:
92
  if e.response.status_code == 404:
 
93
  if attempt == 0 and not os.path.exists(file_name):
94
  try:
95
  if file_name == DATA_FILE:
96
  with open(file_name, 'w', encoding='utf-8') as f:
97
  json.dump({}, f)
 
98
  except Exception as create_e:
99
+ pass
100
  success = False
101
  break
102
  else:
103
+ pass
104
  except requests.exceptions.RequestException as e:
105
+ pass
106
  except Exception as e:
107
+ pass
108
 
109
  if attempt < retries:
 
110
  time.sleep(delay)
111
 
112
  if not success:
 
113
  all_successful = False
114
 
115
  return all_successful
116
 
117
  def upload_db_to_hf(specific_file=None):
118
  if not HF_TOKEN_WRITE:
 
119
  return
120
  try:
121
  api = HfApi()
 
132
  token=HF_TOKEN_WRITE,
133
  commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
134
  )
 
135
  except Exception as e:
136
+ pass
137
  else:
138
+ pass
139
  except Exception as e:
140
+ pass
141
 
142
  def periodic_backup():
143
  backup_interval = 1800
144
  while True:
145
  time.sleep(backup_interval)
 
146
  upload_db_to_hf()
147
 
148
  def load_data():
 
150
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
151
  data = json.load(f)
152
  if not isinstance(data, dict):
 
153
  data = {}
154
  except (FileNotFoundError, json.JSONDecodeError):
 
155
  if download_db_from_hf(specific_file=DATA_FILE):
156
  try:
157
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
158
  data = json.load(f)
159
  if not isinstance(data, dict):
 
160
  data = {}
161
  except (FileNotFoundError, json.JSONDecodeError):
 
162
  data = {}
163
  else:
164
  data = {}
 
168
  try:
169
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
170
  json.dump(data, file, ensure_ascii=False, indent=4)
171
+ upload_db_to_hf(specific_file=DATA_FILE)
 
 
172
  except Exception as e:
173
+ pass
174
 
175
  def is_chat_active(env_id):
176
  data = get_env_data(env_id)
 
251
 
252
  def configure_gemini():
253
  if not GOOGLE_API_KEY:
 
254
  return False
255
  try:
256
  genai.configure(api_key=GOOGLE_API_KEY)
 
257
  return True
258
  except Exception as e:
 
259
  return False
260
 
261
  def generate_ai_description_from_image(image_data, language):
 
268
  image_stream = io.BytesIO(image_data)
269
  image = Image.open(image_stream).convert('RGB')
270
  except Exception as e:
 
271
  raise ValueError(f"Не удалось обработать изображение. Убедитесь, что это действительный файл изображения.")
272
 
273
  base_prompt = "Напиши большой и красивый, содержательный рекламный пост минимум на 1000 символов со смайликами и 25 тематических хэштегов с ключевыми словами разных вариантов, чтобы мои клиенты могли найти меня в поиске Instagram, Google и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона."
 
285
  final_prompt = f"{base_prompt}{lang_suffix}"
286
 
287
  try:
288
+ model = genai.GenerativeModel('gemma-3-27b-it')
289
 
290
  response = model.generate_content([final_prompt, image])
291
 
 
299
  return response.text
300
 
301
  except Exception as e:
 
302
  if "API key not valid" in str(e):
303
  raise ValueError("Внутренняя ошибка конфигурации API.")
304
  elif " Billing account not found" in str(e):
305
  raise ValueError("Проблема с биллингом аккаунта Google Cloud. Проверьте ваш аккаунт.")
306
  elif "Could not find model" in str(e):
307
+ raise ValueError(f"Модель 'learnlm-2.0-flash-experimental' не найдена или недоступна.")
308
  elif "resource has been exhausted" in str(e).lower():
309
  raise ValueError("Квота запросов исчерпана. Попробуйте позже.")
310
  elif "content has been blocked" in str(e).lower():
 
374
  response = None
375
 
376
  try:
377
+ model = genai.GenerativeModel('gemma-3-27b-it')
378
 
379
  model_chat_history_for_gemini = [
380
  {'role': 'user', 'parts': [{'text': system_instruction_content}]}
 
404
  return generated_text
405
 
406
  except Exception as e:
 
407
  if "API key not valid" in str(e):
408
  return "Внутренняя ошибка конфигурации API."
409
  elif " Billing account not found" in str(e):
 
444
  h1 { font-weight: 600; color: var(--bg-medium); margin-bottom: 25px; text-align: center; }
445
  .section { margin-bottom: 30px; }
446
  .add-env-form { margin-bottom: 20px; text-align: center; }
447
+ #search-env {
448
+ width: 100%;
449
+ padding: 10px;
450
+ border: 1px solid #ddd;
451
+ border-radius: 6px;
452
+ box-sizing: border-box;
453
+ font-size: 1rem;
454
+ font-family: 'Montserrat', sans-serif;
455
+ }
456
  .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: var(--accent); color: var(--text-on-accent); font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; }
457
  .button:hover { background-color: var(--accent-hover); }
458
  .button:disabled { background-color: #ccc; cursor: not-allowed; }
 
489
  </div>
490
 
491
  <div class="section">
492
+ <input type="text" id="search-env" placeholder="Поиск по ID среды...">
 
 
 
493
  </div>
494
 
495
  <div class="section">
 
534
  </div>
535
  </div>
536
  <script>
537
+ document.getElementById('search-env').addEventListener('input', function() {
538
+ const searchTerm = this.value.toLowerCase().trim();
539
+ const envItems = document.querySelectorAll('.env-item');
540
+ envItems.forEach(item => {
541
+ const envId = item.querySelector('.env-id').textContent.toLowerCase();
542
+ if (envId.includes(searchTerm)) {
543
+ item.style.display = 'grid';
544
+ } else {
545
+ item.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  }
547
+ });
548
+ });
 
 
549
  </script>
550
  </body>
551
  </html>
 
2082
  .item .description { font-size: 0.85rem; color: #999; max-height: 60px; overflow: hidden; text-overflow: ellipsis; }
2083
  .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
2084
  .edit-form-container { margin-top: 15px; padding: 20px; background: #E0F2F1; border: 1px dashed #B2DFDB; border-radius: 6px; display: none; }
2085
+ details { background-color: #fdfdff; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 20px; }
2086
+ details > summary { cursor: pointer; font-weight: 600; color: var(--bg-medium); display: block; padding: 15px; list-style: none; position: relative; font-size: 1.2rem; }
2087
+ details > summary:hover { background-color: #fafafa; }
 
 
2088
  details > summary::after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); transition: transform 0.2s ease; color: var(--bg-medium); }
2089
  details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
2090
+ details[open] > summary { border-bottom: 1px solid #e0e0e0; }
2091
  details .form-content { padding: 20px; }
2092
  .color-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
2093
  .color-input-group input { flex-grow: 1; margin: 0; }
 
2149
  <p>Чат <strong>активен</strong>. Срок действия истекает: <strong class="{{ 'status-indicator expires-soon' if chat_status.expires_soon else '' }}">{{ chat_status.expires_date }}</strong></p>
2150
  {% else %}
2151
  <p>Чат <strong>неактивен</strong>. {% if chat_status.expires_date %}Срок действия истек: {{ chat_status.expires_date }}{% endif %}</p>
2152
+ <p style="color: var(--danger);">Для активации чат-бота, пожалуйста, свяжитесь с администратором.</p>
2153
+ <a href="https://wa.me/77472479197" target="_blank" class="button add-button" style="margin-top: 10px;"><i class="fab fa-whatsapp"></i> Написать администратору</a>
2154
  {% endif %}
2155
  </div>
2156
 
 
2168
  </div>
2169
 
2170
  <div class="section">
 
2171
  <details>
2172
+ <summary><i class="fas fa-cog"></i> Настройки магазина и чата</summary>
2173
  <div class="form-content">
2174
  <form method="POST" enctype="multipart/form-data">
2175
  <input type="hidden" name="action" value="update_settings">
 
2212
 
2213
  <div class="section">
2214
  <details>
2215
+ <summary><i class="fas fa-comments"></i> Диалоги с {{ settings.chat_name }}</summary>
2216
+ <div class="form-content">
2217
+ <div class="item-list">
2218
+ {% if chats %}
2219
+ {% for chat_id, chat_data in chats.items()|sort(reverse=True) %}
2220
+ <div class="chat-log-item" onclick="viewChat('{{ chat_id }}')">
2221
+ <strong>ID Диалога:</strong> {{ chat_id }}
2222
+ <br>
2223
+ <small>Сообщений: {{ chat_data|length }} | Последнее сообщение: {{ chat_data[-1].timestamp if chat_data and 'timestamp' in chat_data[-1] else 'N/A' }}</small>
2224
+ </div>
2225
+ {% endfor %}
2226
+ {% else %}
2227
+ <p>Пока не было ни одного диалога.</p>
2228
+ {% endif %}
2229
+ </div>
2230
  </div>
2231
  </details>
2232
  </div>
 
2598
  modalBody.innerHTML = 'Загрузка...';
2599
  openModal('chat-modal');
2600
  try {
2601
+ const response = await fetch(`/${env_id}/get_chat/${chatId}`);
2602
  if(!response.ok) throw new Error('Chat not found');
2603
  const chatHistory = await response.json();
2604
 
 
2852
  })
2853
  total_price += price * quantity
2854
  except (ValueError, TypeError) as e:
 
2855
  return jsonify({"error": "Неверная цена или количество в товаре."}), 400
2856
 
2857
  order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}"
 
2876
  return jsonify({"order_id": order_id}), 201
2877
 
2878
  except Exception as e:
 
2879
  return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
2880
 
2881
  @app.route('/<env_id>/order/<order_id>')
 
2963
  if old_avatar:
2964
  try:
2965
  api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
2966
+ except Exception:
2967
+ pass
2968
 
2969
  ext = os.path.splitext(avatar_file.filename)[1].lower()
2970
  avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}"
 
2984
  flash("Аватар чата успешно обновлен.", 'success')
2985
  except Exception as e:
2986
  flash(f"Ошибка при загрузке аватара: {e}", 'error')
 
2987
  else:
2988
  flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
2989
 
 
3047
  uploaded_count += 1
3048
  except Exception as e:
3049
  flash(f"Ошибка при загрузке фото {photo.filename}.", 'error')
 
3050
  if os.path.exists(temp_path):
3051
  try: os.remove(temp_path)
3052
  except OSError: pass
 
3056
  if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
3057
  os.rmdir(uploads_dir)
3058
  except OSError as e:
3059
+ pass
3060
  elif not HF_TOKEN_WRITE and photos_files and any(f.filename for f in photos_files):
3061
  flash("HF_TOKEN (write) не настроен. Фотографии не были загружены.", "warning")
3062
 
 
3130
  uploaded_count += 1
3131
  except Exception as e:
3132
  flash(f"Ошибка при загрузке нового фото {photo.filename}.", 'error')
 
3133
  if os.path.exists(temp_path):
3134
  try: os.remove(temp_path)
3135
  except OSError: pass
 
3137
  if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
3138
  os.rmdir(uploads_dir)
3139
  except OSError as e:
3140
+ pass
3141
 
3142
  if new_photos_list:
3143
  old_photos = product_to_edit.get('photos', [])
 
3153
  )
3154
  except Exception as e:
3155
  flash("Не удалось удалить старые фотографии с сервера. Новые фото загружены.", "warning")
 
3156
  product_to_edit['photos'] = new_photos_list
3157
  flash("Фотографии товара успешно обновлены.", "success")
3158
  elif uploaded_count == 0 and any(f.filename for f in photos_files):
 
3187
  )
3188
  except Exception as e:
3189
  flash(f"Не удалось удалить фото для товара '{product_name}' с сервера. Товар удален локально.", "warning")
 
3190
  elif photos_to_delete and not HF_TOKEN_WRITE:
3191
  flash(f"Товар '{product_name}' удален локально, но фото не удалены с сервера (токен не задан).", "warning")
3192
 
 
3200
  return redirect(url_for('admin', env_id=env_id))
3201
 
3202
  except Exception as e:
 
3203
  flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
3204
  return redirect(url_for('admin', env_id=env_id))
3205
 
 
3260
  except ValueError as ve:
3261
  return jsonify({"error": str(ve)}), 400
3262
  except Exception as e:
 
3263
  return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
3264
 
3265
  @app.route('/<env_id>/chat_with_ai', methods=['POST'])
 
3293
 
3294
  return jsonify({"text": ai_response_text})
3295
  except Exception as e:
 
3296
  return jsonify({"error": f"Ошибка чата: {e}"}), 500
3297
 
3298
  @app.route('/<env_id>/chat')
 
3334
  flash("Данные успешно загружены на Hugging Face.", 'success')
3335
  except Exception as e:
3336
  flash(f"Ошибка при загрузке на Hugging Face: {e}", 'error')
 
3337
  return redirect(url_for('admin', env_id=env_id))
3338
 
3339
  @app.route('/<env_id>/force_download', methods=['POST'])
 
3345
  flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
3346
  except Exception as e:
3347
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
 
3348
  return redirect(url_for('admin', env_id=env_id))
3349
 
3350
  if __name__ == '__main__':
 
3355
  if HF_TOKEN_WRITE:
3356
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
3357
  backup_thread.start()
 
3358
  else:
3359
+ pass
3360
 
3361
  port = int(os.environ.get('PORT', 7860))
3362
  app.run(debug=False, host='0.0.0.0', port=port)