# -*- coding: utf-8 -*- """ Модуль генерации документа по шаблону. Заполняет плейсхолдеры {{...}} на основе данных из базы знаний. АДАПТАЦИЯ: теперь функция принимает промпт и конфиг как аргументы. """ import re import asyncio import os import logging import shutil from docx import Document from core.functions.file_processor import send_file_to_user logger = logging.getLogger(__name__) async def generate_document_from_template( bot, msg, room_jid, template_path, template_name, user_jid, giga, kb, config, prompt_text, temperature=0.1, allow_public=False ): """ Фоновая задача: парсит шаблон, ищет плейсхолдеры {{...}}, для каждого делает контекстный поиск в БЗ комнаты, генерирует значение через LLM, заменяет в копии шаблона и отправляет готовый файл в комнату. Аргументы: bot: экземпляр бота msg: объект XMPP-сообщения room_jid: JID комнаты template_path: путь к шаблону template_name: имя шаблона user_jid: JID пользователя giga: клиент GigaChat kb: сервис базы знаний config: объект конфигурации prompt_text: содержимое промпта для генерации temperature: температура генерации allow_public: разрешено ли использовать общие знания """ logger.info(f"🚀 [ГЕНЕРАЦИЯ] Запущена для шаблона '{template_name}' в комнате {room_jid}") try: doc = Document(template_path) # Собираем все плейсхолдеры и их контекст placeholders_info = {} for paragraph in doc.paragraphs: for match in re.finditer(r'\{\{([^}]+)\}\}', paragraph.text): ph = match.group(1) if ph not in placeholders_info: placeholders_info[ph] = paragraph.text for table in doc.tables: for row in table.rows: for cell in row.cells: for match in re.finditer(r'\{\{([^}]+)\}\}', cell.text): ph = match.group(1) if ph not in placeholders_info: placeholders_info[ph] = cell.text placeholders = set(placeholders_info.keys()) logger.info(f"🔍 [ГЕНЕРАЦИЯ] Найдено плейсхолдеров: {len(placeholders)}") if not placeholders: reply = msg.reply(f"❌ В шаблоне '{template_name}' нет плейсхолдеров {{...}}.") if reply: reply.send() return generated = {} for ph, surrounding in placeholders_info.items(): logger.info(f"📖 [ГЕНЕРАЦИЯ] Обработка плейсхолдера '{ph}'") # Формируем поисковый запрос search_query = f"{ph} {surrounding[:100]}" context = await kb.find_relevant_info(search_query, user_jid, room_jid, top_k=20) if not context and allow_public: logger.info(f"⚠️ [ГЕНЕРАЦИЯ] Контекст для '{ph}' не найден, используем общие знания") prompt = f"Ты — эксперт. Ответь на вопрос: {ph}. Дай краткое значение, без пояснений." try: response = await giga.chat( history=[], query=prompt, system_prompt=None, file_id=None, temperature=temperature ) generated[ph] = response.strip() continue except Exception as e: logger.error(f"❌ [ГЕНЕРАЦИЯ] Ошибка LLM для '{ph}': {e}") generated[ph] = "[ошибка]" continue if not context: logger.warning(f"⚠️ [ГЕНЕРАЦИЯ] Нет контекста для '{ph}', и общие знания запрещены") generated[ph] = "не определено" continue # Генерация значения на основе контекста prompt = prompt_text.format(ph=ph, context=context) try: response = await giga.chat( history=[], query=prompt, system_prompt=None, file_id=None, temperature=temperature ) cleaned = re.sub(r'^```\w*\n?|```$', '', response.strip()) generated[ph] = cleaned logger.info(f"✅ [ГЕНЕРАЦИЯ] Значение для '{ph}': {generated[ph][:100]}") except Exception as e: logger.error(f"❌ [ГЕНЕРАЦИЯ] Ошибка LLM для '{ph}': {e}", exc_info=True) generated[ph] = "[ошибка]" # Замена плейсхолдеров в копии документа temp_dir = config.temp_dir new_path = temp_dir / f"generated_{template_name}_{int(asyncio.get_event_loop().time())}.docx" shutil.copy2(template_path, new_path) new_doc = Document(new_path) def replace_in_paragraph(paragraph, replacements): full_text = ''.join(run.text for run in paragraph.runs) modified = False for key, value in replacements.items(): placeholder = f"{{{{{key}}}}}" if placeholder in full_text: for run in paragraph.runs: if placeholder in run.text: run.text = run.text.replace(placeholder, value) modified = True if not modified: for key, value in replacements.items(): placeholder = f"{{{{{key}}}}}" if placeholder in paragraph.text: paragraph.text = paragraph.text.replace(placeholder, value) def walk_document(doc_obj, replacements): for paragraph in doc_obj.paragraphs: replace_in_paragraph(paragraph, replacements) for table in doc_obj.tables: for row in table.rows: for cell in row.cells: for paragraph in cell.paragraphs: replace_in_paragraph(paragraph, replacements) walk_document(new_doc, generated) new_doc.save(new_path) await send_file_to_user(bot, msg, str(new_path), prefix=f"📄 Сгенерированный документ по шаблону '{template_name}':", suffix="generated") try: os.unlink(new_path) except Exception as e: logger.warning(f"Не удалось удалить временный файл {new_path}: {e}") logger.info(f"🏁 [ГЕНЕРАЦИЯ] Завершено для шаблона '{template_name}'") except Exception as e: logger.exception(f"💥 [ГЕНЕРАЦИЯ] Критическая ошибка: {e}") if msg: reply = msg.reply(f"❌ Ошибка при генерации документа: {e}") if reply: reply.send()