Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
61 changes: 51 additions & 10 deletions EasyDOC-frontend/easydoc-parser/main.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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()

# 확장자에 따라 파싱
Expand All @@ -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))
Expand All @@ -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)}

Expand Down
2 changes: 2 additions & 0 deletions EasyDOC-frontend/easydoc-parser/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ pandas==2.2.0
konlpy==0.6.0
torch==2.5.1
transformers==4.47.1
sqlalchemy
pymysql
104 changes: 91 additions & 13 deletions EasyDOC-frontend/src/pages/Viewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");

Expand Down Expand Up @@ -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", {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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 서버에 분석 요청...");
Expand All @@ -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);
Expand All @@ -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", {
Expand All @@ -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);
Expand All @@ -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 (
<div className="viewer-page">

Expand Down Expand Up @@ -338,14 +412,18 @@ const handleFileChange = async (e) => {
</div>
<ul className="doc-list">
{recentDocs.map((doc) => (
<li key={doc.id} className="doc-item">
<li
key={doc.id}
className={`doc-item ${selectedDoc && selectedDoc.id === doc.id ? "active" : ""}`}
onClick={() => handleDocClick(doc.id)}
>
<div className="doc-info">
<div className="doc-icon-box">
<FileText size={18} />
</div>
<div className="doc-text">
<span className="doc-title">{doc.title}</span>
<span className="doc-date">{doc.date}</span>
<span className="doc-title">{doc.file_name}</span>
<span className="doc-date">{formatDate(doc.created_at)}</span>
</div>
</div>
<ChevronRight size={16} color="#9ca3af" />
Expand Down
Loading