Files

371 lines
16 KiB
JavaScript

/**
* 글벗 Light v2.1 — 시연용 데모 모드
*
* 배치:
* 1. output/result/ 폴더에 HTML 파일:
* - report.html, brief_1.html, brief_2.html, slide.html
* 2. index.html에 generator.js 뒤에 추가:
* <script src="/static/js/demo_mode.js"></script>
* 3. 시연 후 DEMO_MODE = false
*/
const DEMO_MODE = true;
// ===== 가짜 목차 데이터 (보고서 6장) =====
const DEMO_TOC_ITEMS = [
{
num: '1장',
title: '한국 토목 엔지니어링 소프트웨어 시장 현황',
guide: '국내 토목 설계 시장의 AutoCAD 독점 구조와 시장 점유율 현황을 분석하고, 독점적 지위의 배경과 그로 인한 구조적 문제점을 도출한다.',
keywords: ['시장 점유율', 'AutoCAD 독점', '라이선스 비용', '기술 종속'],
contents: [
'📊 국내 CAD 소프트웨어 시장 점유율 비교표 (핵심역량강화방안.hwp p.8, 새로운시대준비된우리.hwp p.3)',
'📊 연간 라이선스 비용 추이표 (핵심역량강화방안.hwp p.12)'
]
},
{
num: '2장',
title: 'AutoCAD, 토목설계에 정말 적합한가?',
guide: '건축과 토목의 근본적 차이를 분석하고, AutoCAD의 토목 분야 적용 시 기능적 한계와 기술적 비효율을 검증한다.',
keywords: ['레고와 찰흙', '비정형 형상', '데이터 단절', 'BIM 부조화'],
contents: [
'📊 건축 vs 토목 설계 특성 비교표 (핵심역량강화방안.hwp p.15)',
'🖼️ 비정형 지형 모델링 사례 이미지 (발표자료.pptx p.7)',
'📊 측량→설계→시공 데이터 흐름도 (새로운시대준비된우리.hwp p.11)'
]
},
{
num: '3장',
title: '시장의 족쇄: 관행인가, 필수인가?',
guide: '익숙함의 함정과 전환 비용 인식, 라이선스 비용 압박 구조를 분석하여 독점 유지 메커니즘의 실체를 파악한다.',
keywords: ['익숙함의 함정', '삼중 잠금', '비용 압박', '기술적 우위 허상'],
contents: [
'📊 삼중 잠금 효과 구조도 (핵심역량강화방안.hwp p.22)',
'📊 AutoCAD vs 대안 SW 기능 비교표 (발표자료.pptx p.12, 핵심역량강화방안.hwp p.25)'
]
},
{
num: '4장',
title: '지식재산권: 문제점과 해결 방안',
guide: '비공개 DWG 포맷으로 인한 성과물 소유권 왜곡, 기술 종속, 데이터 주권 침해 문제를 분석하고 해결 방안을 제시한다.',
keywords: ['DWG 종속', '성과물 소유권', '데이터 주권', '개방형 포맷'],
contents: [
'📊 DWG vs 개방형 포맷 비교표 (핵심역량강화방안.hwp p.30)',
'🖼️ 데이터 종속 구조 다이어그램 (발표자료.pptx p.15)',
'📊 공공조달 포맷 현황표 (새로운시대준비된우리.hwp p.18)'
]
},
{
num: '5장',
title: '새로운 가능성: 대안을 찾아서',
guide: '토목 엔지니어의 핵심 요구사항을 정리하고, 시장의 대안 소프트웨어 옵션 및 국산 솔루션 개발의 전략적 중요성을 분석한다.',
keywords: ['핵심 요구사항', 'Civil 3D', 'OpenRoads', '국산 솔루션'],
contents: [
'📊 대안 소프트웨어 기능·비용 비교표 (핵심역량강화방안.hwp p.35, 발표자료.pptx p.18)',
'📊 단계별 전환 로드맵 (새로운시대준비된우리.hwp p.22)',
'🖼️ 국산 솔루션 아키텍처 구성도 (발표자료.pptx p.20)'
]
},
{
num: '6장',
title: '결론 및 시사점',
guide: '분석 결과를 종합하여 단계적 전환 로드맵을 제시하고, 비용 절감·데이터 주권 확보·기술 경쟁력 강화의 기대효과를 도출한다.',
keywords: ['전환 로드맵', '비용 절감', '데이터 주권', '기술 경쟁력'],
contents: [
'📊 기대효과 종합표 (핵심역량강화방안.hwp p.40, 새로운시대준비된우리.hwp p.25)',
'📊 Q1~Q4 실행 일정표 (발표자료.pptx p.22)'
]
}
];
// ===== 문서 유형별 HTML 경로 =====
const DEMO_HTML_MAP = {
'report': '/static/result/report.html',
'briefing_1': '/static/result/brief_1.html',
'briefing_2': '/static/result/brief_2.html',
'slide': '/static/result/slide.html'
};
// ===== fetch 인터셉터 =====
if (DEMO_MODE) {
const _originalFetch = window.fetch;
window.fetch = async function(url, options) {
// --- /api/doc-types → presentation 활성화 ---
if (typeof url === 'string' && url.includes('/api/doc-types') && !options) {
const resp = await _originalFetch(url, options);
const data = await resp.json();
// presentation enabled로 변경
const pres = data.find(t => t.id === 'presentation');
if (pres) { pres.enabled = true; pres.badge = ''; }
return { ok: true, json: async () => data };
}
// --- /api/generate-toc → 가짜 목차 반환 ---
if (typeof url === 'string' && url.includes('/api/generate-toc')) {
console.log('[DEMO] 목차 생성 인터셉트');
await new Promise(r => setTimeout(r, 800));
return {
ok: true,
json: async () => ({
success: true,
toc_items: DEMO_TOC_ITEMS
})
};
}
// --- /api/generate-report-from-toc → 데모 HTML 반환 ---
if (typeof url === 'string' && url.includes('/api/generate-report-from-toc')) {
console.log('[DEMO] 보고서 생성 인터셉트');
const docType = window.currentDocType || 'report';
const htmlPath = _resolveHtmlPath(docType);
try {
const resp = await _originalFetch(htmlPath);
const html = await resp.text();
return { ok: true, json: async () => ({ success: true, html }) };
} catch (e) {
console.error('[DEMO] HTML 로드 실패:', htmlPath, e);
return { ok: true, json: async () => ({ success: false, error: '데모 HTML 로드 실패' }) };
}
}
// --- 그 외: 원래 fetch ---
return _originalFetch(url, options);
};
console.log('[DEMO] ✅ 데모 모드 활성화');
}
// ===== HTML 경로 결정 =====
function _resolveHtmlPath(docType) {
if (docType === 'report' || (docType && docType.includes('보고'))) {
return DEMO_HTML_MAP['report'];
}
if (docType === 'slide' || docType === 'ppt' || docType === 'presentation' || (docType && (docType.includes('발표') || docType.includes('slide')))) {
return DEMO_HTML_MAP['slide'];
}
if (docType === 'briefing' || (docType && docType.includes('기획'))) {
// briefing_1 vs briefing_2 판별: currentPageConfig 또는 기본값
const pageConfig = window.currentPageConfig || '';
if (pageConfig === 'body-only') {
return DEMO_HTML_MAP['briefing_1'];
}
return DEMO_HTML_MAP['briefing_2'];
}
return DEMO_HTML_MAP['report'];
}
// ===== 문서 유형 전환 팝업 =====
async function demoConvertDocument(targetType) {
if (!DEMO_MODE) return false;
const typeNames = {
'report': '📄 보고서',
'briefing': '📋 기획서',
'briefing_1': '📋 기획서 (1p)',
'briefing_2': '📋 기획서 (2p)',
'slide': '📊 발표자료',
'ppt': '📊 발표자료'
};
const typeName = typeNames[targetType] || targetType;
const overlay = document.createElement('div');
overlay.id = 'demoConvertOverlay';
overlay.innerHTML = `
<div class="demo-convert-popup">
<div class="demo-convert-header">
<span class="demo-convert-icon">🔄</span>
<span>${typeName} 변환 중</span>
</div>
<div class="demo-convert-steps" id="demoSteps">
<div class="demo-step"><span class="demo-step-icon">⏳</span><span>원본 콘텐츠 분석</span></div>
<div class="demo-step"><span class="demo-step-icon">⏳</span><span>문서 구조 재설계</span></div>
<div class="demo-step"><span class="demo-step-icon">⏳</span><span>${typeName} 형식 적용</span></div>
<div class="demo-step"><span class="demo-step-icon">⏳</span><span>레이아웃 최적화</span></div>
<div class="demo-step"><span class="demo-step-icon">⏳</span><span>최종 퍼블리싱</span></div>
</div>
<div class="demo-convert-progress">
<div class="demo-progress-bar" id="demoProgressBar"></div>
</div>
<div class="demo-convert-status" id="demoStatus">준비 중...</div>
</div>
`;
document.body.appendChild(overlay);
const steps = overlay.querySelectorAll('.demo-step');
const progressBar = overlay.querySelector('#demoProgressBar');
const statusEl = overlay.querySelector('#demoStatus');
const msgs = [
'원본 콘텐츠를 분석하고 있습니다...',
'문서 구조를 재설계하고 있습니다...',
`${typeName} 형식을 적용하고 있습니다...`,
'레이아웃을 최적화하고 있습니다...',
'최종 퍼블리싱 중입니다...'
];
for (let i = 0; i < steps.length; i++) {
steps[i].querySelector('.demo-step-icon').textContent = '🔄';
steps[i].classList.add('running');
statusEl.textContent = msgs[i];
progressBar.style.width = ((i + 1) / steps.length * 100) + '%';
await new Promise(r => setTimeout(r, 600 + Math.random() * 400));
steps[i].querySelector('.demo-step-icon').textContent = '✅';
steps[i].classList.remove('running');
steps[i].classList.add('done');
}
// 발표자료면 16:9 비율로 전환, 아니면 A4 복원
const a4Preview = document.getElementById('a4Preview');
const a4Wrapper = document.getElementById('a4Wrapper');
if (a4Preview && a4Wrapper) {
if (targetType === 'slide' || targetType === 'presentation') {
a4Preview.style.width = '1100px';
a4Preview.style.height = '620px';
a4Preview.style.aspectRatio = '16/9';
a4Wrapper.style.width = '1100px';
} else {
a4Preview.style.width = '';
a4Preview.style.height = '';
a4Preview.style.aspectRatio = '';
a4Wrapper.style.width = '';
}
}
statusEl.textContent = '✅ 변환 완료!';
await new Promise(r => setTimeout(r, 500));
// HTML 로드 & 프리뷰 표시
const htmlPath = _resolveHtmlPath(targetType);
try {
const resp = await fetch(htmlPath);
const html = await resp.text();
window.generatedHTML = html;
if (typeof generatedHTML !== 'undefined') generatedHTML = html;
const placeholder = document.getElementById('placeholder');
if (placeholder) placeholder.style.display = 'none';
const frame = document.getElementById('previewFrame');
if (frame) {
frame.classList.add('active');
frame.srcdoc = html;
setTimeout(setupIframeSelection, 500);
}
const feedbackBar = document.getElementById('feedbackBar');
if (feedbackBar) feedbackBar.classList.add('show');
if (typeof setStatus === 'function') setStatus('생성 완료', true);
} catch (e) {
console.error('[DEMO] 변환 HTML 로드 실패:', e);
}
overlay.classList.add('fade-out');
setTimeout(() => overlay.remove(), 300);
return true;
}
// ===== 팝업 CSS 주입 (배지 없음) =====
if (DEMO_MODE) {
const style = document.createElement('style');
style.textContent = `
#demoConvertOverlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6); backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
z-index: 99999; animation: demoFadeIn 0.2s ease;
}
#demoConvertOverlay.fade-out { animation: demoFadeOut 0.3s ease forwards; }
.demo-convert-popup {
background: #1e1e2e; color: #e0e0e0;
border-radius: 16px; padding: 35px 40px;
width: 420px; box-shadow: 0 20px 60px rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.1);
}
.demo-convert-header {
font-size: 18pt; font-weight: 800; margin-bottom: 25px;
display: flex; align-items: center; gap: 12px; color: #fff;
}
.demo-convert-icon { font-size: 22pt; }
.demo-convert-steps { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.demo-step {
display: flex; align-items: center; gap: 12px;
padding: 10px 14px; border-radius: 8px;
background: rgba(255,255,255,0.03);
font-size: 11pt; color: #d1d1d1; transition: all 0.3s ease;
}
.demo-step.running { background: rgba(59,130,246,0.15); color: #93c5fd; font-weight: 600; }
.demo-step.done { background: rgba(34,197,94,0.1); color: #86efac; }
.demo-step-icon { font-size: 14pt; min-width: 24px; text-align: center; }
.demo-convert-progress {
height: 4px; background: rgba(255,255,255,0.1);
border-radius: 2px; overflow: hidden; margin-bottom: 12px;
}
.demo-progress-bar {
height: 100%; width: 0%;
background: linear-gradient(90deg, #3b82f6, #22c55e);
border-radius: 2px; transition: width 0.5s ease;
}
.demo-convert-status { font-size: 10pt; color: #cfcfcf; text-align: center; }
@keyframes demoFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes demoFadeOut { from { opacity: 1; } to { opacity: 0; } }
`;
document.head.appendChild(style);
}
// ===== generate() 패치 — 문서 유형 전환 시 팝업 =====
if (DEMO_MODE) {
let _lastDemoKey = null;
window.addEventListener('DOMContentLoaded', () => {
const _origGenerate = window.generate;
if (typeof _origGenerate !== 'function') {
console.warn('[DEMO] generate() 함수를 찾을 수 없음 — 패치 스킵');
return;
}
window.generate = async function() {
const docType = window.currentDocType || 'report';
const pageConfig = window.currentPageConfig || '';
const demoKey = docType + '|' + pageConfig;
// 이미 결과 있으면 → 데모 전환
if (window.generatedHTML && window.generatedHTML.trim() !== '') {
// 완전히 같은 조합이면 원래 흐름
if (_lastDemoKey === demoKey) {
return _origGenerate();
}
let demoType = docType;
if (docType === 'presentation') demoType = 'slide';
await demoConvertDocument(demoType);
_lastDemoKey = demoKey;
return;
}
// 첫 생성 → 원래 흐름 (fetch 인터셉터가 처리)
_lastDemoKey = demoKey;
return _origGenerate();
};
console.log('[DEMO] ✅ generate() 패치 완료');
});
}