v3:추출 파이프라인_260122

This commit is contained in:
2026-02-13 14:06:57 +09:00
parent 3c5b9e29fe
commit 05d2d8cc9a
28 changed files with 6910 additions and 47 deletions

View File

@@ -1073,6 +1073,7 @@
font-size: 16px;
}
</style>
<link rel="stylesheet" href="/static/css/editor.css">
</head>
<body>
<!-- 상단 툴바 -->
@@ -1081,10 +1082,10 @@
<div class="toolbar-spacer"></div>
<button class="toolbar-btn" id="editBtn" onclick="toggleEditMode()">✏️ 편집하기</button>
<button class="toolbar-btn" id="editModeBtn" onclick="toggleEditMode()">✏️ 편집하기</button>
<div class="toolbar-divider"></div>
<select class="zoom-select" id="zoomSelect" onchange="setZoom(this.value)">
<option value="50">50%</option>
<option value="75">75%</option>
@@ -1092,10 +1093,12 @@
<option value="125">125%</option>
<option value="150">150%</option>
</select>
<div class="toolbar-divider"></div>
<button class="toolbar-btn" onclick="exportHwp()">📄 HWP 추출</button>
<button class="toolbar-btn" onclick="saveHtml()">💾 HTML 저장</button>
<button class="toolbar-btn" disabled title="준비중">📊 PPT 저장</button>
<button class="toolbar-btn" onclick="printDoc()">🖨️ PDF/인쇄</button>
</div>
@@ -1299,10 +1302,9 @@
</div>
<!-- 보고서 -->
<div class="doc-type-item disabled" data-type="report">
<input type="radio" name="docType" disabled>
<span class="label">📄 보고서</span>
<span class="badge">준비중</span>
<div class="doc-type-item" data-type="report" onclick="selectDocType('report')">
<input type="radio" name="docType">
<span class="label">📄 보고서</span>
<div class="doc-type-preview">
<div class="preview-thumbnail report">
@@ -1373,15 +1375,15 @@
<div class="option-group">
<div class="option-item" onclick="selectPageOption('1')">
<input type="radio" name="pages" value="1" id="page1">
<label for="page1">1p (본문)</label>
<label for="page1"> (본문) 1p</label>
</div>
<div class="option-item selected" onclick="selectPageOption('2')">
<input type="radio" name="pages" value="2" id="page2" checked>
<label for="page2">1p + 1p 첨부</label>
<label for="page2"> (본문) 1p + (첨부) 1p</label>
</div>
<div class="option-item" onclick="selectPageOption('n')">
<input type="radio" name="pages" value="n" id="pageN">
<label for="pageN">1p + np 첨부 (자동)</label>
<label for="pageN"> (본문) 1p + (첨부) np</label>
</div>
</div>
</div>
@@ -1393,6 +1395,43 @@
</div>
</div>
<!-- 보고서 옵션 -->
<div id="reportOptions" style="display:none;">
<!-- 보고서 구성 -->
<div class="option-section">
<div class="option-title">보고서 구성</div>
<div class="option-group">
<div class="option-item" style="cursor:default;">
<input type="checkbox" id="reportCover" checked>
<label for="reportCover">📘 표지</label>
</div>
<div class="option-item" style="cursor:default;">
<input type="checkbox" id="reportToc" checked>
<label for="reportToc">📑 목차</label>
</div>
<div class="option-item" style="cursor:default;">
<input type="checkbox" id="reportDivider">
<label for="reportDivider">📄 간지</label>
</div>
<div class="option-item" style="cursor:default; opacity:0.6;">
<input type="checkbox" id="reportContent" checked disabled>
<label for="reportContent">📝 내지 (필수)</label>
</div>
</div>
</div>
<!-- 요청사항 -->
<div class="option-section">
<div class="option-title">요청사항</div>
<textarea class="request-textarea" id="reportInstructionInput" placeholder="예: 요약을 상세하게 작성해줘&#10;예: 표지에 로고 추가"></textarea>
</div>
</div>
<!-- 생성 버튼 -->
<button class="generate-btn" id="generateBtn" onclick="generate()" disabled>
<span id="generateBtnText">🚀 생성하기</span>
@@ -1463,7 +1502,6 @@
let generatedHTML = '';
let currentDocType = 'briefing';
let currentPageOption = '2';
let isEditing = false;
let currentZoom = 100;
let folderPath = '';
let referenceLinks = [];
@@ -1472,6 +1510,53 @@
let selectedText = '';
let selectedRange = null;
// ===== HWP 추출 =====
async function exportHwp() {
if (!generatedHTML) {
alert('먼저 문서를 생성해주세요.');
return;
}
// 현재 편집된 HTML 가져오기
const frame = document.getElementById('previewFrame');
const html = frame.contentDocument ?
'<!DOCTYPE html>' + frame.contentDocument.documentElement.outerHTML :
generatedHTML;
setStatus('HWP 변환 중...', true);
try {
const response = await fetch('/export-hwp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
html: html,
doc_type: currentDocType
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'HWP 변환 실패');
}
// 파일 다운로드
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `report_${new Date().toISOString().slice(0,10)}.hwp`;
a.click();
URL.revokeObjectURL(url);
setStatus('HWP 변환 완료', true);
} catch (error) {
alert('HWP 변환 오류: ' + error.message);
setStatus('오류 발생', false);
}
}
// iframe 로드 후 선택 이벤트 연결
function setupIframeSelection() {
const frame = document.getElementById('previewFrame');
@@ -1815,8 +1900,8 @@
// ===== 문서 유형 선택 =====
function selectDocType(type) {
if (type !== 'briefing') {
return; // disabled 항목 클릭 무시
if (type === 'presentation') {
return; // PPT만 disabled
}
currentDocType = type;
@@ -1827,6 +1912,10 @@
item.querySelector('input[type="radio"]').checked = true;
}
});
// 옵션 패널 표시/숨김
document.getElementById('briefingOptions').style.display = (type === 'briefing') ? 'block' : 'none';
document.getElementById('reportOptions').style.display = (type === 'report') ? 'block' : 'none';
}
// ===== 템플릿 추가 =====
@@ -1846,6 +1935,15 @@
// ===== 생성 =====
async function generate() {
if (currentDocType === 'briefing') {
await generateBriefing();
} else if (currentDocType === 'report') {
await generateReport();
}
}
// ===== 기획서 생성 (기존 로직) =====
async function generateBriefing() {
if (!inputContent && !folderPath && referenceLinks.length === 0) {
alert('먼저 폴더 위치, 참고 링크, 또는 HTML을 입력해주세요.');
return;
@@ -1900,20 +1998,15 @@
if (data.success && data.html) {
generatedHTML = data.html;
// 미리보기 표시
document.getElementById('placeholder').style.display = 'none';
const frame = document.getElementById('previewFrame');
frame.classList.add('active');
frame.srcdoc = generatedHTML;
setTimeout(setupIframeSelection, 500);
// 피드백 바 표시
setTimeout(setupIframeSelection, 500); // ← 이 줄 추가
document.getElementById('feedbackBar').classList.add('show');
setStatus('생성 완료', true);
}
} catch (error) {
alert('생성 오류: ' + error.message);
setStatus('오류 발생', false);
@@ -1931,8 +2024,85 @@
}
}
// ===== 보고서 생성 (새로 추가) =====
async function generateReport() {
if (!folderPath && !inputContent) {
alert('폴더 위치 또는 HTML을 입력해주세요.');
return;
}
const btn = document.getElementById('generateBtn');
const btnText = document.getElementById('generateBtnText');
const spinner = document.getElementById('generateSpinner');
btn.disabled = true;
btnText.textContent = '생성 중...';
spinner.style.display = 'block';
resetSteps();
// 체크박스 값 수집
const options = {
content: inputContent, // ← 추가!
folder_path: folderPath,
cover: document.getElementById('reportCover').checked,
toc: document.getElementById('reportToc').checked,
divider: document.getElementById('reportDivider').checked,
instruction: document.getElementById('reportInstructionInput').value
};
setStatus('보고서 생성 중...', true);
try {
// Step 1~9 진행 표시
for (let i = 1; i <= 9; i++) {
updateStep(i, 'running');
await new Promise(r => setTimeout(r, 500));
// TODO: 실제 API 호출
updateStep(i, 'done');
}
const response = await fetch('/generate-report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: inputContent, // HTML 내용 추가
folder_path: folderPath,
cover: document.getElementById('reportCover').checked,
toc: document.getElementById('reportToc').checked,
divider: document.getElementById('reportDivider').checked,
instruction: document.getElementById('reportInstructionInput').value
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
if (data.success && data.html) {
generatedHTML = data.html;
document.getElementById('placeholder').style.display = 'none';
const frame = document.getElementById('previewFrame');
frame.classList.add('active');
frame.srcdoc = generatedHTML;
setTimeout(setupIframeSelection, 500); // ← 추가!
document.getElementById('feedbackBar').classList.add('show');
setStatus('생성 완료', true);
}
} catch (error) {
alert('생성 오류: ' + error.message);
setStatus('오류 발생', false);
} finally {
btn.disabled = false;
btnText.textContent = '🚀 생성하기';
spinner.style.display = 'none';
}
}
// ===== 피드백 수정 =====
async function submitFeedback() {
async function submitFeedback() {
const feedback = document.getElementById('feedbackInput').value.trim();
if (!feedback) {
alert('수정 내용을 입력해주세요.');
@@ -1998,28 +2168,6 @@ async function submitFeedback() {
}
}
// ===== 편집 모드 =====
function toggleEditMode() {
isEditing = !isEditing;
const btn = document.getElementById('editBtn');
const formatBar = document.getElementById('formatBar');
const frame = document.getElementById('previewFrame');
btn.classList.toggle('active', isEditing);
formatBar.classList.toggle('active', isEditing);
if (frame.contentDocument) {
frame.contentDocument.designMode = isEditing ? 'on' : 'off';
}
}
function formatText(command) {
const frame = document.getElementById('previewFrame');
if (frame.contentDocument) {
frame.contentDocument.execCommand(command, false, null);
}
}
// ===== 줌 =====
function setZoom(value) {
currentZoom = parseInt(value);
@@ -2094,6 +2242,6 @@ async function submitFeedback() {
<textarea class="ai-edit-input" id="aiEditInput" rows="3" placeholder="예: 한 줄로 요약해줘&#10;예: 표 형태로 만들어줘"></textarea>
<button class="ai-edit-btn" onclick="submitAiEdit()">✨ 수정하기</button>
</div>
<script src="/static/js/editor.js"></script>
</body>
</html>