diff --git a/EasyDOC-frontend/easydoc-parser/__pycache__/main.cpython-310.pyc b/EasyDOC-frontend/easydoc-parser/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000..746de0c Binary files /dev/null and b/EasyDOC-frontend/easydoc-parser/__pycache__/main.cpython-310.pyc differ diff --git a/EasyDOC-frontend/easydoc-parser/__pycache__/main.cpython-314.pyc b/EasyDOC-frontend/easydoc-parser/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000..8d7adf6 Binary files /dev/null and b/EasyDOC-frontend/easydoc-parser/__pycache__/main.cpython-314.pyc differ diff --git a/EasyDOC-frontend/easydoc-parser/main.py b/EasyDOC-frontend/easydoc-parser/main.py index c3b0424..9332562 100644 --- a/EasyDOC-frontend/easydoc-parser/main.py +++ b/EasyDOC-frontend/easydoc-parser/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, UploadFile, File +from fastapi import FastAPI, UploadFile, File, Depends from fastapi.middleware.cors import CORSMiddleware import pdfplumber import olefile @@ -7,11 +7,23 @@ import io import boto3 import os +import sys +from urllib.parse import unquote from dotenv import load_dotenv import pandas as pd from konlpy.tag import Okt import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification +from sqlalchemy.orm import Session + +# DB 접근을 위한 경로 설정 및 모듈 임포트 +current_dir = os.path.dirname(os.path.abspath(__file__)) +parent_dir = os.path.dirname(current_dir) +root_dir = os.path.dirname(parent_dir) +sys.path.append(parent_dir) +sys.path.append(root_dir) + +from database_document.database import get_db, Document load_dotenv() @@ -152,19 +164,24 @@ async def parse_hwp(file: UploadFile = File(...)): "text": text.strip() } - +#S3 문서 파싱 및 데이터베이스 저장 API @app.get("/parse/s3/{file_key:path}") -async def parse_from_s3(file_key: str): +async def parse_from_s3(file_key: str, db: Session = Depends(get_db)): # db 파라미터 추가 """S3에서 파일 가져와서 파싱""" - print(f"[DEBUG] 파싱 요청 받음 - 파일 키: {file_key}") - print(f"[DEBUG] 버킷: {BUCKET_NAME}") + + # URL 인코딩된 파일명(예: %ED%95...)을 원래 한글 파일명으로 디코딩 + decoded_file_key = unquote(file_key) + + print(f"[DEBUG] 파싱 요청 받음 - 파일 키: {decoded_file_key}") + #print(f"[DEBUG] 파싱 요청 받음 - 파일 키: {file_key}") + #print(f"[DEBUG] 버킷: {BUCKET_NAME}") try: # S3에서 파일 다운로드 print(f"[DEBUG] S3 GetObject 시도 중...") - response = s3.get_object(Bucket=BUCKET_NAME, Key=file_key) + response = s3.get_object(Bucket=BUCKET_NAME, Key=decoded_file_key) print(f"[DEBUG] S3에서 파일 다운로드 성공!") contents = response["Body"].read() - filename = file_key.split("/")[-1] + filename = decoded_file_key.split("/")[-1] ext = filename.split(".")[-1].lower() # 확장자에 따라 파싱 @@ -175,7 +192,7 @@ async def parse_from_s3(file_key: str): page_text = page.extract_text() if page_text: text += page_text + "\n" - return {"filename": filename, "text": text} + #return {"filename": filename, "text": text} elif ext == "hwp": ole = olefile.OleFileIO(io.BytesIO(contents)) @@ -185,11 +202,35 @@ async def parse_from_s3(file_key: str): else: text = "텍스트를 추출할 수 없습니다." ole.close() - return {"filename": filename, "text": text.strip()} + text = text.strip() + #return {"filename": filename, "text": text.strip()} else: return {"error": "지원하지 않는 파일 형식입니다."} - + + # DB 저장 로직 + AWS_REGION = os.getenv("AWS_DEFAULT_REGION", "ap-northeast-2") + s3_url = f"https://{BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com/{file_key}" + + new_doc = Document( + file_name = filename, + file_type = ext, + s3_url = s3_url, + extracted_text=text + ) + + db.add(new_doc) + db.commit() + db.refresh(new_doc) # 생성된 고유 id 가져오기 + print(f"[DEBUG] DB 저장 성공! (문서 번호: {new_doc.id})") + + # 프론트엔드(Viewer.jsx)가 요구하는 형태로 반환 + return { + "id": new_doc.id, + "filename": filename, + "text": text + } + except Exception as e: return {"error": str(e)} diff --git a/EasyDOC-frontend/easydoc-parser/requirements.txt b/EasyDOC-frontend/easydoc-parser/requirements.txt index 29bd4a0..6992687 100644 --- a/EasyDOC-frontend/easydoc-parser/requirements.txt +++ b/EasyDOC-frontend/easydoc-parser/requirements.txt @@ -9,3 +9,5 @@ pandas==2.2.0 konlpy==0.6.0 torch==2.5.1 transformers==4.47.1 +sqlalchemy +pymysql \ No newline at end of file diff --git a/EasyDOC-frontend/src/pages/Viewer.jsx b/EasyDOC-frontend/src/pages/Viewer.jsx index 67f00cc..f538bdf 100644 --- a/EasyDOC-frontend/src/pages/Viewer.jsx +++ b/EasyDOC-frontend/src/pages/Viewer.jsx @@ -99,12 +99,8 @@ export default function Viewer({ parsedData, ocrData, pdfFileUrl }) { const [isPdf, setIsPdf] = useState(false); // 최근 문서 목록 상태 - const [recentDocs, setRecentDocs] = useState([ - {id: 1, title: '행정기본법.pdf', date: '2024.11.14'}, - {id: 2, title: '조세특례제한법.pdf', date: '2024.11.13'}, - {id: 3, title: '도시및주거환경지정비법.pdf', date: '2024.11.13'}, - {id: 4, title: '건축법시행령.pdf', date: '2024.11.12'}, - ]); + const [recentDocs, setRecentDocs] = useState([]); + const [selectedDoc, setSelectedDoc] = useState(null); // 현재 선택된 문서 상세 정보 // 파일 선택을 위한 ref const fileInputRef = useRef(null); @@ -122,6 +118,49 @@ export default function Viewer({ parsedData, ocrData, pdfFileUrl }) { const [ocrText, setOcrText] = useState(""); const [isLoading, setIsLoading] = useState(false); + // docsinfos DB에서 최근 문서 목록 가져오기 + useEffect(() => { + fetchDocuments(); + }, []); + + const fetchDocuments = async () => { + try { + const response = await axios.get('http://localhost:8001/api/documents'); + setRecentDocs(response.data); + } catch (error) { + console.error("문서 목록 로딩 실패:", error); + } + }; + + // 사이드바에서 문서 클릭 시 상세 내용 가져오기 + const handleDocClick = async (id) => { + try { + setIsLoading(true); + const response = await axios.get(`http://localhost:8001/api/documents/${id}`); + + const docData = response.data; + + setSelectedDoc(response.data); // 선택된 문서 상태 업데이트 + setPdfUrl(null); // PDF 뷰어에서 텍스트 모드로 전환 + + // 가져온 텍스트를 뷰어 상태에 반영 + setParsedText(docData.text || ""); + setOcrText(""); + + // DB에 저장된 difficultWords(어려운 단어 데이터) 호출 + setDifficultWords(docData.difficult_words || []); + + // 추후 DB에 저장된 difficultWords(어려운 단어 데이터)가 있다면 호출 + //setDifficultWords([]); // 현재는 빈 배열로 초기화 (이전 문서의 하이라이트 제거) + + } catch (error) { + console.error("문서 상세 로딩 실패:", error); + alert("문서 내용을 불러올 수 없습니다."); + } finally { + setIsLoading(false); + } + }; + // 뷰 모드 상태 ("parsed": 파싱된 문서, "original": 원본 문서) const [viewMode, setViewMode] = useState("parsed"); @@ -153,7 +192,7 @@ export default function Viewer({ parsedData, ocrData, pdfFileUrl }) { }; // 난이도 분석 함수 -const analyzeText = async (text) => { +const analyzeText = async (text, docId = null) => { try { console.log("난이도 분석 요청 중..."); const response = await axios.post("http://localhost:8000/analyze", { @@ -192,6 +231,19 @@ const analyzeText = async (text) => { } setDifficultWords(difficultWords); + + // DB에 분석된 하이라이트 단어들 전송 및 저장 + if (docId) { + try { + await axios.put(`http://localhost:8001/api/documents/${docId}/words`, { + difficult_words: difficultWords + }); + console.log("DB에 하이라이트 단어 저장 완료!") + } catch (dbErr) { + console.error("DB 하이라이트 단어 저장 오류:", dbErr); + } + } + } catch (error) { console.error("난이도 분석 오류:", error); } @@ -205,6 +257,7 @@ const handleFileChange = async (e) => { const objectUrl = URL.createObjectURL(file); setPdfUrl(objectUrl); + setSelectedDoc(null); // 기존 선택된 텍스트 초기화 // PDF 여부에 따라 렌더링 모드 결정 const fileIsPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'); setIsPdf(fileIsPdf); @@ -254,6 +307,7 @@ const handleFileChange = async (e) => { await new Promise(resolve => setTimeout(resolve, 1000)); // 업로드된 파일형에 따라 파싱 혹은 OCR 실행 + let resultData = null; // 파싱 또는 OCR 결과를 DB에 넣기 위해 저장할 변수 if (file.type.startsWith("image/")) { // 파일이 이미지일 때 -> OCR 서버 (8001번) 요청 console.log("6. OCR 서버에 분석 요청..."); @@ -263,6 +317,7 @@ const handleFileChange = async (e) => { console.log("7. OCR 결과 도착!", ocrResponse.data); setOcrText(ocrResponse.data.text || ocrResponse.data); setParsedText(""); // 문서 파싱 결과는 비움 + resultData = ocrResponse.data; } else { // 파일이 문서일 때 -> 파싱 서버 (8000번) 요청 console.log("6. 파싱 요청 중..., S3 키:", s3Key); @@ -272,14 +327,26 @@ const handleFileChange = async (e) => { console.log("7. 파싱 완료!", parseResponse.data); const extractedText = parseResponse.data.text; setParsedText(extractedText); // 파싱 결과 저장 + resultData = parseResponse.data; // 난이도 분석 추가 - await analyzeText(extractedText); + await analyzeText(extractedText, resultData.id); + //await analyzeText(extractedText); setOcrText(""); // OCR 결과는 비움 } // 최근 문서 목록 업데이트 - const newDoc = { + // 백엔드에서 받은 {id, text} 형식을 selectedDoc 상태에 맞춰서 넣음 + setSelectedDoc({ + id: resultData.id, + file_name: file.name, + text: resultData.text + }); + + // 목록 새로고침 (방금 올린 파일을 최근 문서 목록에 등록) + fetchDocuments(); + + /*const newDoc = { id: Date.now(), title: file.name, date: new Date().toLocaleDateString("ko-KR", { @@ -292,7 +359,7 @@ const handleFileChange = async (e) => { }; const filteredDocs = recentDocs.filter((doc) => doc.title != file.name); - setRecentDocs([newDoc, ...filteredDocs].slice(0, 10)); + setRecentDocs([newDoc, ...filteredDocs].slice(0, 10));*/ } catch (error) { console.error("파일 처리 오류:", error); @@ -305,6 +372,13 @@ const handleFileChange = async (e) => { } }; + // 날짜 형식 변환 함수 (2024-11-14 -> 2024.11.14) + const formatDate = (dateString) => { + if (!dateString) return ""; + return dateString.substring(0, 10).replace(/-/g, '.'); + }; + + return (