📦 Initialize Geulbeot structure and merge Prompts & test projects
This commit is contained in:
484
03. Code/geulbeot_10th/static/js/generator.js
Normal file
484
03. Code/geulbeot_10th/static/js/generator.js
Normal file
@@ -0,0 +1,484 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user