148 lines
5.6 KiB
Python
148 lines
5.6 KiB
Python
"""
|
|
===============================================================================
|
|
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
|
|
} |