Files
fckbot/rag/functions/file_processor.py
Markov Andrey 63900feece Update 86 files
- /rag/commands/expert.py
- /core/services/__init__.py
- /core/services/embedding_service.py
- /core/services/file_service.py
- /core/services/giga_client.py
- /core/services/kb_service.py
- /core/services/postgres_service.py
- /core/services/qdrant_service.py
- /core/services/reranker_service.py
- /core/functions/__init__.py
- /core/functions/check_consistency.py
- /core/functions/check_spelling.py
- /core/functions/critique_answer.py
- /core/functions/expand_query.py
- /core/functions/extract_metrics.py
- /core/functions/file_processor.py
- /core/functions/generate_document.py
- /core/functions/intent_classify.py
- /core/functions/rerank_context.py
- /core/functions/summarize_document.py
- /core/utils/__init__.py
- /core/utils/arg_parser.py
- /core/utils/config_loader.py
- /core/utils/layout_converter.py
- /core/utils/logger.py
- /core/utils/text_utils.py
- /core/utils/web_utils.py
- /bots/commands/__init__.py
- /bots/commands/base.py
- /bots/commands/create.py
- /core/commands/global_remove.py
- /core/commands/help.py
- /core/commands/info.py
- /core/commands/kb.py
- /core/commands/learn.py
- /core/commands/other.py
- /core/commands/registry.py
- /core/commands/stats.py
- /core/commands/template.py
- /core/handlers/__init__.py
- /core/handlers/message_handler.py
- /core/workers/__init__.py
- /core/workers/indexing_worker.py
- /core/xmpp/__init__.py
- /core/xmpp/client.py
- /bots/commands/global_remove.py
- /bots/commands/help.py
- /bots/commands/info.py
- /bots/commands/kb.py
- /bots/commands/registry.py
- /bots/commands/other.py
- /bots/commands/template.py
- /bots/commands/learn.py
- /bots/commands/stats.py
- /bots/handlers/message_handler.py
- /bots/handlers/__init__.py
- /bots/workers/__init__.py
- /bots/workers/indexing_worker.py
- /bots/xmpp/__init__.py
- /bots/xmpp/client.py
- /rag/services/qdrant_service.py
- /rag/services/giga_client.py
- /rag/services/embedding_service.py
- /rag/services/reranker_service.py
- /rag/services/__init__.py
- /rag/services/file_service.py
- /rag/services/postgres_service.py
- /rag/services/kb_service.py
- /rag/functions/check_spelling.py
- /rag/functions/__init__.py
- /rag/functions/extract_metrics.py
- /rag/functions/generate_document.py
- /rag/functions/expand_query.py
- /rag/functions/critique_answer.py
- /rag/functions/file_processor.py
- /rag/functions/check_consistency.py
- /rag/functions/summarize_document.py
- /rag/functions/rerank_context.py
- /rag/functions/intent_classify.py
- /rag/utils/__init__.py
- /rag/utils/layout_converter.py
- /rag/utils/text_utils.py
- /rag/utils/arg_parser.py
- /rag/utils/config_loader.py
- /rag/utils/logger.py
- /rag/utils/web_utils.py
2026-06-30 10:33:28 +00:00

