docs(office): add section 19 to roadmap and changelog for two-phase handwritten ocr pipeline
This commit is contained in:
@@ -1,21 +1,22 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: modules/web_api/routers/chat.py
|
||||
ROLE: Полнофункциональный роутер чата с извлечением текста из PDF и сканов,
|
||||
поддержкой Function Calling, Fast-Path и оптического распознавания OCR.
|
||||
ROLE: Роутер чата с чистым разделением:
|
||||
- Диалог и команды СКУД/1С (через agent.py).
|
||||
- Парсинг и извлечение документов без обрезания (через file_parser.py).
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import base64
|
||||
import logging
|
||||
from typing import Optional, List, Dict, Any
|
||||
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")
|
||||
@@ -39,11 +40,6 @@ def resolve_user_id(authorization: Optional[str] = None, explicit_user_id: Optio
|
||||
token = authorization.replace("Bearer ", "").strip()
|
||||
if token.isdigit():
|
||||
return int(token)
|
||||
elif token.startswith("dev_token_"):
|
||||
try:
|
||||
return int(token.replace("dev_token_", ""))
|
||||
except ValueError:
|
||||
pass
|
||||
return 1
|
||||
|
||||
|
||||
@@ -56,8 +52,6 @@ async def chat_endpoint(payload: ChatMessageRequest, authorization: Optional[str
|
||||
if not user_msg:
|
||||
raise HTTPException(status_code=400, detail="Пустое сообщение")
|
||||
|
||||
logger.info(f"Сообщение от user_id={user_id}, session_id={session_id}: {user_msg}")
|
||||
|
||||
reply_text, history, action_payload = process_chat_message(
|
||||
user_id=user_id,
|
||||
user_message=user_msg,
|
||||
@@ -86,60 +80,60 @@ async def chat_upload_endpoint(
|
||||
with open(file_path, "wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
file_context = ""
|
||||
image_b64 = None
|
||||
user_msg = (message or "").strip()
|
||||
msg_lower = user_msg.lower()
|
||||
fn_lower = file.filename.lower()
|
||||
|
||||
# 1. Текстовые форматы
|
||||
if fn_lower.endswith((".txt", ".csv", ".log", ".md")):
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
file_context = f.read(6000)
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось прочитать текст: {e}")
|
||||
# ⭐️ 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
|
||||
|
||||
# 2. Изображения (прямой OCR)
|
||||
elif fn_lower.endswith((".png", ".jpg", ".jpeg", ".webp")):
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
image_b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||
except Exception as e:
|
||||
logger.warning(f"Ошибка кодирования картинки в base64: {e}")
|
||||
# Сохраняем вопрос пользователя в историю
|
||||
prompt_text = user_msg or f"Распознать документ {file.filename} для MS Word"
|
||||
db_save_chat_message(session_id, "user", prompt_text, is_ephemeral=0)
|
||||
|
||||
# 3. PDF документы (текстовый слой + рендеринг скана при необходимости)
|
||||
elif fn_lower.endswith(".pdf"):
|
||||
# Попытка извлечь встроенный текстовый слой
|
||||
try:
|
||||
import pypdf
|
||||
reader = pypdf.PdfReader(file_path)
|
||||
extracted = []
|
||||
for page in reader.pages:
|
||||
t = page.extract_text()
|
||||
if t:
|
||||
extracted.append(t)
|
||||
file_context = "\n".join(extracted).strip()
|
||||
except Exception:
|
||||
pass
|
||||
# Вызываем офисный сервис постраничного OCR и сборки DOCX
|
||||
office_res = convert_pdf_to_word_service(file_path, file.filename)
|
||||
|
||||
# Если текстового слоя мало (скан или фото документа), рендерим страницу в картинку для Vision OCR
|
||||
if len(file_context) < 40:
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
doc = fitz.open(file_path)
|
||||
if len(doc) > 0:
|
||||
page = doc[0]
|
||||
pix = page.get_pixmap(dpi=150)
|
||||
img_bytes = pix.tobytes("png")
|
||||
image_b64 = base64.b64encode(img_bytes).decode("utf-8")
|
||||
file_context = ""
|
||||
except Exception as e:
|
||||
logger.warning(f"PyMuPDF не установлен или сбой рендеринга PDF: {e}")
|
||||
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)
|
||||
|
||||
user_msg = message.strip() or f"Распознай и проанализируй прикрепленный документ {file.filename}"
|
||||
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=user_msg,
|
||||
user_message=prompt_for_agent,
|
||||
file_context=file_context,
|
||||
image_b64=image_b64,
|
||||
session_id=session_id
|
||||
|
||||
Reference in New Issue
Block a user