From 514c08880097e99034e6de1ed2db26513d53908c Mon Sep 17 00:00:00 2001 From: pilot639 <60057546+pilot639@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:52:11 +0800 Subject: [PATCH] =?UTF-8?q?=D0=B2=D0=BD=D0=B5=D0=B4=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20deepseek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- restoran_max_bot/content_bot/views.py | 1 - restoran_max_bot/deepseek_bot/__init__.py | 0 restoran_max_bot/deepseek_bot/admin.py | 3 + restoran_max_bot/deepseek_bot/api_deepseek.py | 214 ++++++++++++++++++ restoran_max_bot/deepseek_bot/apps.py | 6 + .../deepseek_bot/system_prompt_base.txt | 42 ++++ restoran_max_bot/deepseek_bot/tests.py | 3 + restoran_max_bot/deepseek_bot/views.py | 0 restoran_max_bot/max_bot/max_api.py | 24 +- restoran_max_bot/max_bot/models.py | 33 +++ restoran_max_bot/max_bot/views.py | 23 +- .../restoran_max_bot/bot_message.py | 113 +++++++-- restoran_max_bot/restoran_max_bot/settings.py | 1 + 13 files changed, 429 insertions(+), 34 deletions(-) create mode 100644 restoran_max_bot/deepseek_bot/__init__.py create mode 100644 restoran_max_bot/deepseek_bot/admin.py create mode 100644 restoran_max_bot/deepseek_bot/api_deepseek.py create mode 100644 restoran_max_bot/deepseek_bot/apps.py create mode 100644 restoran_max_bot/deepseek_bot/system_prompt_base.txt create mode 100644 restoran_max_bot/deepseek_bot/tests.py create mode 100644 restoran_max_bot/deepseek_bot/views.py diff --git a/restoran_max_bot/content_bot/views.py b/restoran_max_bot/content_bot/views.py index 26c7545..6afd7d9 100644 --- a/restoran_max_bot/content_bot/views.py +++ b/restoran_max_bot/content_bot/views.py @@ -1,7 +1,6 @@ from django.http import JsonResponse from django.core.handlers.wsgi import WSGIRequest from django.views.decorators.csrf import csrf_exempt - from max_bot.models import Client, ProductCategory, Product diff --git a/restoran_max_bot/deepseek_bot/__init__.py b/restoran_max_bot/deepseek_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/restoran_max_bot/deepseek_bot/admin.py b/restoran_max_bot/deepseek_bot/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/restoran_max_bot/deepseek_bot/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/restoran_max_bot/deepseek_bot/api_deepseek.py b/restoran_max_bot/deepseek_bot/api_deepseek.py new file mode 100644 index 0000000..67cc53a --- /dev/null +++ b/restoran_max_bot/deepseek_bot/api_deepseek.py @@ -0,0 +1,214 @@ +import os +import json +from datetime import datetime +import requests +from typing import Dict, Any, List +from max_bot.models import Client, ProductCategory, Product, Qr + +# Хранилище контекстов (можно расширить для хранения состояния бронирования) +_contexts: Dict[str, List[Dict[str, str]]] = {} +# Кешируем базовый промт, чтобы не читать файл при каждом запросе +_BASE_PROMPT_CACHE = None + + +def _log_dialogue(session_id: str, role: str, content: str, log_file: str = None) -> None: + """ + Записывает сообщение диалога в файл лога. + Аргументы: + session_id: идентификатор сессии (например, chat_id) + role: 'SYSTEM', 'USER', 'ASSISTANT' + content: текст сообщения + log_file: путь к файлу лога (если None, берётся из настроек или по умолчанию) + """ + if log_file is None: + # Путь к файлу лога: можно задать в settings или использовать фиксированный путь + # Здесь используем папку logs рядом с файлом api_deepseek.py + base_dir = os.path.dirname(os.path.abspath(__file__)) + log_dir = os.path.join(base_dir, 'logs') + os.makedirs(log_dir, exist_ok=True) + log_file = os.path.join(log_dir, 'deepseek_dialogs.log') + + # Формируем строку лога с временной меткой + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # Для удобства чтения обрезаем content, если он слишком длинный? Нет, оставляем полным. + log_entry = f"[{timestamp}] [{role.upper()}] [{session_id}]: {content}\n" + + # Записываем в файл (атомарно, с блокировкой? Для простоты используем обычное добавление) + with open(log_file, 'a', encoding='utf-8') as f: + f.write(log_entry) + + # Если это системное сообщение или начало сессии, добавим разделитель для наглядности + if role.upper() == 'SYSTEM': + with open(log_file, 'a', encoding='utf-8') as f: + f.write('-' * 80 + '\n') + + # Записываем в файл (атомарно, с блокировкой? Для простоты используем обычное добавление) + with open(log_file, 'a', encoding='utf-8') as f: + f.write(log_entry) + + # Если это системное сообщение или начало сессии, добавим разделитель для наглядности + if role.upper() == 'SYSTEM': + with open(log_file, 'a', encoding='utf-8') as f: + f.write('-' * 80 + '\n') + + +def _load_base_prompt() -> str: + """ + Загружает базовый текст системного промта из файла. + Если файл не найден, возвращает встроенный fallback. + """ + global _BASE_PROMPT_CACHE + if _BASE_PROMPT_CACHE is not None: + return _BASE_PROMPT_CACHE + + # Определяем путь к файлу (он лежит рядом с api_deepseek.py) + base_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(base_dir, 'system_prompt_base.txt') + + try: + with open(file_path, 'r', encoding='utf-8') as f: + _BASE_PROMPT_CACHE = f.read() + except FileNotFoundError: + # Fallback: используем встроенный текст (минимальный) + _BASE_PROMPT_CACHE = ( + "Ты — ассистент ресторана. Отвечай на вопросы и помогай с бронированием. " + "Возвращай JSON с полями intent, message, entities, status.\n" + "Информация о ресторане и меню:\n" + ) + # Также можно залогировать ошибку + print("WARNING: system_prompt_base.txt not found, using fallback.") + + return _BASE_PROMPT_CACHE + + +def get_system_prompt(client: Client) -> str: + """ + Генерирует системный промпт на основе данных клиента. + Базовый текст загружается из файла, затем добавляются ресторан и меню. + """ + # 1. Загружаем базовый промт + prompt = _load_base_prompt() + + # 2. Добавляем информацию о ресторане + qr = Qr.objects.filter(client=client).first() + if qr: + restaurant_info = f""" + Название: {qr.title or 'Не указано'} + Бренд: {qr.brand or 'Не указан'} + Описание: {qr.description or 'Нет описания'} + Адрес: {qr.addr or 'Не указан'} + Телефон: {qr.phone or 'Не указан'} + Часы работы: {qr.worktime or 'Не указаны'} + Активность: {qr.activity or 'Не указана'} + """ + prompt += restaurant_info + else: + prompt += "\nИнформация о ресторане отсутствует.\n" + + # 3. Добавляем меню + categories = ProductCategory.objects.filter(client=client, status=True).order_by('ord') + if categories.exists(): + prompt += "\nМеню:\n" + for cat in categories: + prompt += f"\nКатегория: {cat.title}\n" + products = Product.objects.filter(client=client, up=cat.id, status=1) + if products.exists(): + for prod in products: + prompt += f" - {prod.title} — {prod.price} руб." + if prod.descr: + prompt += f" ({prod.descr})" + prompt += "\n" + else: + prompt += " (в этой категории пока нет блюд)\n" + else: + prompt += "\nМеню не загружено.\n" + + # 4. Добавляем финальную инструкцию (если она не в файле) + prompt += """ + При ответах на вопросы о меню используй только те блюда, которые перечислены выше. + Если клиент спрашивает о блюде, которого нет в списке, честно скажи об этом и предложи посмотреть другие позиции. + Всегда помни, что ты должен возвращать только JSON, без лишнего текста вне JSON. + """ + return prompt + + +def deepseek_chat_structured( + session_id: str, + api_token: str, + system_prompt: str, + user_query: str, + model: str = "deepseek-chat", + max_tokens: int = 1000, + temperature: float = 0.3, +) -> Dict[str, Any]: + """ + Отправляет запрос к DeepSeek и возвращает структурированный ответ (JSON). + """ + # Логируем системный промт только при создании новой сессии + if session_id not in _contexts: + _contexts[session_id] = [ + {"role": "system", "content": system_prompt} + ] + _log_dialogue(session_id, 'SYSTEM', system_prompt) + + # Логируем запрос пользователя + _log_dialogue(session_id, 'USER', user_query) + + _contexts[session_id].append({"role": "user", "content": user_query}) + + url = "https://api.deepseek.com/v1/chat/completions" + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + payload = { + "model": model, + "messages": _contexts[session_id], + "stream": False, + "max_tokens": max_tokens, + "temperature": temperature, + "response_format": {"type": "json_object"} + } + + try: + response = requests.post(url, headers=headers, json=payload, timeout=30) + response.raise_for_status() + data = response.json() + assistant_content = data["choices"][0]["message"]["content"] + + # Логируем ответ ассистента + _log_dialogue(session_id, 'ASSISTANT', assistant_content) + + _contexts[session_id].append({"role": "assistant", "content": assistant_content}) + + # Парсим JSON + try: + parsed = json.loads(assistant_content) + required = ["intent", "message", "entities", "status"] + if all(k in parsed for k in required): + return parsed + else: + return { + "intent": "general", + "message": assistant_content, + "entities": {}, + "status": "complete" + } + except json.JSONDecodeError: + return { + "intent": "general", + "message": assistant_content, + "entities": {}, + "status": "complete" + } + + except Exception as e: + error_msg = f"⚠️ Извините, произошла ошибка. Попробуйте позже." + # Логируем ошибку + _log_dialogue(session_id, 'ERROR', f"DeepSeek error: {e}") + return { + "intent": "general", + "message": error_msg, + "entities": {}, + "status": "complete" + } diff --git a/restoran_max_bot/deepseek_bot/apps.py b/restoran_max_bot/deepseek_bot/apps.py new file mode 100644 index 0000000..9dd6504 --- /dev/null +++ b/restoran_max_bot/deepseek_bot/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DeepseekBotConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'deepseek_bot' diff --git a/restoran_max_bot/deepseek_bot/system_prompt_base.txt b/restoran_max_bot/deepseek_bot/system_prompt_base.txt new file mode 100644 index 0000000..16401fd --- /dev/null +++ b/restoran_max_bot/deepseek_bot/system_prompt_base.txt @@ -0,0 +1,42 @@ +Ты — вежливый и компетентный ассистент ресторана. Твоя задача — помогать клиентам с вопросами о меню, акциях, бронировании столиков, а также организовывать связь с администратором. + +Ты должен возвращать ответ строго в формате JSON со следующими полями: +{ + "intent": "booking" | "general" | "contact_admin", + "message": "текст для пользователя", + "entities": { + "date": null | "ДД.ММ.ГГГГ", + "time": null | "ЧЧ:ММ", + "people": null | число, + "phone": null | номер_телефона, + "contact_method": null | "call" | "callback" | "message", + "question": null | "текст вопроса/проблемы", + "preferred_time": null | "время для обратной связи" + }, + "status": "need_more_info" | "complete" +} + +Правила для каждого намерения: + +1. **Бронирование столика (intent: "booking")**: + - Если клиент хочет забронировать столик, задавай последовательные вопросы: дата (ДД.ММ.ГГГГ), время (ЧЧ:ММ), количество человек, номер телефона (для связи). + - Когда все данные собраны, укажи status: "complete" и заполни entities (date, time, people, phone). + +2. **Общие вопросы (intent: "general")**: + - Отвечай на вопросы о меню, акциях, бонусах, часах работы и прочей информации, которая есть в предоставленных данных. + - Используй только ту информацию, которая передана в контексте (ресторан, меню). Если ответа нет, предложи обратиться к администратору. + +3. **Связь с администратором (intent: "contact_admin")**: + - Если клиент хочет поговорить с администратором, менеджером, или просит перезвонить (фразы вроде "свяжите меня с администратором", "хочу поговорить с менеджером", "позвоните мне" и т.п.), то начинай сбор необходимых данных: + - номер телефона клиента (обязательно); + - способ связи: "call" – позвонить сейчас, "callback" – обратный звонок в удобное время, "message" – написать в мессенджер или оставить сообщение; + - если выбран "callback" – уточни предпочтительное время (preferred_time); + - вопрос/проблему (question), чтобы администратор мог подготовиться. + - Когда все обязательные поля (phone, contact_method, question) собраны, укажи status: "complete". + +Важно: +- Если клиент не указал какую-то информацию, задавай уточняющий вопрос и возвращай status: "need_more_info". +- Не выдумывай данные, которых нет. +- Все ответы должны быть только в JSON, без постороннего текста. + +Информация о ресторане и меню: \ No newline at end of file diff --git a/restoran_max_bot/deepseek_bot/tests.py b/restoran_max_bot/deepseek_bot/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/restoran_max_bot/deepseek_bot/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/restoran_max_bot/deepseek_bot/views.py b/restoran_max_bot/deepseek_bot/views.py new file mode 100644 index 0000000..e69de29 diff --git a/restoran_max_bot/max_bot/max_api.py b/restoran_max_bot/max_bot/max_api.py index 5f4090b..1ee9a25 100644 --- a/restoran_max_bot/max_bot/max_api.py +++ b/restoran_max_bot/max_bot/max_api.py @@ -5,9 +5,11 @@ from max_bot.models import Client from restoran_max_bot.common import common_get_menu, common_get_data from restoran_max_bot.settings import DEBUG +url_max = "https://platform-api2.max.ru" + def maxbot_send_text_message(chat_id: str, max_token: str, message: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" payload = json.dumps({ "text": message @@ -25,7 +27,7 @@ def maxbot_send_text_message(chat_id: str, max_token: str, message: str): def maxbot_send_img_message(chat_id: str, max_token: str, message: str, img: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" payload = json.dumps({ "text": message, @@ -47,7 +49,7 @@ def maxbot_send_img_message(chat_id: str, max_token: str, message: str, img: str def maxbot_send_menu_button(client: Client, chat_id: str, max_token: str, message: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" menu = common_get_menu(client=client) buttons = [] button = [] @@ -94,7 +96,7 @@ def maxbot_send_menu_button(client: Client, chat_id: str, max_token: str, messag def maxbot_get_all_messages(chat_id: str, max_token: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" payload = {} headers = { @@ -108,7 +110,7 @@ def maxbot_get_all_messages(chat_id: str, max_token: str): def maxbot_del_messages(max_token: str, message_id: str): - url = f"https://platform-api.max.ru/messages?message_id={message_id}" + url = f"{url_max}/messages?message_id={message_id}" payload = {} headers = { @@ -132,7 +134,7 @@ def maxbot_delete_all_messages(chat_id: str, max_token: str): def maxbot_get_phone(chat_id: str, max_token: str, text: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" payload = json.dumps({ "text": text, "attachments": [ @@ -160,7 +162,7 @@ def maxbot_get_phone(chat_id: str, max_token: str, text: str): def maxbot_send_day_button(client: Client, chat_id: str, max_token: str, message: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" buttons = [] button = [] cnt = 0 @@ -204,7 +206,7 @@ def maxbot_send_month_button(client: Client, chat_id: str, max_token: str, messa 'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь' ] - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" buttons = [] button = [] cnt = 0 @@ -261,7 +263,7 @@ def maxbot_send_content(client: Client, chat_id: str, max_token: str, message: s def maxbot_send_feedback_button(client: Client, chat_id: str, max_token: str, message: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" buttons = [] button = [] cnt = 0 @@ -297,7 +299,7 @@ def maxbot_send_feedback_button(client: Client, chat_id: str, max_token: str, me def maxbot_send_booking_count_people_button(client: Client, chat_id: str, max_token: str, message: str): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" buttons = [] button = [] for dt in range(1, 6): @@ -338,7 +340,7 @@ def maxbot_send_booking_count_people_button(client: Client, chat_id: str, max_to def maxbot_send_link_button(text, title_button, link, chat_id, max_token): - url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + url = f"{url_max}/messages?chat_id={chat_id}" payload = json.dumps({ "text": text, "attachments": [ diff --git a/restoran_max_bot/max_bot/models.py b/restoran_max_bot/max_bot/models.py index 8dcf527..3d031c7 100644 --- a/restoran_max_bot/max_bot/models.py +++ b/restoran_max_bot/max_bot/models.py @@ -608,3 +608,36 @@ class BonusTransaction(models.Model): class Meta: db_table = 'lk_qr_bonus_transaction' + + +# описание ресторана +class Qr(models.Model): + objects = None + client = models.ForeignKey(Client, on_delete=models.PROTECT) + domain = models.CharField(max_length=100, null=True, blank=True) + keywords = models.CharField(max_length=500, null=True, blank=True) + brand = models.CharField(max_length=300, null=True, blank=True) + title = models.CharField(max_length=300, null=True, blank=True) + description = models.TextField(null=True, blank=True) # MEDIUMTEXT в MySQL + activity = models.CharField(max_length=100, null=True, blank=True) + thumb = models.CharField(max_length=300, null=True, blank=True) + slider = models.CharField(max_length=300, null=True, blank=True) + sections = models.TextField(null=True, blank=True) + addr = models.CharField(max_length=300, null=True, blank=True) + phone = models.CharField(max_length=50, null=True, blank=True) + worktime = models.CharField(max_length=100, null=True, blank=True) + links = models.TextField(null=True, blank=True) # MEDIUMTEXT + metrika = models.TextField(null=True, blank=True) # MEDIUMTEXT + status = models.IntegerField(null=True, blank=True) + dt = models.DateTimeField(auto_now_add=True, null=True, blank=True) # default current_timestamp + logo = models.CharField(max_length=255, null=True, blank=True) + feedback = models.CharField(max_length=255, null=True, blank=True) + type = models.CharField(max_length=30, null=True, blank=True) + lng = models.CharField(max_length=10, default='ru') + setting = models.CharField(max_length=255, null=True, blank=True) + theme = models.CharField(max_length=100, null=True, blank=True) + base_limit = models.IntegerField(null=True, blank=True) + vkpxl = models.CharField(max_length=100, null=True, blank=True) + + class Meta: + db_table = 'lk_qr' diff --git a/restoran_max_bot/max_bot/views.py b/restoran_max_bot/max_bot/views.py index ea6cf6b..331922a 100644 --- a/restoran_max_bot/max_bot/views.py +++ b/restoran_max_bot/max_bot/views.py @@ -42,19 +42,29 @@ def api_decorator(func): integration = Integration.objects.filter(client=client, partner=partner, status=True).first() if not integration: rt = {'success': False, 'error': 'not found max bot integration', 'data': ''} - return JsonResponse(rt, status=404) + return JsonResponse(rt, status=200) if not is_json(integration.setting): rt = {'success': False, 'error': 'not found max bot settings', 'data': ''} - return JsonResponse(rt, status=404) + return JsonResponse(rt, status=200) settings_max = json.loads(integration.setting) settings_max['token'] = integration.token + # проверка на интеграцию с максом + partner = Partner.objects.filter(slug='deepseek').first() + integration = Integration.objects.filter(client=client, partner=partner, status=True).first() + + if not integration: + settings_deepseek = {'token': None, 'deepseek_system_prompt': None} + else: + deepseek_system_prompt = json.loads(integration.setting).get('system_prompt', None) + settings_deepseek = {'token': integration.token, 'deepseek_system_prompt': deepseek_system_prompt} + kwargs['client'] = client kwargs['integration'] = integration kwargs['settings_max'] = settings_max - + kwargs['settings_deepseek'] = settings_deepseek return func(*args, **kwargs) return wrapper_api_decorator @@ -66,6 +76,7 @@ def api_start_max_v1(request, token, **kwargs): data = json.loads(request.body.decode('utf-8')) client = kwargs['client'] settings_max = kwargs['settings_max'] + settings_deepseek = kwargs['settings_deepseek'] if DEBUG: print(data) @@ -76,10 +87,12 @@ def api_start_max_v1(request, token, **kwargs): # получение сообщения из бота if data['update_type'] == 'message_created': - bot_message(client=client, message=data, settings=settings_max, message_type='message_created') + bot_message(client=client, message=data, settings_max=settings_max, settings_deepseek=settings_deepseek, + message_type='message_created') if data['update_type'] == 'message_callback': - bot_message(client=client, message=data, settings=settings_max, message_type='message_callback') + bot_message(client=client, message=data, settings_max=settings_max, settings_deepseek=settings_deepseek, + message_type='message_callback') # start - запуск бота if data['update_type'] == 'bot_started': diff --git a/restoran_max_bot/restoran_max_bot/bot_message.py b/restoran_max_bot/restoran_max_bot/bot_message.py index 2913b70..504aa38 100644 --- a/restoran_max_bot/restoran_max_bot/bot_message.py +++ b/restoran_max_bot/restoran_max_bot/bot_message.py @@ -1,4 +1,5 @@ import re +from deepseek_bot.api_deepseek import * from max_bot.max_api import * import qrcode from restoran_max_bot.bot_started import check_registration @@ -9,6 +10,15 @@ from restoran_max_bot.settings import STATICFILES_DIRS from restoran_max_bot.utils import has_key, is_json +# отправка сообщения в группу администраторв по бронированию +def send_booking_to_crm(chat_id, date, time, people, contact): + return True + + +def send_contact_to_admin(chat_id, phone, contact_method, question, preferred_time, contact): + return True + + # получение информации по интеграции с терминалом ikko, rkeeper def get_bonus(client: Client, phone: str, default_bonus: float, contact: Contact): integration = common_get_integration(client=client) @@ -76,7 +86,7 @@ def get_attachments(message: dict): return {'attachments_type': False, 'data': ''} -def bot_message(client: Client, message: dict, settings: dict, message_type): +def bot_message(client: Client, message: dict, settings_max: dict, settings_deepseek: dict, message_type): chat_id = message['message']['recipient']['chat_id'] attachments = get_attachments(message=message) @@ -86,7 +96,7 @@ def bot_message(client: Client, message: dict, settings: dict, message_type): phone=attachments['data']) message['message']['body']['text'] = 'system_registration' - contact = check_registration(client=client, chat_id=chat_id, message=message, settings=settings) + contact = check_registration(client=client, chat_id=chat_id, message=message, settings=settings_max) if not contact: return False @@ -103,20 +113,20 @@ def bot_message(client: Client, message: dict, settings: dict, message_type): img = qrcode.make(contact.phone) img.save(f"{STATICFILES_DIRS[0]}/client_qr/{contact.phone}.png") url = f"https://maxbot.telefon-ip.ru/static/client_qr/{contact.phone}.png" - maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, + maxbot_send_img_message(chat_id=chat_id, max_token=settings_max['token'], img=url, message='QR-код для начисления, списания бонусов.') bonus = get_bonus(client=client, phone=contact.phone, default_bonus=float(dt.price), contact=contact) - maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], + maxbot_send_text_message(chat_id=chat_id, max_token=settings_max['token'], message=f"💰 Ваш бонусный баланс: {bonus} руб") if dt.title == '##feedback##': - maxbot_send_feedback_button(client=client, message='👇', chat_id=chat_id, max_token=settings['token']) + maxbot_send_feedback_button(client=client, message='👇', chat_id=chat_id, max_token=settings_max['token']) return True if dt.title == '##booking##': maxbot_send_booking_count_people_button(client=client, message='👇', - chat_id=chat_id, max_token=settings['token']) + chat_id=chat_id, max_token=settings_max['token']) return True # для более сложных функций @@ -124,28 +134,97 @@ def bot_message(client: Client, message: dict, settings: dict, message_type): data = json.loads(dt.title) if 'type' in dt.title: if data['type'] == 'linkbutton': - maxbot_send_link_button(text=dt.descr, title_button=data['button'], link=dt.url, chat_id=chat_id, - max_token=settings['token']) + maxbot_send_link_button(text=dt.descr, title_button=data['button'], link=dt.url, + chat_id=chat_id, + max_token=settings_max['token']) if dt.img: url = f"https://cdn.telefon-ip.ru/{dt.img}?thumb=600" - maxbot_send_img_message(chat_id=chat_id, max_token=settings['token'], img=url, message=dt.descr) + maxbot_send_img_message(chat_id=chat_id, max_token=settings_max['token'], img=url, message=dt.descr) if '##' not in dt.title and not dt.img and not is_json(dt.title): - maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.descr) + maxbot_send_text_message(chat_id=chat_id, max_token=settings_max['token'], message=dt.descr) if dt.url and not is_json(dt.title): - maxbot_send_text_message(chat_id=chat_id, max_token=settings['token'], message=dt.url) + maxbot_send_text_message(chat_id=chat_id, max_token=settings_max['token'], message=dt.url) # отправка меню - maxbot_send_menu_button(chat_id=chat_id, max_token=settings['token'], message='👇 Выберите раздел', + maxbot_send_menu_button(chat_id=chat_id, max_token=settings_max['token'], message='👇 Выберите раздел', client=client) else: - if payload['payload_type'] == 'booking_count_people': - print('запрос даты') + # обработка сообщения через ИИ =========================== + if settings_deepseek.get('token', False): + text = message['message']['body']['text'] + # Получаем системный промт из настроек + system_prompt = settings_deepseek.get('deepseek_system_prompt', get_system_prompt(client)) + + # Отправляем запрос в DeepSeek + response = deepseek_chat_structured( + session_id=str(chat_id), + api_token=settings_deepseek.get('token'), + system_prompt=system_prompt, + user_query=text + ) + + intent = response.get('intent') + message_text = response.get('message', '') + entities = response.get('entities', {}) + status = response.get('status', 'complete') + + # Если общий вопрос — просто отвечаем + if intent == 'general': + maxbot_send_text_message(chat_id, settings_max['token'], message_text) + elif intent == 'booking': + if status == 'need_more_info': + # Отправляем уточняющий вопрос + maxbot_send_text_message(chat_id, settings_max['token'], message_text) + elif status == 'complete': + # Все данные собраны — передаём в CRM + date = entities.get('date') + time = entities.get('time') + people = entities.get('people') + phone = entities.get('phone') + if date and time and people: + # Вызываем функцию для отправки в CRM + success = send_booking_to_crm(chat_id, date, time, people, phone) + if success: + confirm_msg = f"✅ Бронирование на {date} в {time} на {people} чел. принято! " \ + f"Администратор свяжется с вами для уточнения вопросов" + else: + confirm_msg = "❌ Не удалось забронировать. Попробуйте позже." + maxbot_send_text_message(chat_id, settings_max['token'], confirm_msg) + else: + maxbot_send_text_message(chat_id, settings_max['token'], "⚠️ Не хватает данных для бронирования.") + else: + maxbot_send_text_message(chat_id, settings_max['token'], message_text) + elif intent == 'contact_admin': + if status == 'need_more_info': + # Отправляем уточняющий вопрос + maxbot_send_text_message(chat_id, settings_max['token'], message_text) + elif status == 'complete': + # Все данные собраны — передаём администратору + phone = entities.get('phone') + contact_method = entities.get('contact_method') + question = entities.get('question', '') + preferred_time = entities.get('preferred_time', '') + if phone and contact_method: + # Вызываем функцию отправки уведомления администратору + success = send_contact_to_admin(chat_id, phone, contact_method, question, preferred_time, contact) + if success: + confirm_msg = f"✅ Ваш запрос передан администратору. Способ связи: {contact_method}. Скоро с вами свяжутся." + else: + confirm_msg = "❌ Не удалось отправить запрос. Попробуйте позже." + maxbot_send_text_message(chat_id, settings_max['token'], confirm_msg) + else: + maxbot_send_text_message(chat_id, settings_max['token'], + "⚠️ Не хватает данных для связи с администратором.") + else: + maxbot_send_text_message(chat_id, settings_max['token'], message_text) + else: + maxbot_send_text_message(chat_id, settings_max['token'], message_text) - # отправка меню - maxbot_send_menu_button(chat_id=chat_id, max_token=settings['token'], message='👇 Выберите раздел', - client=client) + # После ответа отправляем меню (если нужно) + maxbot_send_menu_button(chat_id=chat_id, max_token=settings_max['token'], + message='👇 Выберите раздел', client=client) return True diff --git a/restoran_max_bot/restoran_max_bot/settings.py b/restoran_max_bot/restoran_max_bot/settings.py index 1a41419..5ff9dcc 100644 --- a/restoran_max_bot/restoran_max_bot/settings.py +++ b/restoran_max_bot/restoran_max_bot/settings.py @@ -35,6 +35,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'max_bot', + 'deepseek_bot', ] MIDDLEWARE = [