✅ Обновлён: content_bot/views.py — Добавлено представление landing_page в content_bot.views

✅ Обновлён: content_bot/urls.py — Добавлен URL-маршрут для минилендинга с параметром client_id
✅ Создан: templates/max_bot/landing.html — Создан шаблон минилендинга с логотипом, названием, описанием и двумя кнопками с иконками Font Awesome
main
pilot 1 month ago
parent dc74bdc185
commit 291ce655cb

@ -19,9 +19,11 @@ COLOR_RED = "\033[31m" # красный (ошибки)
COLOR_CYAN = "\033[36m" # голубой (акценты, откат) COLOR_CYAN = "\033[36m" # голубой (акценты, откат)
COLOR_BOLD = "\033[1m" COLOR_BOLD = "\033[1m"
def color_text(text, color=COLOR_RESET): def color_text(text, color=COLOR_RESET):
return f"{color}{text}{COLOR_RESET}" return f"{color}{text}{COLOR_RESET}"
# ---------- Функции сбора проекта ---------- # ---------- Функции сбора проекта ----------
def collect_project_files(root_dir, extensions=('.py', '.html'), def collect_project_files(root_dir, extensions=('.py', '.html'),
exclude_dirs=('venv', '__pycache__', '.git', 'env', 'node_modules')): exclude_dirs=('venv', '__pycache__', '.git', 'env', 'node_modules')):
@ -47,6 +49,7 @@ def collect_project_files(root_dir, extensions=('.py', '.html'),
files_data.append({'path': rel_path, 'content': content}) files_data.append({'path': rel_path, 'content': content})
return files_data return files_data
def build_tree(files_paths): def build_tree(files_paths):
tree = defaultdict(dict) tree = defaultdict(dict)
for path in sorted(files_paths): for path in sorted(files_paths):
@ -70,8 +73,10 @@ def build_tree(files_paths):
extension = ' ' if is_last else '│ ' extension = ' ' if is_last else '│ '
lines.extend(render(node[key], prefix + extension)) lines.extend(render(node[key], prefix + extension))
return lines return lines
return '\n'.join(render(tree)) return '\n'.join(render(tree))
def build_prompt(main_prompt, files_data): def build_prompt(main_prompt, files_data):
parts = [] parts = []
parts.append("=" * 80) parts.append("=" * 80)
@ -350,7 +355,8 @@ def rollback_last_apply(project_root, ai_response_dir):
backup_files.sort(key=lambda x: x[1], reverse=True) backup_files.sort(key=lambda x: x[1], reverse=True)
latest_backup = backup_files[0][0] latest_backup = backup_files[0][0]
shutil.copy2(latest_backup, abs_path) shutil.copy2(latest_backup, abs_path)
reports.append(f"♻️ Восстановлен (откат update): {file_path} — из {os.path.basename(latest_backup)}") reports.append(
f"♻️ Восстановлен (откат update): {file_path} — из {os.path.basename(latest_backup)}")
else: else:
reports.append(f"❌ Не найден бэкап для {file_path}, пропускаем (откат update)") reports.append(f"❌ Не найден бэкап для {file_path}, пропускаем (откат update)")
errors = True errors = True
@ -367,7 +373,8 @@ def rollback_last_apply(project_root, ai_response_dir):
latest_backup = backup_files[0][0] latest_backup = backup_files[0][0]
os.makedirs(os.path.dirname(abs_path), exist_ok=True) os.makedirs(os.path.dirname(abs_path), exist_ok=True)
shutil.copy2(latest_backup, abs_path) shutil.copy2(latest_backup, abs_path)
reports.append(f"♻️ Восстановлен (откат delete): {file_path} — из {os.path.basename(latest_backup)}") reports.append(
f"♻️ Восстановлен (откат delete): {file_path} — из {os.path.basename(latest_backup)}")
else: else:
reports.append(f"❌ Не найден бэкап для {file_path}, пропускаем (откат delete)") reports.append(f"❌ Не найден бэкап для {file_path}, пропускаем (откат delete)")
errors = True errors = True
@ -392,6 +399,7 @@ def get_downloads_folder():
return str(alt) return str(alt)
return os.getcwd() return os.getcwd()
def find_latest_json_file(directory, prefix='deepseek_json_'): def find_latest_json_file(directory, prefix='deepseek_json_'):
files = [] files = []
for f in os.listdir(directory): for f in os.listdir(directory):
@ -404,6 +412,7 @@ def find_latest_json_file(directory, prefix='deepseek_json_'):
files.sort(key=lambda x: x[1], reverse=True) files.sort(key=lambda x: x[1], reverse=True)
return files[0][0] return files[0][0]
def run_tests(project_root, test_path=None): def run_tests(project_root, test_path=None):
reports = [] reports = []
manage_path = os.path.join(project_root, 'manage.py') manage_path = os.path.join(project_root, 'manage.py')
@ -473,7 +482,8 @@ def main():
if mode == '1': if mode == '1':
print(color_text("\nСбор информации о проекте для отправки в DeepSeek.", COLOR_LIGHT_GRAY)) print(color_text("\nСбор информации о проекте для отправки в DeepSeek.", COLOR_LIGHT_GRAY))
main_prompt = input(color_text("Введите ваш промт (одна строка, Enter для завершения): ", COLOR_YELLOW)).strip() main_prompt = input(
color_text("Введите ваш промт (одна строка, Enter для завершения): ", COLOR_YELLOW)).strip()
if not main_prompt: if not main_prompt:
print(color_text("Промт не может быть пустым. Завершение.", COLOR_RED)) print(color_text("Промт не может быть пустым. Завершение.", COLOR_RED))
continue continue
@ -498,8 +508,11 @@ def main():
print(color_text(f"Всего собрано файлов: {len(files_data)}", COLOR_GRAY)) print(color_text(f"Всего собрано файлов: {len(files_data)}", COLOR_GRAY))
print(color_text("\nТеперь скопируйте содержимое этого файла и отправьте в DeepSeek.", COLOR_YELLOW)) print(color_text("\nТеперь скопируйте содержимое этого файла и отправьте в DeepSeek.", COLOR_YELLOW))
print(color_text("DeepSeek вернёт ответ с описанием и JSON-блоком.", COLOR_YELLOW)) print(color_text("DeepSeek вернёт ответ с описанием и JSON-блоком.", COLOR_YELLOW))
print(color_text("Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads.", COLOR_YELLOW)) print(color_text(
print(color_text("Затем запустите этот скрипт в режиме 2 для автоматического применения.", COLOR_YELLOW)) "Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads.",
COLOR_YELLOW))
print(
color_text("Затем запустите этот скрипт в режиме 2 для автоматического применения.", COLOR_YELLOW))
except Exception as e: except Exception as e:
print(color_text(f"❌ Ошибка при сохранении: {e}", COLOR_RED)) print(color_text(f"❌ Ошибка при сохранении: {e}", COLOR_RED))
@ -507,7 +520,8 @@ def main():
downloads_dir = get_downloads_folder() downloads_dir = get_downloads_folder()
json_file = find_latest_json_file(downloads_dir) json_file = find_latest_json_file(downloads_dir)
if json_file is None: if json_file is None:
print(color_text(f"❌ Не найден JSON-файл с префиксом 'deepseek_json_' в папке {downloads_dir}", COLOR_RED)) print(color_text(f"❌ Не найден JSON-файл с префиксом 'deepseek_json_' в папке {downloads_dir}",
COLOR_RED))
continue continue
print(color_text(f"📂 Найден файл: {json_file}", COLOR_GRAY)) print(color_text(f"📂 Найден файл: {json_file}", COLOR_GRAY))
@ -521,7 +535,9 @@ def main():
data = extract_json_from_text(text) data = extract_json_from_text(text)
if data is None: if data is None:
print(color_text("❌ Не удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON.", COLOR_RED)) print(color_text(
"❌ Не удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON.",
COLOR_RED))
continue continue
actions = data.get('actions') actions = data.get('actions')
@ -579,7 +595,9 @@ def main():
print(color_text(f"\n📄 Лог изменений записан в: {log_file}", COLOR_GRAY)) print(color_text(f"\n📄 Лог изменений записан в: {log_file}", COLOR_GRAY))
elif mode == '3': elif mode == '3':
test_path = input(color_text("Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): ", COLOR_YELLOW)).strip() test_path = input(
color_text("Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): ",
COLOR_YELLOW)).strip()
if test_path == '': if test_path == '':
test_path = None test_path = None
@ -603,7 +621,8 @@ def main():
f"Результат: {'✅ УСПЕШНО' if success else '❌ ОШИБКА'}", f"Результат: {'✅ УСПЕШНО' if success else '❌ ОШИБКА'}",
f"Полный отчёт сохранён в: {test_report_file}" f"Полный отчёт сохранён в: {test_report_file}"
] ]
write_log_entry(log_file, 'TEST', brief_lines, extra_info=f"Путь: {test_path if test_path else 'все тесты'}") write_log_entry(log_file, 'TEST', brief_lines,
extra_info=f"Путь: {test_path if test_path else 'все тесты'}")
print(color_text("\n" + "=" * 60, COLOR_WHITE + COLOR_BOLD)) print(color_text("\n" + "=" * 60, COLOR_WHITE + COLOR_BOLD))
print(color_text("РЕЗУЛЬТАТ ТЕСТИРОВАНИЯ", COLOR_WHITE + COLOR_BOLD)) print(color_text("РЕЗУЛЬТАТ ТЕСТИРОВАНИЯ", COLOR_WHITE + COLOR_BOLD))
@ -663,7 +682,8 @@ def main():
for idx, (path, dt, name) in enumerate(backups, 1): for idx, (path, dt, name) in enumerate(backups, 1):
print(color_text(f" {idx}. {name} ({dt.strftime('%Y-%m-%d %H:%M:%S')})", COLOR_GRAY)) print(color_text(f" {idx}. {name} ({dt.strftime('%Y-%m-%d %H:%M:%S')})", COLOR_GRAY))
choice = input(color_text("Выберите номер бэкапа для восстановления (или 0 для отмены): ", COLOR_YELLOW)).strip() choice = input(
color_text("Выберите номер бэкапа для восстановления (или 0 для отмены): ", COLOR_YELLOW)).strip()
if choice == '0': if choice == '0':
continue continue
try: try:
@ -676,7 +696,9 @@ def main():
print(color_text("❌ Неверный ввод.", COLOR_RED)) print(color_text("❌ Неверный ввод.", COLOR_RED))
continue continue
print(color_text(f"\n⚠️ Восстановление из бэкапа {os.path.basename(backup_path)} полностью заменит текущий проект.", COLOR_YELLOW)) print(color_text(
f"\n⚠️ Восстановление из бэкапа {os.path.basename(backup_path)} полностью заменит текущий проект.",
COLOR_YELLOW))
confirm = input(color_text("Вы уверены? (введите 'да' для подтверждения): ", COLOR_YELLOW)).strip().lower() confirm = input(color_text("Вы уверены? (введите 'да' для подтверждения): ", COLOR_YELLOW)).strip().lower()
if confirm != 'да': if confirm != 'да':
print(color_text("Восстановление отменено.", COLOR_GRAY)) print(color_text("Восстановление отменено.", COLOR_GRAY))
@ -687,12 +709,14 @@ def main():
print(color_text(f"\n✅ Проект восстановлен из бэкапа: {os.path.basename(backup_path)}", COLOR_GREEN)) print(color_text(f"\n✅ Проект восстановлен из бэкапа: {os.path.basename(backup_path)}", COLOR_GREEN))
today = datetime.now().strftime('%Y-%m-%d') today = datetime.now().strftime('%Y-%m-%d')
log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt') log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt')
write_log_entry(log_file, 'RESTORE_FULL', [f"Восстановлен проект из {os.path.basename(backup_path)}"], extra_info="Полное восстановление") write_log_entry(log_file, 'RESTORE_FULL', [f"Восстановлен проект из {os.path.basename(backup_path)}"],
extra_info="Полное восстановление")
except Exception as e: except Exception as e:
print(color_text(f"❌ Ошибка при восстановлении: {e}", COLOR_RED)) print(color_text(f"❌ Ошибка при восстановлении: {e}", COLOR_RED))
else: else:
print(color_text("❌ Неверный выбор. Пожалуйста, введите число от 0 до 5.", COLOR_RED)) print(color_text("❌ Неверный выбор. Пожалуйста, введите число от 0 до 5.", COLOR_RED))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -1,9 +1,10 @@
from django.urls import path from django.urls import path
from content_bot.views import api_default_load, ui_menu_view from content_bot.views import api_default_load, ui_menu_view, landing_page
from max_bot.views import api_start_max_v1, iiko_webhook, iiko_send_message from max_bot.views import api_start_max_v1, iiko_webhook, iiko_send_message
urlpatterns = [ urlpatterns = [
path('default-load', api_default_load), path('default-load', api_default_load),
path('ui/', ui_menu_view, name='ui_menu'), path('ui/', ui_menu_view, name='ui_menu'),
path('landing/<int:client_id>/', landing_page, name='landing_page'),
] ]

