diff --git a/restoran_max_bot/ai.py b/restoran_max_bot/ai.py index 88c1300..5ef7278 100644 --- a/restoran_max_bot/ai.py +++ b/restoran_max_bot/ai.py @@ -10,18 +10,20 @@ from pathlib import Path # ---------- ЦВЕТА ДЛЯ КОНСОЛИ (НЕЙТРАЛЬНАЯ ПАЛИТРА) ---------- COLOR_RESET = "\033[0m" -COLOR_WHITE = "\033[97m" # белый (для заголовков) -COLOR_LIGHT_GRAY = "\033[37m" # светло-серый (для меню) -COLOR_GRAY = "\033[90m" # тёмно-серый (для информации) -COLOR_YELLOW = "\033[93m" # жёлтый (для ввода/предупреждений) -COLOR_GREEN = "\033[32m" # обычный зелёный (успех) -COLOR_RED = "\033[31m" # красный (ошибки) -COLOR_CYAN = "\033[36m" # голубой (акценты, откат) +COLOR_WHITE = "\033[97m" # белый (для заголовков) +COLOR_LIGHT_GRAY = "\033[37m" # светло-серый (для меню) +COLOR_GRAY = "\033[90m" # тёмно-серый (для информации) +COLOR_YELLOW = "\033[93m" # жёлтый (для ввода/предупреждений) +COLOR_GREEN = "\033[32m" # обычный зелёный (успех) +COLOR_RED = "\033[31m" # красный (ошибки) +COLOR_CYAN = "\033[36m" # голубой (акценты, откат) COLOR_BOLD = "\033[1m" + def color_text(text, color=COLOR_RESET): return f"{color}{text}{COLOR_RESET}" + # ---------- Функции сбора проекта ---------- def collect_project_files(root_dir, extensions=('.py', '.html'), 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}) return files_data + def build_tree(files_paths): tree = defaultdict(dict) for path in sorted(files_paths): @@ -70,8 +73,10 @@ def build_tree(files_paths): extension = ' ' if is_last else '│ ' lines.extend(render(node[key], prefix + extension)) return lines + return '\n'.join(render(tree)) + def build_prompt(main_prompt, files_data): parts = [] parts.append("=" * 80) @@ -154,7 +159,7 @@ def extract_json_from_text(text): end = text.rfind('}') if end == -1: return None - json_candidate = text[start:end+1] + json_candidate = text[start:end + 1] try: return json.loads(json_candidate) except json.JSONDecodeError: @@ -350,7 +355,8 @@ def rollback_last_apply(project_root, ai_response_dir): backup_files.sort(key=lambda x: x[1], reverse=True) latest_backup = backup_files[0][0] 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: reports.append(f"❌ Не найден бэкап для {file_path}, пропускаем (откат update)") errors = True @@ -367,7 +373,8 @@ def rollback_last_apply(project_root, ai_response_dir): latest_backup = backup_files[0][0] os.makedirs(os.path.dirname(abs_path), exist_ok=True) 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: reports.append(f"❌ Не найден бэкап для {file_path}, пропускаем (откат delete)") errors = True @@ -392,6 +399,7 @@ def get_downloads_folder(): return str(alt) return os.getcwd() + def find_latest_json_file(directory, prefix='deepseek_json_'): files = [] 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) return files[0][0] + def run_tests(project_root, test_path=None): reports = [] manage_path = os.path.join(project_root, 'manage.py') @@ -473,7 +482,8 @@ def main(): if mode == '1': 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: print(color_text("Промт не может быть пустым. Завершение.", COLOR_RED)) continue @@ -498,8 +508,11 @@ def main(): print(color_text(f"Всего собрано файлов: {len(files_data)}", COLOR_GRAY)) print(color_text("\nТеперь скопируйте содержимое этого файла и отправьте в DeepSeek.", COLOR_YELLOW)) print(color_text("DeepSeek вернёт ответ с описанием и JSON-блоком.", COLOR_YELLOW)) - print(color_text("Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads.", COLOR_YELLOW)) - print(color_text("Затем запустите этот скрипт в режиме 2 для автоматического применения.", COLOR_YELLOW)) + print(color_text( + "Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads.", + COLOR_YELLOW)) + print( + color_text("Затем запустите этот скрипт в режиме 2 для автоматического применения.", COLOR_YELLOW)) except Exception as e: print(color_text(f"❌ Ошибка при сохранении: {e}", COLOR_RED)) @@ -507,7 +520,8 @@ def main(): downloads_dir = get_downloads_folder() json_file = find_latest_json_file(downloads_dir) 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 print(color_text(f"📂 Найден файл: {json_file}", COLOR_GRAY)) @@ -521,7 +535,9 @@ def main(): data = extract_json_from_text(text) if data is None: - print(color_text("❌ Не удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON.", COLOR_RED)) + print(color_text( + "❌ Не удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON.", + COLOR_RED)) continue actions = data.get('actions') @@ -579,7 +595,9 @@ def main(): print(color_text(f"\n📄 Лог изменений записан в: {log_file}", COLOR_GRAY)) elif mode == '3': - test_path = input(color_text("Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): ", COLOR_YELLOW)).strip() + test_path = input( + color_text("Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): ", + COLOR_YELLOW)).strip() if test_path == '': test_path = None @@ -603,7 +621,8 @@ def main(): f"Результат: {'✅ УСПЕШНО' if success else '❌ ОШИБКА'}", 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("РЕЗУЛЬТАТ ТЕСТИРОВАНИЯ", COLOR_WHITE + COLOR_BOLD)) @@ -663,7 +682,8 @@ def main(): 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)) - choice = input(color_text("Выберите номер бэкапа для восстановления (или 0 для отмены): ", COLOR_YELLOW)).strip() + choice = input( + color_text("Выберите номер бэкапа для восстановления (или 0 для отмены): ", COLOR_YELLOW)).strip() if choice == '0': continue try: @@ -676,7 +696,9 @@ def main(): print(color_text("❌ Неверный ввод.", COLOR_RED)) 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() if confirm != 'да': print(color_text("Восстановление отменено.", COLOR_GRAY)) @@ -687,12 +709,14 @@ def main(): print(color_text(f"\n✅ Проект восстановлен из бэкапа: {os.path.basename(backup_path)}", COLOR_GREEN)) today = datetime.now().strftime('%Y-%m-%d') 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: print(color_text(f"❌ Ошибка при восстановлении: {e}", COLOR_RED)) else: print(color_text("❌ Неверный выбор. Пожалуйста, введите число от 0 до 5.", COLOR_RED)) + if __name__ == "__main__": main() diff --git a/restoran_max_bot/content_bot/urls.py b/restoran_max_bot/content_bot/urls.py index 8998a4d..5562ac7 100644 --- a/restoran_max_bot/content_bot/urls.py +++ b/restoran_max_bot/content_bot/urls.py @@ -1,9 +1,10 @@ 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 urlpatterns = [ path('default-load', api_default_load), path('ui/', ui_menu_view, name='ui_menu'), -] + path('landing//', landing_page, name='landing_page'), +] \ No newline at end of file diff --git a/restoran_max_bot/content_bot/views.py b/restoran_max_bot/content_bot/views.py index afc537b..9b15245 100644 --- a/restoran_max_bot/content_bot/views.py +++ b/restoran_max_bot/content_bot/views.py @@ -1,8 +1,9 @@ from django.http import JsonResponse from django.core.handlers.wsgi import WSGIRequest from django.views.decorators.csrf import csrf_exempt -from django.shortcuts import render -from max_bot.models import Client, ProductCategory, Product +from django.shortcuts import render, get_object_or_404 +from max_bot.models import Client, Qr, Integration, Partner +import json # Create your views here. @@ -14,6 +15,82 @@ def api_default_load(request: WSGIRequest): def ui_menu_view(request): """Отображает главную страницу MAX UI с двумя кнопками.""" - # В будущем здесь можно будет получать данные клиента из сессии или параметров запроса - # Сейчас просто рендерим шаблон 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)