diff --git a/backend/api/v1/documents/serializers.py b/backend/api/v1/documents/serializers.py index 5bd2ece9..d6d3b7ea 100644 --- a/backend/api/v1/documents/serializers.py +++ b/backend/api/v1/documents/serializers.py @@ -278,6 +278,7 @@ class DocumentWriteSerializer(serializers.ModelSerializer): "title", "description", "file", + "extracted_text", "sent_to_all", "department_ids", "recipient_ids", diff --git a/frontend/src/components/documents/DocumentUploadForm.tsx b/frontend/src/components/documents/DocumentUploadForm.tsx index 301b7689..9f17b29c 100644 --- a/frontend/src/components/documents/DocumentUploadForm.tsx +++ b/frontend/src/components/documents/DocumentUploadForm.tsx @@ -2,28 +2,86 @@ import { useState, useCallback } from "react"; import { useDropzone } from "react-dropzone"; -import { Upload, X, FileText, AlertCircle } from "lucide-react"; +import { Upload, X, FileText, AlertCircle, Loader2 } from "lucide-react"; import { apiClient } from "@/lib/api"; import { toast } from "sonner"; +import { processDocument, needsProcessing, type ProcessingProgress } from "@/lib/document-utils"; interface DocumentUploadFormProps { onSuccess?: () => void; onCancel?: () => void; } +// Сообщения для различных этапов обработки +const STAGE_MESSAGES: Record = { + compressing: "Сжатие изображения...", + ocr: "Распознавание текста (OCR)...", + extracting_text: "Извлечение текста...", + generating_thumbnail: "Создание миниатюры...", + complete: "Обработка завершена", +}; + export function DocumentUploadForm({ onSuccess, onCancel }: DocumentUploadFormProps) { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [file, setFile] = useState(null); + const [processedFile, setProcessedFile] = useState(null); + const [extractedText, setExtractedText] = useState(""); + const [isProcessing, setIsProcessing] = useState(false); + const [processingProgress, setProcessingProgress] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); - const onDrop = useCallback((acceptedFiles: File[]) => { + const onDrop = useCallback(async (acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { - setFile(acceptedFiles[0]); + const selectedFile = acceptedFiles[0]; + setFile(selectedFile); + setError(null); + // Автоматически заполняем название, если оно пустое - if (!title && acceptedFiles[0].name) { - setTitle(acceptedFiles[0].name.replace(/\.[^/.]+$/, "")); // Убираем расширение + if (!title && selectedFile.name) { + setTitle(selectedFile.name.replace(/\.[^/.]+$/, "")); // Убираем расширение + } + + // Проверяем, требуется ли обработка файла + if (needsProcessing(selectedFile)) { + setIsProcessing(true); + setProcessingProgress({ + stage: "compressing", + progress: 0, + message: "Начало обработки..." + }); + + try { + const result = await processDocument(selectedFile, { + enableOCR: true, + enableCompression: true, + enableTextExtraction: true, + enableThumbnail: true, + onProgress: (progress) => { + setProcessingProgress(progress); + }, + }); + + // Сохраняем извлеченный текст + if (result.extractedText) { + setExtractedText(result.extractedText); + } + + // Если файл был обработан (сжат), используем обработанную версию + if (result.processedFile) { + setProcessedFile(result.processedFile); + } + + toast.success("Документ обработан успешно"); + } catch (err) { + console.error("Ошибка обработки документа:", err); + setError(err instanceof Error ? err.message : "Не удалось обработать документ"); + toast.error("Ошибка обработки документа"); + } finally { + setIsProcessing(false); + setProcessingProgress(null); + } } } }, [title]); @@ -54,10 +112,14 @@ export function DocumentUploadForm({ onSuccess, onCancel }: DocumentUploadFormPr setIsSubmitting(true); try { + // Используем обработанный файл, если он есть, иначе оригинальный + const fileToUpload = processedFile || file; + await apiClient.createDocument({ title, description, - file, + file: fileToUpload, + extracted_text: extractedText || undefined, // Отправляем извлеченный текст, если есть }); toast.success("Документ успешно загружен"); @@ -66,6 +128,8 @@ export function DocumentUploadForm({ onSuccess, onCancel }: DocumentUploadFormPr setTitle(""); setDescription(""); setFile(null); + setProcessedFile(null); + setExtractedText(""); if (onSuccess) { onSuccess(); @@ -113,21 +177,56 @@ export function DocumentUploadForm({ onSuccess, onCancel }: DocumentUploadFormPr

) : ( -
- -
-

{file.name}

-

- {(file.size / 1024 / 1024).toFixed(2)} МБ -

+
+
+ +
+

{file.name}

+

+ {(file.size / 1024 / 1024).toFixed(2)} МБ +

+
+
- + + {/* Индикатор обработки */} + {isProcessing && processingProgress && ( +
+
+ + + {STAGE_MESSAGES[processingProgress.stage] || "Обработка..."} + +
+
+
+
+

+ {processingProgress.progress}% +

+
+ )} + + {/* Сообщение о сжатии */} + {processedFile && !isProcessing && ( +
+ ✓ Изображение сжато: {(file.size / 1024 / 1024).toFixed(2)} МБ → {(processedFile.size / 1024 / 1024).toFixed(2)} МБ +
+ )}
)}
@@ -163,21 +262,45 @@ export function DocumentUploadForm({ onSuccess, onCancel }: DocumentUploadFormPr />
+ {/* Извлеченный текст */} + {extractedText && ( +
+ +