Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -84,38 +84,45 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN
|
|
| 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 |
-
|
| 100 |
success = False
|
| 101 |
break
|
| 102 |
else:
|
| 103 |
-
|
| 104 |
except requests.exceptions.RequestException as e:
|
| 105 |
-
|
| 106 |
except Exception as e:
|
| 107 |
-
|
| 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,17 +139,19 @@ def upload_db_to_hf(specific_file=None):
|
|
| 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 |
-
|
| 137 |
else:
|
| 138 |
-
|
| 139 |
except Exception as e:
|
| 140 |
-
|
| 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,15 +159,19 @@ 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,9 +181,11 @@ def save_data(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
|
|
|
|
|
|
|
| 172 |
except Exception as e:
|
| 173 |
-
|
| 174 |
|
| 175 |
def is_chat_active(env_id):
|
| 176 |
data = get_env_data(env_id)
|
|
@@ -251,11 +266,14 @@ def save_env_data(env_id, env_data):
|
|
| 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,6 +286,7 @@ 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 и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона."
|
|
@@ -299,6 +318,7 @@ def generate_ai_description_from_image(image_data, language):
|
|
| 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):
|
|
@@ -404,6 +424,7 @@ def generate_chat_response(message, chat_history_from_client, env_id):
|
|
| 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):
|
|
@@ -2055,11 +2076,13 @@ ADMIN_TEMPLATE = '''
|
|
| 2055 |
.item .description { font-size: 0.85rem; color: #999; max-height: 60px; overflow: hidden; text-overflow: ellipsis; }
|
| 2056 |
.item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
| 2057 |
.edit-form-container { margin-top: 15px; padding: 20px; background: #E0F2F1; border: 1px dashed #B2DFDB; border-radius: 6px; display: none; }
|
| 2058 |
-
details { background-color: #fdfdff; border
|
| 2059 |
-
details
|
|
|
|
|
|
|
|
|
|
| 2060 |
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); }
|
| 2061 |
details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
|
| 2062 |
-
details[open] > summary { border-bottom: 1px solid #e0e0e0; }
|
| 2063 |
details .form-content { padding: 20px; }
|
| 2064 |
.color-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
| 2065 |
.color-input-group input { flex-grow: 1; margin: 0; }
|
|
@@ -2121,7 +2144,8 @@ ADMIN_TEMPLATE = '''
|
|
| 2121 |
<p>Чат <strong>активен</strong>. Срок действия истекает: <strong class="{{ 'status-indicator expires-soon' if chat_status.expires_soon else '' }}">{{ chat_status.expires_date }}</strong></p>
|
| 2122 |
{% else %}
|
| 2123 |
<p>Чат <strong>неактивен</strong>. {% if chat_status.expires_date %}Срок действия истек: {{ chat_status.expires_date }}{% endif %}</p>
|
| 2124 |
-
<p
|
|
|
|
| 2125 |
{% endif %}
|
| 2126 |
</div>
|
| 2127 |
|
|
@@ -2183,20 +2207,22 @@ ADMIN_TEMPLATE = '''
|
|
| 2183 |
|
| 2184 |
|
| 2185 |
<div class="section">
|
| 2186 |
-
<
|
| 2187 |
-
|
| 2188 |
-
|
| 2189 |
-
{%
|
| 2190 |
-
|
| 2191 |
-
<
|
| 2192 |
-
|
| 2193 |
-
|
| 2194 |
-
|
| 2195 |
-
|
| 2196 |
-
|
| 2197 |
-
|
| 2198 |
-
|
| 2199 |
-
|
|
|
|
|
|
|
| 2200 |
</div>
|
| 2201 |
|
| 2202 |
<div class="flex-container">
|
|
@@ -2820,6 +2846,7 @@ def create_order(env_id):
|
|
| 2820 |
})
|
| 2821 |
total_price += price * quantity
|
| 2822 |
except (ValueError, TypeError) as e:
|
|
|
|
| 2823 |
return jsonify({"error": "Неверная цена или количество в товаре."}), 400
|
| 2824 |
|
| 2825 |
order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}"
|
|
@@ -2844,6 +2871,7 @@ def create_order(env_id):
|
|
| 2844 |
return jsonify({"order_id": order_id}), 201
|
| 2845 |
|
| 2846 |
except Exception as e:
|
|
|
|
| 2847 |
return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
|
| 2848 |
|
| 2849 |
@app.route('/<env_id>/order/<order_id>')
|
|
@@ -2931,8 +2959,8 @@ def admin(env_id):
|
|
| 2931 |
if old_avatar:
|
| 2932 |
try:
|
| 2933 |
api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
|
| 2934 |
-
except Exception:
|
| 2935 |
-
|
| 2936 |
|
| 2937 |
ext = os.path.splitext(avatar_file.filename)[1].lower()
|
| 2938 |
avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}"
|
|
@@ -2952,6 +2980,7 @@ def admin(env_id):
|
|
| 2952 |
flash("Аватар чата успешно обновлен.", 'success')
|
| 2953 |
except Exception as e:
|
| 2954 |
flash(f"Ошибка при загрузке аватара: {e}", 'error')
|
|
|
|
| 2955 |
else:
|
| 2956 |
flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
|
| 2957 |
|
|
@@ -3015,6 +3044,7 @@ def admin(env_id):
|
|
| 3015 |
uploaded_count += 1
|
| 3016 |
except Exception as e:
|
| 3017 |
flash(f"Ошибка при загрузке фото {photo.filename}.", 'error')
|
|
|
|
| 3018 |
if os.path.exists(temp_path):
|
| 3019 |
try: os.remove(temp_path)
|
| 3020 |
except OSError: pass
|
|
@@ -3024,7 +3054,7 @@ def admin(env_id):
|
|
| 3024 |
if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
|
| 3025 |
os.rmdir(uploads_dir)
|
| 3026 |
except OSError as e:
|
| 3027 |
-
|
| 3028 |
elif not HF_TOKEN_WRITE and photos_files and any(f.filename for f in photos_files):
|
| 3029 |
flash("HF_TOKEN (write) не настроен. Фотографии не были загружены.", "warning")
|
| 3030 |
|
|
@@ -3098,6 +3128,7 @@ def admin(env_id):
|
|
| 3098 |
uploaded_count += 1
|
| 3099 |
except Exception as e:
|
| 3100 |
flash(f"Ошибка при загрузке нового фото {photo.filename}.", 'error')
|
|
|
|
| 3101 |
if os.path.exists(temp_path):
|
| 3102 |
try: os.remove(temp_path)
|
| 3103 |
except OSError: pass
|
|
@@ -3105,7 +3136,7 @@ def admin(env_id):
|
|
| 3105 |
if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
|
| 3106 |
os.rmdir(uploads_dir)
|
| 3107 |
except OSError as e:
|
| 3108 |
-
|
| 3109 |
|
| 3110 |
if new_photos_list:
|
| 3111 |
old_photos = product_to_edit.get('photos', [])
|
|
@@ -3121,6 +3152,7 @@ def admin(env_id):
|
|
| 3121 |
)
|
| 3122 |
except Exception as e:
|
| 3123 |
flash("Не удалось удалить старые фотографии с сервера. Новые фото загружены.", "warning")
|
|
|
|
| 3124 |
product_to_edit['photos'] = new_photos_list
|
| 3125 |
flash("Фотографии товара успешно обновлены.", "success")
|
| 3126 |
elif uploaded_count == 0 and any(f.filename for f in photos_files):
|
|
@@ -3155,6 +3187,7 @@ def admin(env_id):
|
|
| 3155 |
)
|
| 3156 |
except Exception as e:
|
| 3157 |
flash(f"Не удалось удалить фото для товара '{product_name}' с сервера. Товар удален локально.", "warning")
|
|
|
|
| 3158 |
elif photos_to_delete and not HF_TOKEN_WRITE:
|
| 3159 |
flash(f"Товар '{product_name}' удален локально, но фото не удалены с сервера (токен не задан).", "warning")
|
| 3160 |
|
|
@@ -3168,6 +3201,7 @@ def admin(env_id):
|
|
| 3168 |
return redirect(url_for('admin', env_id=env_id))
|
| 3169 |
|
| 3170 |
except Exception as e:
|
|
|
|
| 3171 |
flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
|
| 3172 |
return redirect(url_for('admin', env_id=env_id))
|
| 3173 |
|
|
@@ -3228,6 +3262,7 @@ def handle_generate_description_ai():
|
|
| 3228 |
except ValueError as ve:
|
| 3229 |
return jsonify({"error": str(ve)}), 400
|
| 3230 |
except Exception as e:
|
|
|
|
| 3231 |
return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
|
| 3232 |
|
| 3233 |
@app.route('/<env_id>/chat_with_ai', methods=['POST'])
|
|
@@ -3261,6 +3296,7 @@ def handle_chat_with_ai(env_id):
|
|
| 3261 |
|
| 3262 |
return jsonify({"text": ai_response_text})
|
| 3263 |
except Exception as e:
|
|
|
|
| 3264 |
return jsonify({"error": f"Ошибка чата: {e}"}), 500
|
| 3265 |
|
| 3266 |
@app.route('/<env_id>/chat')
|
|
@@ -3302,6 +3338,7 @@ def force_upload(env_id):
|
|
| 3302 |
flash("Данные успешно загружены на Hugging Face.", 'success')
|
| 3303 |
except Exception as e:
|
| 3304 |
flash(f"Ошибка при загрузке на Hugging Face: {e}", 'error')
|
|
|
|
| 3305 |
return redirect(url_for('admin', env_id=env_id))
|
| 3306 |
|
| 3307 |
@app.route('/<env_id>/force_download', methods=['POST'])
|
|
@@ -3313,6 +3350,7 @@ def force_download(env_id):
|
|
| 3313 |
flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
|
| 3314 |
except Exception as e:
|
| 3315 |
flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
|
|
|
|
| 3316 |
return redirect(url_for('admin', env_id=env_id))
|
| 3317 |
|
| 3318 |
if __name__ == '__main__':
|
|
@@ -3323,8 +3361,9 @@ if __name__ == '__main__':
|
|
| 3323 |
if HF_TOKEN_WRITE:
|
| 3324 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 3325 |
backup_thread.start()
|
|
|
|
| 3326 |
else:
|
| 3327 |
-
|
| 3328 |
|
| 3329 |
port = int(os.environ.get('PORT', 7860))
|
| 3330 |
app.run(debug=False, host='0.0.0.0', port=port)
|
|
|
|
| 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 |
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 |
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 |
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 |
|
| 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 |
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 и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона."
|
|
|
|
| 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):
|
|
|
|
| 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):
|
|
|
|
| 2076 |
.item .description { font-size: 0.85rem; color: #999; max-height: 60px; overflow: hidden; text-overflow: ellipsis; }
|
| 2077 |
.item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
| 2078 |
.edit-form-container { margin-top: 15px; padding: 20px; background: #E0F2F1; border: 1px dashed #B2DFDB; border-radius: 6px; display: none; }
|
| 2079 |
+
details { background-color: #fdfdff; border-radius: 8px; margin-bottom: 10px; }
|
| 2080 |
+
details:not(.section) { border: 1px solid #e0e0e0; }
|
| 2081 |
+
details > summary { cursor: pointer; font-weight: 600; color: var(--bg-medium); display: block; padding: 15px; list-style: none; position: relative; }
|
| 2082 |
+
details:not(.section) > summary { border-bottom: 1px solid #e0e0e0; }
|
| 2083 |
+
details[open] > summary { border-bottom: 1px solid #e0e0e0; }
|
| 2084 |
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); }
|
| 2085 |
details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
|
|
|
|
| 2086 |
details .form-content { padding: 20px; }
|
| 2087 |
.color-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
| 2088 |
.color-input-group input { flex-grow: 1; margin: 0; }
|
|
|
|
| 2144 |
<p>Чат <strong>активен</strong>. Срок действия истекает: <strong class="{{ 'status-indicator expires-soon' if chat_status.expires_soon else '' }}">{{ chat_status.expires_date }}</strong></p>
|
| 2145 |
{% else %}
|
| 2146 |
<p>Чат <strong>неактивен</strong>. {% if chat_status.expires_date %}Срок действия истек: {{ chat_status.expires_date }}{% endif %}</p>
|
| 2147 |
+
<p>Для активации или продления чат-бота, пожалуйста, свяжитесь с администратором.</p>
|
| 2148 |
+
<a href="https://wa.me/77472479197" class="button add-button" target="_blank" style="margin-top: 10px;"><i class="fab fa-whatsapp"></i> Написать администратору</a>
|
| 2149 |
{% endif %}
|
| 2150 |
</div>
|
| 2151 |
|
|
|
|
| 2207 |
|
| 2208 |
|
| 2209 |
<div class="section">
|
| 2210 |
+
<details>
|
| 2211 |
+
<summary><h2><i class="fas fa-comments"></i> Диалоги с {{ settings.chat_name }}</h2></summary>
|
| 2212 |
+
<div class="item-list" style="margin-top: 20px;">
|
| 2213 |
+
{% if chats %}
|
| 2214 |
+
{% for chat_id, chat_data in chats.items()|sort(reverse=True) %}
|
| 2215 |
+
<div class="chat-log-item" onclick="viewChat('{{ chat_id }}')">
|
| 2216 |
+
<strong>ID Диалога:</strong> {{ chat_id }}
|
| 2217 |
+
<br>
|
| 2218 |
+
<small>Сообщений: {{ chat_data|length }} | Последнее сообщение: {{ chat_data[-1].timestamp if chat_data and 'timestamp' in chat_data[-1] else 'N/A' }}</small>
|
| 2219 |
+
</div>
|
| 2220 |
+
{% endfor %}
|
| 2221 |
+
{% else %}
|
| 2222 |
+
<p>Пока не было ни одного диалога.</p>
|
| 2223 |
+
{% endif %}
|
| 2224 |
+
</div>
|
| 2225 |
+
</details>
|
| 2226 |
</div>
|
| 2227 |
|
| 2228 |
<div class="flex-container">
|
|
|
|
| 2846 |
})
|
| 2847 |
total_price += price * quantity
|
| 2848 |
except (ValueError, TypeError) as e:
|
| 2849 |
+
logging.error(f"Invalid cart item format: {e}")
|
| 2850 |
return jsonify({"error": "Неверная цена или количество в товаре."}), 400
|
| 2851 |
|
| 2852 |
order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}"
|
|
|
|
| 2871 |
return jsonify({"order_id": order_id}), 201
|
| 2872 |
|
| 2873 |
except Exception as e:
|
| 2874 |
+
logging.error(f"Server error while creating order: {e}")
|
| 2875 |
return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
|
| 2876 |
|
| 2877 |
@app.route('/<env_id>/order/<order_id>')
|
|
|
|
| 2959 |
if old_avatar:
|
| 2960 |
try:
|
| 2961 |
api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
|
| 2962 |
+
except Exception as e:
|
| 2963 |
+
logging.warning(f"Could not delete old avatar {old_avatar}: {e}")
|
| 2964 |
|
| 2965 |
ext = os.path.splitext(avatar_file.filename)[1].lower()
|
| 2966 |
avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}"
|
|
|
|
| 2980 |
flash("Аватар чата успешно обновлен.", 'success')
|
| 2981 |
except Exception as e:
|
| 2982 |
flash(f"Ошибка при загрузке аватара: {e}", 'error')
|
| 2983 |
+
logging.error(f"Error uploading avatar: {e}")
|
| 2984 |
else:
|
| 2985 |
flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
|
| 2986 |
|
|
|
|
| 3044 |
uploaded_count += 1
|
| 3045 |
except Exception as e:
|
| 3046 |
flash(f"Ошибка при загрузке фото {photo.filename}.", 'error')
|
| 3047 |
+
logging.error(f"Error uploading photo {photo.filename}: {e}")
|
| 3048 |
if os.path.exists(temp_path):
|
| 3049 |
try: os.remove(temp_path)
|
| 3050 |
except OSError: pass
|
|
|
|
| 3054 |
if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
|
| 3055 |
os.rmdir(uploads_dir)
|
| 3056 |
except OSError as e:
|
| 3057 |
+
logging.warning(f"Could not remove temp upload directory: {e}")
|
| 3058 |
elif not HF_TOKEN_WRITE and photos_files and any(f.filename for f in photos_files):
|
| 3059 |
flash("HF_TOKEN (write) не настроен. Фотографии не были загружены.", "warning")
|
| 3060 |
|
|
|
|
| 3128 |
uploaded_count += 1
|
| 3129 |
except Exception as e:
|
| 3130 |
flash(f"Ошибка при загрузке нового фото {photo.filename}.", 'error')
|
| 3131 |
+
logging.error(f"Error uploading new photo {photo.filename}: {e}")
|
| 3132 |
if os.path.exists(temp_path):
|
| 3133 |
try: os.remove(temp_path)
|
| 3134 |
except OSError: pass
|
|
|
|
| 3136 |
if os.path.exists(uploads_dir) and not os.listdir(uploads_dir):
|
| 3137 |
os.rmdir(uploads_dir)
|
| 3138 |
except OSError as e:
|
| 3139 |
+
logging.warning(f"Could not remove temp upload directory: {e}")
|
| 3140 |
|
| 3141 |
if new_photos_list:
|
| 3142 |
old_photos = product_to_edit.get('photos', [])
|
|
|
|
| 3152 |
)
|
| 3153 |
except Exception as e:
|
| 3154 |
flash("Не удалось удалить старые фотографии с сервера. Новые фото загружены.", "warning")
|
| 3155 |
+
logging.warning(f"Could not delete old photos for product {product_to_edit['name']}: {e}")
|
| 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 |
+
logging.warning(f"Could not delete photos for product {product_name}: {e}")
|
| 3191 |
elif photos_to_delete and not HF_TOKEN_WRITE:
|
| 3192 |
flash(f"Товар '{product_name}' удален локально, но фото не удалены с сервера (токен не задан).", "warning")
|
| 3193 |
|
|
|
|
| 3201 |
return redirect(url_for('admin', env_id=env_id))
|
| 3202 |
|
| 3203 |
except Exception as e:
|
| 3204 |
+
logging.error(f"An internal error occurred in admin POST action '{action}': {e}")
|
| 3205 |
flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
|
| 3206 |
return redirect(url_for('admin', env_id=env_id))
|
| 3207 |
|
|
|
|
| 3262 |
except ValueError as ve:
|
| 3263 |
return jsonify({"error": str(ve)}), 400
|
| 3264 |
except Exception as e:
|
| 3265 |
+
logging.error(f"Internal server error during AI description generation: {e}")
|
| 3266 |
return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
|
| 3267 |
|
| 3268 |
@app.route('/<env_id>/chat_with_ai', methods=['POST'])
|
|
|
|
| 3296 |
|
| 3297 |
return jsonify({"text": ai_response_text})
|
| 3298 |
except Exception as e:
|
| 3299 |
+
logging.error(f"Error in chat handler: {e}")
|
| 3300 |
return jsonify({"error": f"Ошибка чата: {e}"}), 500
|
| 3301 |
|
| 3302 |
@app.route('/<env_id>/chat')
|
|
|
|
| 3338 |
flash("Данные успешно загружены на Hugging Face.", 'success')
|
| 3339 |
except Exception as e:
|
| 3340 |
flash(f"Ошибка при загрузке на Hugging Face: {e}", 'error')
|
| 3341 |
+
logging.error(f"Error during forced upload: {e}")
|
| 3342 |
return redirect(url_for('admin', env_id=env_id))
|
| 3343 |
|
| 3344 |
@app.route('/<env_id>/force_download', methods=['POST'])
|
|
|
|
| 3350 |
flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
|
| 3351 |
except Exception as e:
|
| 3352 |
flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
|
| 3353 |
+
logging.error(f"Error during forced download: {e}")
|
| 3354 |
return redirect(url_for('admin', env_id=env_id))
|
| 3355 |
|
| 3356 |
if __name__ == '__main__':
|
|
|
|
| 3361 |
if HF_TOKEN_WRITE:
|
| 3362 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 3363 |
backup_thread.start()
|
| 3364 |
+
logging.info("Periodic backup thread started.")
|
| 3365 |
else:
|
| 3366 |
+
logging.warning("HF_TOKEN_WRITE not set, periodic backups are disabled.")
|
| 3367 |
|
| 3368 |
port = int(os.environ.get('PORT', 7860))
|
| 3369 |
app.run(debug=False, host='0.0.0.0', port=port)
|