""" =============================================================================== FILE: modules/web_api/routers/chat.py ROLE: Роутер чата с чистым разделением: - Диалог и команды СКУД/1С (через agent.py). - Парсинг и извлечение документов без обрезания (через file_parser.py). =============================================================================== """ import os import shutil import logging from typing import Optional from fastapi import APIRouter, Header, HTTPException, UploadFile, File, Form from pydantic import BaseModel from llm.agent import process_chat_message from llm.file_parser import parse_uploaded_file from config import BASE_DIR logger = logging.getLogger("CHAT_API") router = APIRouter(prefix="/api/v1", tags=["Chat"]) UPLOAD_TMP_DIR = os.path.join(BASE_DIR, "data", "uploads") os.makedirs(UPLOAD_TMP_DIR, exist_ok=True) class ChatMessageRequest(BaseModel): message: str session_id: Optional[str] = "web_session_main" user_id: Optional[int] = 1 def resolve_user_id(authorization: Optional[str] = None, explicit_user_id: Optional[int] = None) -> int: if explicit_user_id and explicit_user_id > 0: return explicit_user_id if authorization and authorization.startswith("Bearer "): token = authorization.replace("Bearer ", "").strip() if token.isdigit(): return int(token) return 1 @router.post("/chat") async def chat_endpoint(payload: ChatMessageRequest, authorization: Optional[str] = Header(None)): user_id = resolve_user_id(authorization, payload.user_id) session_id = payload.session_id or "web_session_main" user_msg = payload.message.strip() if not user_msg: raise HTTPException(status_code=400, detail="Пустое сообщение") reply_text, history, action_payload = process_chat_message( user_id=user_id, user_message=user_msg, session_id=session_id ) return { "status": "success", "user_id": user_id, "session_id": session_id, "response": reply_text, "action_payload": action_payload } @router.post("/chat/upload") async def chat_upload_endpoint( file: UploadFile = File(...), message: Optional[str] = Form(""), session_id: Optional[str] = Form("web_session_main"), authorization: Optional[str] = Header(None) ): user_id = resolve_user_id(authorization, 1) file_path = os.path.join(UPLOAD_TMP_DIR, file.filename) with open(file_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) user_msg = (message or "").strip() msg_lower = user_msg.lower() fn_lower = file.filename.lower() # ⭐️ 1. ПРЯМАЯ ИЗОЛИРОВАННАЯ ОБРАБОТКА PDF/СКАНОВ В WORD ЧЕРЕЗ OFFICE МОДУЛЬ ocr_keywords = [ "распознай", "распознать", "в word", "в ворд", "для ворда", "для word", "отформатируй", "текст для вставки", "извлеки текст", "сделай документ", "переведи в ворд" ] if fn_lower.endswith(".pdf") and (any(k in msg_lower for k in ocr_keywords) or not user_msg): logger.info(f"[Office] Прямой запуск распознавания PDF в Word: {file.filename}") from services.office.service import convert_pdf_to_word_service from modules.web_api.llm.db_tools import db_save_chat_message, db_get_chat_history # Сохраняем вопрос пользователя в историю prompt_text = user_msg or f"Распознать документ {file.filename} для MS Word" db_save_chat_message(session_id, "user", prompt_text, is_ephemeral=0) # Вызываем офисный сервис постраничного OCR и сборки DOCX office_res = convert_pdf_to_word_service(file_path, file.filename) reply_text = ( f"📄 **{office_res['message']}**\n\n" f"**Фрагмент первой страницы документа:**\n" f"```text\n{office_res['preview_text']}...\n```\n\n" f"Файл готов к скачиванию и редактированию в MS Word." ) db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=0) action_payload = { "type": "FILE_DOWNLOAD_CARD", "filename": office_res["filename"], "download_url": office_res["download_url"], "tasks_count": f"{office_res['total_pages']} стр." } return { "status": "success", "user_id": user_id, "session_id": session_id, "response": reply_text, "action_payload": action_payload } # 2. Если это не задача OCR в Word — отправляем файл в обычный диалог агента parsed = parse_uploaded_file(file_path, file.filename) file_context = parsed.get("context_text", "") image_b64 = parsed.get("image_b64") prompt_for_agent = user_msg or f"Проанализируй документ {file.filename}" reply_text, history, action_payload = process_chat_message( user_id=user_id, user_message=prompt_for_agent, file_context=file_context, image_b64=image_b64, session_id=session_id ) return { "status": "success", "user_id": user_id, "session_id": session_id, "response": reply_text, "action_payload": action_payload }