Compare commits
2 Commits
main
...
bfc477bedc
| Author | SHA1 | Date | |
|---|---|---|---|
| bfc477bedc | |||
| 604c29403f |
68
analyze.py
Normal file
68
analyze.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
from pypdf import PdfReader
|
||||||
|
try:
|
||||||
|
import pytesseract
|
||||||
|
from pdf2image import convert_from_path
|
||||||
|
from PIL import Image
|
||||||
|
TESSERACT_PATH = r'C:\Users\User\AppData\Local\Programs\Tesseract-OCR esseract.exe'
|
||||||
|
POPPLER_PATH = r'D:\이태훈\00크롬다운로드\poppler-25.12.0\Library\bin'
|
||||||
|
pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH
|
||||||
|
OCR_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
OCR_AVAILABLE = False
|
||||||
|
|
||||||
|
def analyze_file_content(filename: str):
|
||||||
|
file_path = os.path.join("sample", filename)
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return {"error": "File not found"}
|
||||||
|
|
||||||
|
content_parts = []
|
||||||
|
log_steps = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
if filename.lower().endswith(".pdf"):
|
||||||
|
log_steps.append("1. 레이어: 파일 제목 분석 중...")
|
||||||
|
log_steps.append("2. 레이어: PDF 텍스트 엔진 가동...")
|
||||||
|
reader = PdfReader(file_path)
|
||||||
|
text_extracted = ""
|
||||||
|
for page in reader.pages[:5]:
|
||||||
|
text = page.extract_text()
|
||||||
|
if text: text_extracted += text + "
|
||||||
|
"
|
||||||
|
if text_extracted.strip():
|
||||||
|
content_parts.append(unicodedata.normalize('NFC', text_extracted))
|
||||||
|
|
||||||
|
log_steps.append("3. 레이어: OCR 이미지 스캔 강제 실행...")
|
||||||
|
if OCR_AVAILABLE and os.path.exists(TESSERACT_PATH):
|
||||||
|
images = convert_from_path(file_path, first_page=1, last_page=2, poppler_path=POPPLER_PATH)
|
||||||
|
for i, img in enumerate(images):
|
||||||
|
page_text = pytesseract.image_to_string(img, lang='kor+eng')
|
||||||
|
content_parts.append(unicodedata.normalize('NFC', page_text))
|
||||||
|
else:
|
||||||
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||||
|
content_parts.append(unicodedata.normalize('NFC', f.read(5000)))
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
full_content = "
|
||||||
|
".join(content_parts)
|
||||||
|
search_pool = (full_content + " " + filename).lower().replace(" ", "").replace("
|
||||||
|
", "")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"suggested_path": "분석실패",
|
||||||
|
"confidence": "Low",
|
||||||
|
"log_steps": log_steps,
|
||||||
|
"raw_text": full_content,
|
||||||
|
"reason": "일치 키워드 없음"
|
||||||
|
}
|
||||||
|
|
||||||
|
if "실정보고" in search_pool:
|
||||||
|
if any(k in search_pool for k in ["어천", "공주"]):
|
||||||
|
if "품질" in search_pool: result["suggested_path"] = "설계변경 > 실정보고(어천~공주) > 품질관리"
|
||||||
|
else: result["suggested_path"] = "설계변경 > 실정보고(어천~공주) > 기타"
|
||||||
|
result["confidence"] = "100%"
|
||||||
|
result["reason"] = "3중 레이어 분석: 실정보고+어천공주 키워드 통합 검출"
|
||||||
|
|
||||||
|
return result
|
||||||
21
index.html
Normal file
21
index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Project Master Portal</title>
|
||||||
|
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||||
|
<link rel="stylesheet" href="style/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="topbar">
|
||||||
|
<div class="topbar-header"><a href="/"><h2>Project Master Test</h2></a></div>
|
||||||
|
</nav>
|
||||||
|
<div class="portal-container">
|
||||||
|
<div class="button-grid" style="display:grid; grid-template-columns:repeat(2, 1fr); gap:30px;">
|
||||||
|
<a href="/dashboard" class="portal-card"><div class="icon">📊</div><h2>대시보드</h2></a>
|
||||||
|
<a href="/mailTest" class="portal-card"><div class="icon">✉️</div><h2>메일 테스트</h2></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
87
mailTest.html
Normal file
87
mailTest.html
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Project Mail Manager</title>
|
||||||
|
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||||
|
<link rel="stylesheet" href="/style/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="topbar">
|
||||||
|
<div class="topbar-header"><a href="/"><h2>Project Master Test</h2></a></div>
|
||||||
|
<ul class="nav-list">
|
||||||
|
<li class="nav-item" onclick="location.href='/dashboard'">대시보드</li>
|
||||||
|
<li class="nav-item active" onclick="location.href='/mailTest'">메일관리</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="mail-wrapper">
|
||||||
|
<aside class="mail-sidebar">
|
||||||
|
<select class="project-select"><option>라오스 ITTC 관개 교육센터</option></select>
|
||||||
|
<ul class="folder-list"><li class="folder-item active"><span>📥 수신함</span></li></ul>
|
||||||
|
</aside>
|
||||||
|
<section class="mail-list-area">
|
||||||
|
<div class="mail-items-container"><div class="mail-item active"><div>라오스 농림부</div><div>ITTC 교육센터 착공식 일정 협의</div></div></div>
|
||||||
|
</section>
|
||||||
|
<section class="mail-content-area">
|
||||||
|
<div class="attachment-area">
|
||||||
|
<div style="display:flex; justify-content:space-between;">
|
||||||
|
<div>첨부파일 리스트</div>
|
||||||
|
<div class="ai-toggle-wrap"><span class="ai-label">AI 판단</span><label class="switch"><input type="checkbox" id="aiToggle" checked onchange="renderFiles()"><span class="slider"></span></label></div>
|
||||||
|
</div>
|
||||||
|
<div id="attachmentList"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let currentFiles = [];
|
||||||
|
async function loadAttachments() {
|
||||||
|
const res = await fetch('/attachments');
|
||||||
|
currentFiles = await res.json();
|
||||||
|
renderFiles();
|
||||||
|
}
|
||||||
|
function renderFiles() {
|
||||||
|
const isAiActive = document.getElementById('aiToggle').checked;
|
||||||
|
const container = document.getElementById('attachmentList');
|
||||||
|
container.innerHTML = '';
|
||||||
|
currentFiles.forEach((file, index) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'attachment-item-wrap';
|
||||||
|
const btnAiClass = isAiActive ? 'btn-ai' : 'btn-normal';
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="attachment-item">
|
||||||
|
<div class="file-info"><span>📄</span><div>${file.name}</div><span id="recommend-${index}" class="ai-recommend" style="display:none;"></span></div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-upload ${btnAiClass}" onclick="startAnalysis(${index})">AI 분석</button>
|
||||||
|
<button class="btn-upload btn-normal" onclick="confirmUpload(${index})">파일 업로드</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="log-area-${index}" class="file-log-area"><div id="log-content-${index}"></div></div>
|
||||||
|
`;
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function startAnalysis(index) {
|
||||||
|
const file = currentFiles[index];
|
||||||
|
const logArea = document.getElementById(`log-area-${index}`);
|
||||||
|
const logContent = document.getElementById(`log-content-${index}`);
|
||||||
|
const recLabel = document.getElementById(`recommend-${index}`);
|
||||||
|
logArea.classList.add('active');
|
||||||
|
logContent.innerHTML = '>>> 분석 가동...';
|
||||||
|
recLabel.style.display = 'inline-block';
|
||||||
|
recLabel.innerText = '분석 중...';
|
||||||
|
const res = await fetch(`/analyze-file?filename=${encodeURIComponent(file.name)}`);
|
||||||
|
const analysis = await res.json();
|
||||||
|
recLabel.innerText = `추천: ${analysis.suggested_path}`;
|
||||||
|
logContent.innerHTML = `[결과] ${analysis.suggested_path}<br>└ ${analysis.reason}`;
|
||||||
|
currentFiles[index].analysis = analysis;
|
||||||
|
}
|
||||||
|
function confirmUpload(index) {
|
||||||
|
const file = currentFiles[index];
|
||||||
|
const path = file.analysis ? file.analysis.suggested_path : "선택한 탭";
|
||||||
|
if (confirm(`[${file.name}] 파일을 [${path}]로 업로드하시겠습니까?`)) alert("완료");
|
||||||
|
}
|
||||||
|
loadAttachments();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
tokens.json
Normal file
21
tokens.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"core": {
|
||||||
|
"dimension": {
|
||||||
|
"scale": { "$value": "2", "$type": "dimension" },
|
||||||
|
"xs": { "$value": "4", "$type": "dimension" },
|
||||||
|
"sm": { "$value": "8", "$type": "dimension" },
|
||||||
|
"md": { "$value": "16", "$type": "dimension" },
|
||||||
|
"lg": { "$value": "32", "$type": "dimension" },
|
||||||
|
"xl": { "$value": "64", "$type": "dimension" }
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"sm": { "$value": "4", "$type": "dimension" },
|
||||||
|
"lg": { "$value": "8", "$type": "dimension" },
|
||||||
|
"xl": { "$value": "16", "$type": "dimension" }
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"primary-lv-0": { "$value": "#e9eeed", "$type": "color" },
|
||||||
|
"primary-lv-6": { "$value": "#1e5149", "$type": "color" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user