@ -1,8 +1,9 @@
from django.http import JsonResponse from django.http import JsonResponse
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render from django.shortcuts import render, get_object_or_404
from max_bot.models import Client, ProductCategory, Product from max_bot.models import Client, Qr, Integration, Partner
import json
# Create your views here. # Create your views here.
@ -14,6 +15,82 @@ def api_default_load(request: WSGIRequest):
def ui_menu_view(request): def ui_menu_view(request):
"""Отображает главную страницу MAX UI с двумя кнопками.""" """Отображает главную страницу MAX UI с двумя кнопками."""
# В будущем здесь можно будет получать данные клиента из сессии или параметров запроса
# Сейчас просто рендерим шаблон
return render(request, 'max_bot/ui_menu.html') return render(request, 'max_bot/ui_menu.html')
def landing_page(request, client_id):
"""
Минилендинг для мобильного телефона.
Отображает логотип, название, описание заведения и две кнопки:
- Max Bot
- Telegram Bot
"""
# Получаем клиента
client = get_object_or_404(Client, id=client_id, status=True)
# Получаем данные из lk_qr
qr = Qr.objects.filter(client=client).first()
# Данные для шаблона
context = {
'client_id': client.id,
'brand': qr.brand if qr and qr.brand else client.brand or 'Ресторан',
'logo': qr.logo if qr and qr.logo else client.logo or '',
'description': qr.description if qr and qr.description else '',
}
# Ищем интеграции
max_bot_url = None
telegram_url = None
integrations = Integration.objects.filter(client=client, status=True)
for integration in integrations:
if not integration.setting:
continue
try:
settings = json.loads(integration.setting)
# Max Bot
if integration.partner.slug == 'max_bot':
if 'url' in settings:
max_bot_url = settings['url']
# Telegram
if integration.partner.slug == 'telegram':
if 'url_telegram' in settings:
telegram_url = settings['url_telegram']
except json.JSONDecodeError:
continue
# Если не нашли по партнеру, пробуем по ключам
if not max_bot_url:
for integration in integrations:
if not integration.setting:
continue
try:
settings = json.loads(integration.setting)
if 'url' in settings and 'max' in settings['url'].lower():
max_bot_url = settings['url']
break
except json.JSONDecodeError:
continue
if not telegram_url:
for integration in integrations:
if not integration.setting:
continue
try:
settings = json.loads(integration.setting)
if 'url_telegram' in settings:
telegram_url = settings['url_telegram']
break
except json.JSONDecodeError:
continue
context['max_bot_url'] = max_bot_url
context['telegram_url'] = telegram_url
return render(request, 'max_bot/landing.html', context)

Loading…
Cancel
Save