484 lines
20 KiB
JavaScript
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();
|
|
}
|
|
} |