169 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
Обработка файлов: отправка пользователю через HTTP Upload и хирургическая замена в DOCX.
Функции: send_file_to_user, handle_ai_file_request.
АДАПТАЦИЯ: эти функции уже работают через HTTP (загружают файлы в ejabberd).
Они не требуют изменений, так как работают с локальными файлами.
Но я добавлю комментарии для ясности.
"""
import asyncio
import os
import re
import shutil
import time
import random
import string
import logging
logger = logging.getLogger(__name__)
async def send_file_to_user(bot, msg, file_path, prefix="", suffix=None):
"""
Отправляет файл пользователю через HTTP Upload (XEP-0363).
Аргументы:
bot: экземпляр бота
msg: объект XMPP-сообщения
file_path: путь к файлу на диске
prefix: текст перед ссылкой
suffix: суффикс для имени файла
Возвращает:
bool: True при успехе, False при ошибке
"""
if not os.path.exists(file_path):
logger.error(f"Файл не найден: {file_path}")
return False
original_name = os.path.basename(file_path)
name, ext = os.path.splitext(original_name)
# Санитизация суффикса (только буквы, цифры, подчёркивание, дефис)
if suffix:
suffix = re.sub(r'[^a-zA-Zа-яА-ЯёЁ0-9_-]', '', suffix)
suffix = suffix.strip('.')
if not suffix or suffix == '_':
suffix = '_sanitized'
if not suffix.startswith('_'):
suffix = '_' + suffix
new_name = f"{name}{suffix}{ext}"
new_path = os.path.join(os.path.dirname(file_path), new_name)
os.rename(file_path, new_path)
file_path = new_path
try:
# Загружаем файл через XEP-0363 (HTTP Upload)
result = await bot.plugin['xep_0363'].upload_file(file_path)
if isinstance(result, dict):
file_url = result.get('get_url', result.get('url', str(result)))
else:
file_url = str(result)
except Exception as e:
logger.error(f"Ошибка загрузки файла через XEP-0363: {e}", exc_info=True)
error_reply = msg.reply("Не удалось загрузить файл. Проверьте настройки HTTP Upload.")
if error_reply:
await error_reply.send()
return False
reply = msg.reply(f"{prefix}\n📄 {file_url}")
if reply:
reply.send()
else:
logger.error("Не удалось создать reply для ссылки на файл")
return True
async def handle_ai_file_request(bot, msg, ai_response, user_jid, room_jid=None, body=None, suffix=None):
"""
Обрабатывает теги [SURGICAL_REPLACE], [REWRITE], [FILE] из ответа AI.
Поддерживает три режима:
- SURGICAL: замена текста в существующем DOCX
- REWRITE: создание нового DOCX из текста
- FILE: создание нового DOCX (общий случай)
"""
key = (user_jid, room_jid) if room_jid is not None else user_jid
surgical_match = re.search(r'\[SURGICAL_REPLACE\](.*?)\[/SURGICAL_REPLACE\]', ai_response, re.DOTALL)
rewrite_match = re.search(r'\[REWRITE\](.*?)\[/REWRITE\]', ai_response, re.DOTALL)
file_match = re.search(r'\[FILE\](.*?)\[/FILE\]', ai_response, re.DOTALL)
# ---- SURGICAL_REPLACE ----
if surgical_match:
content = surgical_match.group(1).strip()
replacements = {}
for line in content.split('\n'):
line = line.strip()
if '|||' in line:
old, new = line.split('|||', 1)
replacements[old.strip()] = new.strip()
if not replacements:
reply = msg.reply("⚠️ Не найдено пар 'старое ||| новое' в теге SURGICAL_REPLACE.")
if reply:
reply.send()
return
original_path = bot._last_file_path.get(key)
if not original_path or not os.path.exists(original_path):
reply = msg.reply("❌ Нет загруженного документа для замены. Сначала отправьте файл (без !learn).")
if reply:
reply.send()
return
new_path = await asyncio.to_thread(bot.files.surgical_replace, original_path, replacements)
if new_path:
await send_file_to_user(bot, msg, new_path, prefix="✅ Замена выполнена:", suffix="surgical")
try:
os.unlink(new_path)
except Exception as e:
logger.warning(f"Не удалось удалить временный файл {new_path}: {e}")
else:
reply = msg.reply("❌ Ошибка при хирургической замене. Проверьте формат документа.")
if reply:
reply.send()
return
# ---- REWRITE ----
if rewrite_match:
content = rewrite_match.group(1).strip()
title = "Сгенерированный документ"
title_match = re.search(r'\[TITLE\](.*?)\[/TITLE\]', ai_response, re.DOTALL)
if title_match:
title = title_match.group(1).strip()
new_path = await asyncio.to_thread(bot.files.create_docx, content, title, user_jid, mode="REWRITE")
if new_path:
await send_file_to_user(bot, msg, new_path, prefix="📄 Документ создан:", suffix="rewrite")
try:
os.unlink(new_path)
except Exception as e:
logger.warning(f"Не удалось удалить временный файл {new_path}: {e}")
else:
reply = msg.reply("Не удалось создать документ.")
if reply:
reply.send()
return
# ---- FILE ----
if file_match:
content = file_match.group(1).strip()
title = "Файл"
title_match = re.search(r'\[TITLE\](.*?)\[/TITLE\]', ai_response, re.DOTALL)
if title_match:
title = title_match.group(1).strip()
new_path = await asyncio.to_thread(bot.files.create_docx, content, title, user_jid, mode="FILE")
if new_path:
await send_file_to_user(bot, msg, new_path, prefix="📄 Файл создан:", suffix="file")
try:
os.unlink(new_path)
except Exception as e:
logger.warning(f"Не удалось удалить временный файл {new_path}: {e}")
else:
reply = msg.reply("Не удалось создать файл.")
if reply:
reply.send()
return
logger.debug(f"Ответ AI не содержит тегов файлов: {ai_response[:200]}")