/** * 글벗 Light v2.1 — 시연용 데모 모드 * * 배치: * 1. output/result/ 폴더에 HTML 파일: * - report.html, brief_1.html, brief_2.html, slide.html * 2. index.html에 generator.js 뒤에 추가: * * 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 = `
🔄 ${typeName} 변환 중
원본 콘텐츠 분석
문서 구조 재설계
${typeName} 형식 적용
레이아웃 최적화
최종 퍼블리싱
준비 중...
`; 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() 패치 완료'); }); }