Files

484 lines
20 KiB
JavaScript

function updateGenerateBtnText() {
const btnText = document.getElementById('generateBtnText');
if (!btnText) return;
const hasFolder = folderPath && folderPath.trim() !== '';
const hasLinks = referenceLinks.length > 0;
const hasHtml = inputContent && inputContent.trim() !== '';
if (hasFolder || (hasLinks && hasHtml)) {
btnText.textContent = '📋 목차 확인하기';
} else {
btnText.textContent = '🚀 생성하기';
}
}
async function generate() {
const type = docTypes.find(t => t.id === currentDocType);
if (!type) return;
// ★ 입력 타입 자동 판별
const hasFolder = folderPath && folderPath.trim() !== '';
const hasLinks = referenceLinks.length > 0;
const hasHtml = inputContent && inputContent.trim() !== '';
if (hasFolder || (hasLinks && hasHtml)) {
// 입력 1,2,3: 폴더/링크 → 파이프라인 → 목차 → 생성
await generateDraft();
} else if (hasHtml) {
// 입력 4: HTML → 형식 변환
await generateBriefing();
} else {
alert('먼저 폴더 위치, 참고 링크, 또는 HTML을 입력해주세요.');
}
}
async function generateBriefing() {
if (!inputContent && !folderPath && referenceLinks.length === 0) {
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();
updateStep(0, 'done');
setStatus('생성 중...', true);
try {
for (let i = 1; i <= 7; i++) {
updateStep(i, 'running');
await new Promise(r => setTimeout(r, 200));
updateStep(i, 'done');
}
updateStep(8, 'running');
// 페이지 구성 값 가져오기
let pageOption = '2'; // 기본값
if (currentPageConfig === 'body-only') {
pageOption = '1';
} else if (currentPageConfig === 'body-attach') {
pageOption = String(1 + attachPageCount); // 본문 1p + 첨부 np
}
const formData = new FormData();
formData.append('content', inputContent);
formData.append('doc_type', currentDocType);
formData.append('page_option', pageOption);
formData.append('attach_pages', attachPageCount);
formData.append('instruction', document.getElementById('globalInstructionInput').value);
formData.append('write_mode', currentWriteMode);
formData.append('folder_path', folderPath || '');
formData.append('links', JSON.stringify(referenceLinks || []));
const response = await fetch('/generate', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
updateStep(8, 'done');
updateStep(9, 'running');
await new Promise(r => setTimeout(r, 300));
updateStep(9, 'done');
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);
for (let i = 0; i <= 9; i++) {
const item = document.querySelector(`.step-item[data-step="${i}"]`);
if (item && item.classList.contains('running')) {
updateStep(i, 'error');
}
}
} finally {
btn.disabled = false;
btnText.textContent = '🚀 생성하기';
spinner.style.display = 'none';
}
}
async function generateDraft() {
if (!folderPath && !inputContent && referenceLinks.length === 0) {
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();
updateStep(0, 'done');
setStatus('목차 생성 중...', true);
try {
const hasFolder = folderPath && folderPath.trim() !== '';
const hasLinks = referenceLinks && referenceLinks.length > 0;
const hasHtml = inputContent && inputContent.trim() !== '';
const needsPipeline = hasFolder || (hasLinks && hasHtml);
if (needsPipeline) {
// 파이프라인 모드: 서버에서 step3~7 실행
updateStep(1, 'running');
const tocResp = await fetch('/api/generate-toc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
folder_path: folderPath,
links: referenceLinks,
domain_selected: selectedDomains && selectedDomains.length > 0,
write_mode: currentWriteMode
})
});
const tocData = await tocResp.json();
if (!tocData.success) {
throw new Error(tocData.error || '목차 생성 실패');
}
// step 1~7 완료 표시
for (let i = 1; i <= 7; i++) updateStep(i, 'done');
// 목차 애니메이션 표시
if (tocData.toc_items && typeof displayTocWithAnimation === 'function') {
displayTocWithAnimation(tocData.toc_items);
}
} else {
// HTML 입력 모드: 가짜 진행 (기존)
for (let i = 1; i <= 7; i++) {
updateStep(i, 'running');
await new Promise(r => setTimeout(r, 500));
updateStep(i, 'done');
}
}
document.getElementById('feedbackBar').classList.remove('show');
setStatus('목차 생성 완료 - 확인 후 승인해주세요', true);
} catch (error) {
alert('목차 생성 오류: ' + error.message);
setStatus('오류 발생', false);
} finally {
btn.disabled = false;
btnText.textContent = '📋 목차 확인하기';
spinner.style.display = 'none';
}
}
// ===== 목차 애니메이션 표시 =====
function displayTocWithAnimation(tocItems) {
const tocDisplay = document.getElementById('tocDisplayArea');
if (!tocDisplay) return;
tocDisplay.style.display = 'block';
tocDisplay.innerHTML = '';
tocItems.forEach((item, index) => {
const tocEl = document.createElement('div');
tocEl.className = 'toc-anim-item';
tocEl.style.opacity = '0';
tocEl.style.transform = 'translateY(12px)';
// 키워드 HTML 생성
const keywordsHtml = (item.keywords || [])
.map(k => `<span class="toc-anim-keyword">${k}</span>`)
.join('');
const contentsHtml = (item.contents || [])
.map(c => `<div class="toc-anim-content-item">${c}</div>`)
.join('');
tocEl.innerHTML = `
<div class="toc-anim-num">${item.num || (index + 1) + '장'}</div>
<div class="toc-anim-title">${item.title}</div>
${item.guide ? `<div class="toc-anim-guide">${item.guide}</div>` : ''}
${contentsHtml ? `<div class="toc-anim-contents">${contentsHtml}</div>` : ''}
${keywordsHtml ? `<div class="toc-anim-keywords">${keywordsHtml}</div>` : ''}
`;
tocDisplay.appendChild(tocEl);
// 순차적으로 나타남
setTimeout(() => {
tocEl.style.transition = 'all 0.4s ease';
tocEl.style.opacity = '1';
tocEl.style.transform = 'translateY(0)';
}, 300 + (index * 600));
});
// 모든 항목 표시 후 액션바 표시
const totalDelay = 300 + (tocItems.length * 600) + 400;
setTimeout(() => {
document.getElementById('tocActionBar').classList.add('show');
document.getElementById('feedbackBar').classList.remove('show');
setStatus('목차 생성 완료 - 확인 후 승인해주세요', true);
}, totalDelay);
}
// ===== 목차 표시 초기화 =====
function hideTocDisplay() {
const tocDisplay = document.getElementById('tocDisplayArea');
if (tocDisplay) {
tocDisplay.style.display = 'none';
tocDisplay.innerHTML = '';
}
}
function editToc() {
const tocContainer = document.getElementById('tocContainer');
if (tocContainer) {
tocContainer.contentEditable = true;
tocContainer.style.outline = '2px solid var(--ui-accent)';
}
// 애니메이션 목차도 편집 가능하게
const tocDisplay = document.getElementById('tocDisplayArea');
if (tocDisplay && tocDisplay.style.display !== 'none') {
tocDisplay.contentEditable = true;
tocDisplay.style.outline = '2px solid var(--ui-accent)';
}
setStatus('목차 편집 모드 - 직접 수정 가능합니다', true);
}
async function approveToc() {
const btn = document.getElementById('approveBtn');
btn.disabled = true;
btn.textContent = '⏳ 생성 중...';
document.getElementById('tocActionBar').classList.remove('show');
hideTocDisplay();
setStatus('최종 문서 생성 중...', true);
// 진행률 표시
const progressArea = document.getElementById('genProgressArea');
if (progressArea) {
progressArea.style.display = 'block';
}
try {
// Step 8: 콘텐츠 생성
updateStep(8, 'running');
updateGenProgress(10, '📚 RAG 검색 중...');
await generateReport();
updateGenProgress(100, '✅ 생성 완료');
updateStep(8, 'done');
updateStep(9, 'running');
await new Promise(r => setTimeout(r, 300));
updateStep(9, 'done');
// 진행률 숨기기
setTimeout(() => {
if (progressArea) progressArea.style.display = 'none';
}, 1000);
} catch (error) {
alert('문서 생성 오류: ' + error.message);
setStatus('오류 발생', false);
if (progressArea) progressArea.style.display = 'none';
} finally {
btn.disabled = false;
btn.textContent = '✅ 승인 & 생성하기';
}
}
// ===== 생성 진행률 업데이트 =====
function updateGenProgress(percent, message) {
const bar = document.getElementById('genProgressBar');
const text = document.getElementById('genProgressText');
if (bar) bar.style.width = percent + '%';
if (text) text.textContent = message;
}
// === generateReport() 함수 전체 교체 ===
async function generateReport() {
const coverCheck = document.getElementById('cover');
const tocCheck = document.getElementById('toc');
const dividerCheck = document.getElementById('divider');
updateGenProgress(20, '📋 목차 기반 구조화 중...');
// ★ 입력1,2,3일 때 (폴더/링크 있으면)
const hasFolder = folderPath && folderPath.trim() !== '';
const hasLinks = referenceLinks && referenceLinks.length > 0;
const hasHtml = inputContent && inputContent.trim() !== '';
if (hasFolder || (hasLinks && hasHtml)) {
// 편집된 목차 수집
const editedToc = collectEditedToc();
const response = await fetch('/api/generate-report-from-toc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
toc_items: editedToc,
write_mode: currentWriteMode,
instruction: document.getElementById('globalInstructionInput').value,
cover: coverCheck ? coverCheck.checked : true,
toc: tocCheck ? tocCheck.checked : true,
})
});
updateGenProgress(60, '📝 본문 작성 중...');
const data = await response.json();
if (data.error) throw new Error(data.error);
updateGenProgress(85, '🎨 레이아웃 조립 중...');
await new Promise(r => setTimeout(r, 500));
if (data.success && data.html) {
generatedHTML = data.html;
showGeneratedHtml(generatedHTML);
}
} else {
const response = await fetch('/generate-report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: inputContent,
folder_path: folderPath,
cover: coverCheck ? coverCheck.checked : true,
toc: tocCheck ? tocCheck.checked : true,
divider: dividerCheck ? dividerCheck.checked : false,
instruction: document.getElementById('globalInstructionInput').value
})
});
updateGenProgress(60, '📝 본문 작성 중...');
const data = await response.json();
if (data.error) throw new Error(data.error);
updateGenProgress(85, '🎨 레이아웃 조립 중...');
await new Promise(r => setTimeout(r, 500));
if (data.success && data.html) {
generatedHTML = data.html;
showGeneratedHtml(generatedHTML);
}
}
}
// ★ 공통 HTML 표시 함수 추가
function showGeneratedHtml(html) {
document.getElementById('placeholder').style.display = 'none';
const frame = document.getElementById('previewFrame');
frame.classList.add('active');
frame.srcdoc = html;
setTimeout(setupIframeSelection, 500);
document.getElementById('feedbackBar').classList.add('show');
updateGenProgress(100, '✅ 생성 완료');
setStatus('생성 완료', true);
}
// ★ 편집된 목차 수집 함수 추가
function collectEditedToc() {
const tocDisplay = document.getElementById('tocDisplayArea');
if (!tocDisplay) return [];
const items = [];
tocDisplay.querySelectorAll('.toc-anim-item').forEach(el => {
items.push({
num: el.querySelector('.toc-anim-num')?.textContent || '',
title: el.querySelector('.toc-anim-title')?.textContent || '',
guide: el.querySelector('.toc-anim-guide')?.textContent || '',
keywords: Array.from(el.querySelectorAll('.toc-anim-keyword'))
.map(k => k.textContent)
});
});
return items;
}
// ===== 피드백 =====
async function submitFeedback() {
const feedback = document.getElementById('feedbackInput').value.trim();
if (!feedback) {
alert('수정 내용을 입력해주세요.');
return;
}
if (!generatedHTML) {
alert('먼저 문서를 생성해주세요.');
return;
}
const btn = document.getElementById('feedbackBtn');
const btnText = document.getElementById('feedbackBtnText');
const spinner = document.getElementById('feedbackSpinner');
btn.disabled = true;
btnText.textContent = '⏳ 수정 중...';
spinner.style.display = 'inline-block';
setStatus('수정 중...', true);
try {
const response = await fetch('/refine', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
feedback: feedback,
current_html: generatedHTML
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
if (data.success && data.html) {
generatedHTML = data.html;
document.getElementById('previewFrame').srcdoc = generatedHTML;
document.getElementById('feedbackInput').value = '';
setTimeout(setupIframeSelection, 500);
setStatus('수정 완료', true);
}
} catch (error) {
alert('수정 오류: ' + error.message);
setStatus('오류 발생', false);
} finally {
btn.disabled = false;
btnText.textContent = '🔄 수정 반영';
spinner.style.display = 'none';
}
}
function regenerate() {
if (confirm('현재 결과를 버리고 다시 생성하시겠습니까?')) {
hideTocDisplay();
generate();
}
}