Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> | |
| <meta name="theme-color" content="#0052a4"> | |
| <title>[제안 테스트] JGOS · 행정 AI 서비스 특화 모델</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link rel="stylesheet" href="/static/style.css?v=20260612a"> | |
| <script src="https://cdn.jsdelivr.net/npm/marked@14.1.3/marked.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js"></script> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/github.min.css"> | |
| <script src="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script> | |
| <style id="ax-modal-fix">#ax-modal[hidden]{display:none ;}</style> | |
| </head> | |
| <body> | |
| <div id="proposal-banner" style="background:#b91c1c;color:#fff;text-align:center;padding:7px 12px;font-size:13px;font-weight:600;line-height:1.45;position:relative;z-index:9999;">⚠️ 본 서비스는 <b>제안용 테스트(시범) 버전</b>입니다 — 실제 행정 서비스가 아니며, 응답의 정확성을 보장하지 않습니다.</div> | |
| <div id="app"> | |
| <!-- ===== 모바일 햄버거 ===== --> | |
| <button id="hamburger-btn" aria-label="메뉴 열기"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <line x1="3" y1="6" x2="21" y2="6"/> | |
| <line x1="3" y1="12" x2="21" y2="12"/> | |
| <line x1="3" y1="18" x2="21" y2="18"/> | |
| </svg> | |
| </button> | |
| <!-- ===== 사이드바 ===== --> | |
| <aside id="sidebar"> | |
| <div class="sidebar-head"> | |
| <div class="brand-top"> | |
| <div class="emblem">🏛️</div> | |
| <div> | |
| <div class="brand-title">전남광주 통합특별시</div> | |
| <div class="brand-sub">시민 AI 안내 서비스</div> | |
| </div> | |
| </div> | |
| <button id="new-chat-btn" title="새 대화">+ 새 상담</button> | |
| </div> | |
| <div class="sidebar-section-label">최근 상담</div> | |
| <ul id="chat-list"></ul> | |
| <div class="sidebar-foot"> | |
| <div class="brand-small"> | |
| <strong>Powered by @비드래프트</strong> | |
| <small>JGOS v0.1 preview</small> | |
| </div> | |
| </div> | |
| </aside> | |
| <main id="main"> | |
| <!-- ===== 상단 시민포털 헤더 ===== --> | |
| <header id="topbar"> | |
| <div class="topbar-left"> | |
| <button id="sidebar-toggle" class="mobile-only" aria-label="사이드바">☰</button> | |
| <div class="title-block"> | |
| <div class="title-main">🏛️ JGOS · 전남광주 통합특별시민과 함께하는 AI</div> | |
| <div class="title-sub">320만 시민을 위한 한국어 sovereign AI · 행정·법령·관광·민원 안내</div> | |
| </div> | |
| </div> | |
| <nav class="top-tabs"> | |
| <button class="top-tab active" data-view="chat">💬 AI 상담</button> | |
| <button class="top-tab" data-view="office">📑 무료 오피스</button> | |
| <button class="top-tab" data-view="image">🎨 이미지 생성</button> | |
| <button class="top-tab" data-view="edu">🎓 AI 교육</button> | |
| <button class="top-tab" data-view="ax">🏛️ 행정 AX(테스트)</button> | |
| </nav> | |
| <div class="topbar-right"> | |
| <span class="status-pill" id="status">● 정상</span> | |
| </div> | |
| </header> | |
| <div id="view-chat"> | |
| <div id="messages-wrap"> | |
| <div id="messages"></div> | |
| <!-- ===== 첫 화면 시민포털 (메시지 0건 시 표시) ===== --> | |
| <div id="portal" class="portal"> | |
| <div class="portal-hero"> | |
| <div class="hero-emblem">🏛️</div> | |
| <h1 class="hero-title">전남광주 통합특별시민과<br>함께하는 AI <strong>JGOS</strong></h1> | |
| <p class="hero-sub">행정·민원·법령·관광·교통·문화·통계·산업·농수산 정보를<br>친절하게 안내해 드립니다. 무엇이 궁금하신가요?</p> | |
| </div> | |
| <!-- ===== 방안1: 히어로 파일 드롭존 ===== --> | |
| <button class="hero-dropzone" id="hero-dropzone" type="button"> | |
| <div class="hd-icon">📎</div> | |
| <div class="hd-main">파일을 끌어다 놓거나 클릭하세요</div> | |
| <div class="hd-sub">HWP · 이미지 · PDF · 엑셀 무엇이든 올리면 <strong>바로 분석</strong>해 드립니다</div> | |
| <div class="hd-badge">🇰🇷 타 AI가 못 읽는 <strong>한글 HWP</strong>도 분석하는 sovereign AI</div> | |
| <!-- 방안2: 지원 형식 아이콘 띠 --> | |
| <div class="fmt-strip"> | |
| <span class="fmt hwp">HWP</span><span class="fmt hwp">HWPX</span> | |
| <span class="fmt pdf">PDF</span><span class="fmt doc">DOCX</span> | |
| <span class="fmt xls">XLSX</span><span class="fmt ppt">PPTX</span> | |
| <span class="fmt csv">CSV</span><span class="fmt img">이미지</span> | |
| <span class="fmt more">+10종</span> | |
| </div> | |
| </button> | |
| <!-- 8개 카테고리 큰 카드 --> | |
| <div class="cat-grid"> | |
| <button class="cat-card" data-q="민원 처리 절차와 필요한 서류를 알려주세요."> | |
| <div class="cat-emoji">📝</div> | |
| <div class="cat-name">민원</div> | |
| <div class="cat-desc">절차·서류·전화</div> | |
| </button> | |
| <button class="cat-card" data-q="전남광주통합특별법의 주요 조항을 알려주세요."> | |
| <div class="cat-emoji">📜</div> | |
| <div class="cat-name">법령</div> | |
| <div class="cat-desc">특별법·조례</div> | |
| </button> | |
| <button class="cat-card" data-q="전남광주 통합특별시 관광 명소를 추천해 주세요."> | |
| <div class="cat-emoji">🗺️</div> | |
| <div class="cat-name">관광</div> | |
| <div class="cat-desc">22 시군 + 5구</div> | |
| </button> | |
| <button class="cat-card" data-q="전남광주 통합특별시 교통편을 안내해 주세요."> | |
| <div class="cat-emoji">🚆</div> | |
| <div class="cat-name">교통</div> | |
| <div class="cat-desc">KTX·공항·페리</div> | |
| </button> | |
| <button class="cat-card" data-q="전남광주 통합특별시의 농수산 특산품을 알려주세요."> | |
| <div class="cat-emoji">🌾</div> | |
| <div class="cat-name">농수산</div> | |
| <div class="cat-desc">GI 특산·수산물</div> | |
| </button> | |
| <button class="cat-card" data-q="전남광주 통합특별시의 주요 산업과 경제 규모를 알려주세요."> | |
| <div class="cat-emoji">🏭</div> | |
| <div class="cat-name">산업</div> | |
| <div class="cat-desc">경제·기업</div> | |
| </button> | |
| <button class="cat-card" data-q="전남광주 통합특별시의 인구 통계와 지역 현황을 알려주세요."> | |
| <div class="cat-emoji">📊</div> | |
| <div class="cat-name">통계</div> | |
| <div class="cat-desc">인구·경제</div> | |
| </button> | |
| <button class="cat-card" data-attach="true"> | |
| <div class="cat-emoji">📎</div> | |
| <div class="cat-name">첨부문서</div> | |
| <div class="cat-desc">HWP·PDF·이미지</div> | |
| </button> | |
| </div> | |
| <!-- 추천 질문 (자주 묻는) --> | |
| <div class="quick-section"> | |
| <div class="quick-label">자주 묻는 질문</div> | |
| <div class="quick-grid"> | |
| <button class="quick-btn" data-q="전남광주 통합특별시장과 광주 5개 구청장, 전남 시장·군수 당선인이 각각 누구인가요?">🏛️ 지자체장 당선인</button> | |
| <button class="quick-btn" data-q="전남광주(광주·전남) 지역구 국회의원 명단을 알려주세요.">🗳️ 지역구 국회의원</button> | |
| <button class="quick-btn" data-q="주민등록증 재발급 절차와 필요한 서류는?">📇 주민등록증 재발급</button> | |
| <button class="quick-btn" data-q="전남광주 통합특별시 인구는?">👥 통합특별시 인구</button> | |
| <button class="quick-btn" data-q="보성 녹차밭 관광 안내 부탁해.">🍃 보성 녹차밭</button> | |
| <button class="quick-btn" data-q="전남광주통합특별법 제6조 내용은?">📜 특별법 제6조</button> | |
| <button class="quick-btn" data-q="전남광주 통합특별시청 대표 전화번호와 부서 안내는?">📞 시청 대표번호</button> | |
| <button class="quick-btn" data-q="여수에서 가볼 만한 곳 3곳만 추천해.">🏖️ 여수 가볼 곳</button> | |
| <button class="quick-btn" data-q="5·18 민주화운동 기록관 위치와 관람 안내는?">🏛️ 5·18 기록관</button> | |
| <button class="quick-btn" data-q="2026년 7월 1일 전남광주 통합특별시 출범의 의미와 일정은?">🎌 7·1 출범</button> | |
| </div> | |
| </div> | |
| <!-- 신뢰 배지 (첫 화면 only — NIA 본문은 composer footer에서 영구 표시) --> | |
| <div class="portal-foot"> | |
| <div class="trust-line"> | |
| <span class="trust-badge">🔒 sovereign AI</span> | |
| <span class="trust-badge">📅 기준일 2026-06-01</span> | |
| <span class="trust-badge">📚 RAG 21,192 청크</span> | |
| <span class="trust-badge">⚖️ 정식 행정 안내 아님 · 062-120 확인 권장</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===== 입력 영역 ===== --> | |
| <footer id="composer"> | |
| <div class="warn-bar" id="warn-bar" hidden> | |
| ✅ 2026년 6월 3일 지방선거 완료 — 민형배 통합특별시장 당선(7월 1일 취임). 단체장·지역구 국회의원 명단 반영 완료. | |
| </div> | |
| <div class="attach-row" id="attach-row"></div> | |
| <div class="input-row"> | |
| <button id="attach-btn" title="파일 첨부 (HWP/HWPX/PDF/DOCX/PPTX/XLSX/XLS/CSV/이미지)" aria-label="파일 첨부">📎</button> | |
| <button id="web-btn" title="웹검색 — 켜면 네이버·웹에서 최신 정보를 함께 검색합니다" aria-label="웹검색 켜기/끄기" aria-pressed="false">🔍</button> | |
| <button id="minwon-btn" title="민원 — 켜면 질문을 민원으로 보고 담당 부서·연락처를 찾아드립니다" aria-label="민원 모드 켜기/끄기" aria-pressed="false">📞</button> | |
| <input type="file" id="file-input" hidden multiple | |
| accept=".hwp,.hwpx,.pdf,.docx,.pptx,.xlsx,.xls,.csv,.txt,.md,.jpg,.jpeg,.png,.gif,.webp"> | |
| <textarea id="input" rows="1" placeholder="질문을 입력하거나 파일을 첨부하세요 (Shift+Enter 줄바꿈)"></textarea> | |
| <button id="send-btn" title="전송 (Enter)" aria-label="전송"> | |
| <svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> | |
| </button> | |
| </div> | |
| <!-- ===== NIA K-AI 1위 출처 문구 (항상 표시 — 모든 화면 하단) ===== --> | |
| <div class="nia-disclaimer"> | |
| 본 모델은 과학기술정보통신부·한국지능정보사회진흥원(NIA) K-AI 리더보드 1위 달성 모델을 기반으로, | |
| 전남광주 통합특별시 관련 공개정보를 추가 학습·최적화한 한국어 특화 모델입니다. | |
| 한국어 처리 능력과 한국 문화·사회·행정·전문 분야에 대한 이해를 바탕으로 | |
| 시민 질의에 정확하고 친절하게 응답합니다. | |
| </div> | |
| </footer> | |
| </div><!-- /view-chat --> | |
| <!-- ===== 무료 오피스 (HanSoo 엔진 흡수 · same-origin iframe 임베디드) ===== --> | |
| <section id="view-office" hidden> | |
| <iframe id="office-frame" title="무료 오피스 (HWP·DOCX·XLSX·PPTX·PDF)" | |
| allow="clipboard-read; clipboard-write"></iframe> | |
| </section> | |
| <!-- ===== JGOS-Image (Qwen-Image-2512 한글 이미지 생성) ===== --> | |
| <section id="view-image" hidden> | |
| <div class="img-studio"> | |
| <div class="img-head"> | |
| <h2>🎨 이미지 생성 <small>한국형 AI 이미지 · 한국 인물·배경 · 영문 텍스트 · Qwen-Image-2512</small></h2> | |
| </div> | |
| <div class="img-examples"> | |
| <button class="img-ex" data-p="전남광주 통합특별시 출범 기념 포스터, 한국 시민들이 함께 모인 장면, 영문 제목 'Better Together', 밝고 희망찬">🎌 출범 포스터</button> | |
| <button class="img-ex" data-p="보성 녹차밭 관광 포스터, 한국 전통 정취, 영문 문구 'Green Healing Tour', 청량하고 시원한">🍃 관광 홍보</button> | |
| <button class="img-ex" data-p="민원 안내 인포그래픽, 깔끔한 한국 행정 디자인, 영문 제목 'Civil Service Guide', 아이콘">📋 민원 안내</button> | |
| <button class="img-ex" data-p="전남 청정 농수산물 홍보, 한국 농촌 배경, 영문 문구 'Fresh Local Food', 풍성한">🌾 특산물</button> | |
| </div> | |
| <div class="img-opts"> | |
| <label class="img-opt-label">📐 비율 | |
| <select id="img-ratio"> | |
| <option value="1:1" selected>⬜ 정사각형 1:1</option> | |
| <option value="3:4">📄 세로 3:4 (포스터)</option> | |
| <option value="9:16">📱 세로 9:16 (모바일)</option> | |
| <option value="4:3">🖼️ 가로 4:3</option> | |
| <option value="16:9">🖥️ 가로 16:9 (배너)</option> | |
| </select> | |
| </label> | |
| </div> | |
| <div class="img-input-row"> | |
| <textarea id="img-prompt" rows="2" placeholder="원하는 이미지를 설명하세요 (예: 여수 밤바다 야경, 영문 문구 'Visit Yeosu')"></textarea> | |
| <button id="img-gen-btn">🎨 생성</button> | |
| </div> | |
| <div id="img-status" class="img-status" hidden></div> | |
| <div id="img-result" class="img-result"></div> | |
| <div class="img-foot">⚖️ AI 생성 이미지 · 공식 행정자료 아님 · 상업적 이용 시 별도 검토</div> | |
| </div> | |
| </section> | |
| <!-- ===== 🎓 AI 교육 (AI 시민 대학) ===== --> | |
| <section id="view-edu" hidden> | |
| <div class="edu-wrap"> | |
| <div class="edu-head"> | |
| <h2>🎓 AI 시민 대학 <small>AI 선생님에게 배우고 바로 실습하세요 · 누구나 무료</small></h2> | |
| </div> | |
| <div class="edu-cat-title">📱 JGOS 100% 활용</div> | |
| <div class="edu-cards"> | |
| <button class="edu-card" data-q="JGOS에서 민원을 어떻게 물어보나요? 처음 쓰는 사람도 알 수 있게 쉽게 알려주세요." data-go="chat">① 민원 물어보기</button> | |
| <button class="edu-card" data-q="JGOS 무료 오피스로 한글(HWP) 문서를 편집하고 저장하는 법을 알려주세요." data-go="office">② HWP 편집</button> | |
| <button class="edu-card" data-q="JGOS 이미지 생성 기능 사용법과 좋은 설명(프롬프트) 쓰는 법을 알려주세요." data-go="image">③ 이미지 생성</button> | |
| <button class="edu-card" data-q="JGOS에 PDF나 사진 파일을 올려서 내용을 분석하는 방법을 알려주세요." data-go="chat">④ 파일 분석</button> | |
| </div> | |
| <div class="edu-cat-title">🤖 AI 기본 소양</div> | |
| <div class="edu-cards"> | |
| <button class="edu-card" data-q="AI가 무엇인지 어르신도 이해하기 쉽게 비유를 들어 설명해주세요.">⑤ AI란 무엇?</button> | |
| <button class="edu-card" data-q="AI에게 질문을 잘 하는 방법(프롬프트)을 쉬운 예시와 함께 알려주세요.">⑥ 질문 잘하기</button> | |
| <button class="edu-card" data-q="AI가 만든 가짜 정보(환각)를 구별하고 조심하는 방법을 알려주세요.">⑦ 가짜정보 판별</button> | |
| <button class="edu-card" data-q="AI를 사용할 때 내 개인정보를 안전하게 지키는 방법을 알려주세요.">⑧ 개인정보 보호</button> | |
| </div> | |
| <div class="edu-cat-title">🌱 생활 맞춤</div> | |
| <div class="edu-cards"> | |
| <button class="edu-card" data-q="컴퓨터가 익숙하지 않은 어르신을 위해 AI를 처음 사용하는 법을 아주 쉽고 천천히 알려주세요.">⑨ 어르신 첫걸음</button> | |
| <button class="edu-card" data-q="소상공인이 AI로 가게 홍보물과 안내 문서를 만드는 방법을 알려주세요.">⑩ 소상공인 홍보</button> | |
| <button class="edu-card" data-q="학생이 AI를 공부와 학습에 올바르고 똑똑하게 활용하는 방법을 알려주세요.">⑪ 학생 학습</button> | |
| <button class="edu-card" data-q="농어민이 AI로 농수산물 홍보·판매와 영농 정보를 활용하는 방법을 알려주세요.">⑫ 농어민 활용</button> | |
| </div> | |
| <div class="edu-teacher"> | |
| <div class="edu-teacher-head">🧑🏫 AI 선생님 — 무엇이든 편하게 물어보세요</div> | |
| <div id="edu-messages" class="edu-messages"></div> | |
| <div id="edu-goto" class="edu-goto" hidden></div> | |
| <div class="edu-input-row"> | |
| <textarea id="edu-input" rows="1" placeholder="예: AI가 뭐예요? / 프롬프트가 뭔가요?"></textarea> | |
| <button id="edu-send">질문</button> | |
| </div> | |
| </div> | |
| <div class="edu-foot">🎓 전남광주 통합특별시 AI 시민 대학 · 모든 시민 무료 · 위 강의를 누르면 AI 선생님이 설명해 드려요</div> | |
| </div> | |
| </section> | |
| <!-- ===== 🏛️ 행정 AX 데이터 통합 (테스트 데모) ===== --> | |
| <section id="view-ax" hidden> | |
| <div class="ax-container"> | |
| <div class="ax-header"> | |
| <h2>🏛️ 행정 AX 데이터 통합 — 테스트 데모</h2> | |
| <p class="ax-desc">이종 파일 포맷·서식의 행정 데이터를 자동으로 인식·정규화·통합합니다</p> | |
| </div> | |
| <!-- Demo 1: 이종 Excel 통합 --> | |
| <div class="ax-demo-card"> | |
| <div class="ax-demo-header"> | |
| <span class="ax-demo-badge">Demo 1</span> | |
| <h3>📊 이종 Excel 자동 통합</h3> | |
| <p>27개 지자체가 제각각 다른 서식으로 제출한 Excel을 자동으로 표준화·통합합니다</p> | |
| </div> | |
| <div class="ax-demo-files"> | |
| <div class="ax-files-label">📁 예제 파일 (클릭하여 미리보기)</div> | |
| <div class="ax-file-icons" id="ax-excel-demos"><!-- JS로 채움 --></div> | |
| </div> | |
| <div class="ax-upload-zone" id="ax-excel-drop"> | |
| <div class="ax-upload-inner"> | |
| <span class="ax-upload-icon">📤</span> | |
| <div>Excel/CSV 파일을 여기에 드래그하거나 클릭하세요</div> | |
| <div class="ax-upload-hint">XLSX, XLS, CSV — 여러 파일 동시 업로드 가능</div> | |
| </div> | |
| <input type="file" id="ax-excel-input" hidden multiple accept=".xlsx,.xls,.csv"> | |
| </div> | |
| <div class="ax-actions"> | |
| <button class="ax-btn ax-btn-primary" id="ax-excel-run" disabled> | |
| <span>⚡</span> 통합 실행 | |
| </button> | |
| <span class="ax-file-count" id="ax-excel-count"></span> | |
| </div> | |
| <div class="ax-result-area" id="ax-excel-result" hidden></div> | |
| </div> | |
| <!-- Demo 2: 이종 포맷 통합 --> | |
| <div class="ax-demo-card"> | |
| <div class="ax-demo-header"> | |
| <span class="ax-demo-badge ax-demo-badge-2">Demo 2</span> | |
| <h3>🗂️ 이종 포맷 자동 통합</h3> | |
| <p>HWP, XLSX, CSV, PPTX, DOCX, PDF — 7가지 다른 포맷의 데이터를 하나의 표로 통합합니다</p> | |
| </div> | |
| <div class="ax-demo-files"> | |
| <div class="ax-files-label">📁 예제 파일 7종 (클릭하여 미리보기)</div> | |
| <div class="ax-file-icons" id="ax-format-demos"><!-- JS로 채움 --></div> | |
| </div> | |
| <div class="ax-upload-zone" id="ax-format-drop"> | |
| <div class="ax-upload-inner"> | |
| <span class="ax-upload-icon">📤</span> | |
| <div>이종 포맷 파일을 여기에 드래그하거나 클릭하세요</div> | |
| <div class="ax-upload-hint">HWPX, XLSX, CSV, PPTX, DOCX, PDF — 혼합 업로드 가능</div> | |
| </div> | |
| <input type="file" id="ax-format-input" hidden multiple accept=".hwp,.hwpx,.xlsx,.xls,.csv,.docx,.pptx,.pdf"> | |
| </div> | |
| <div class="ax-actions"> | |
| <button class="ax-btn ax-btn-primary ax-btn-2" id="ax-format-run" disabled> | |
| <span>⚡</span> 통합 실행 | |
| </button> | |
| <span class="ax-file-count" id="ax-format-count"></span> | |
| </div> | |
| <div class="ax-result-area" id="ax-format-result" hidden></div> | |
| </div> | |
| </div> | |
| <!-- 파일 미리보기 모달 --> | |
| <div id="ax-modal" class="ax-modal-overlay" hidden> | |
| <div class="ax-modal-box"> | |
| <div class="ax-modal-header"> | |
| <span id="ax-modal-title">파일 미리보기</span> | |
| <button class="ax-modal-close" id="ax-modal-close">✕</button> | |
| </div> | |
| <div class="ax-modal-body" id="ax-modal-body"> | |
| <div class="ax-loading">⏳ 불러오는 중...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- 모바일 사이드바 백드롭 --> | |
| <div id="sidebar-backdrop" hidden></div> | |
| </div> | |
| <div id="drag-overlay" hidden> | |
| <div class="drop-card"> | |
| 📂 여기에 파일을 놓으세요 | |
| <small>HWP · HWPX · PDF · DOCX · PPTX · XLSX · CSV · 이미지<br>(ESC 또는 클릭으로 닫기)</small> | |
| </div> | |
| </div> | |
| <script src="/static/jgos_app_v611.js?v=612"></script> | |
| <script> | |
| /* JGOS AX 미리보기 — 인라인 (캐시 우회, v611-INLINE) */ | |
| window.axPreviewDemo = async function(filename, label){ | |
| var modal=document.getElementById("ax-modal"); | |
| var body=document.getElementById("ax-modal-body"); | |
| var title=document.getElementById("ax-modal-title"); | |
| if(!modal||!body)return; | |
| if(title)title.textContent="\uD83D\uDCC4 "+label+" \u2014 \uBBF8\uB9AC\uBCF4\uAE30"; | |
| body.innerHTML='<div class="ax-loading">\u23F3 \uBD88\uB7EC\uC624\uB294 \uC911... [INLINE]</div>'; | |
| modal.hidden=false; | |
| try{ | |
| var r=await fetch("/api/demo/preview/"+encodeURIComponent(filename)+"?t="+Date.now()); | |
| if(!r.ok){body.innerHTML="<p style=\'color:#b91c1c\'>\uC11C\uBC84 \uC624\uB958 ("+r.status+")</p>";return;} | |
| var d=await r.json(); | |
| body.innerHTML=(d.rows>0)?(d.html||"<p>\uBE48 \uACB0\uACFC</p>"):"<p style=\'color:#92400e\'>\u26A0\uFE0F \uD30C\uC2F1 \uC2E4\uD328</p>"; | |
| }catch(e){ | |
| body.innerHTML="<p style=\'color:red\'>\uC624\uB958: "+(e&&e.message?e.message:e)+"</p>"; | |
| } | |
| }; | |
| /* 칩 onclick 재바인딩 (외부 js가 이미 그렸어도 인라인 함수로 연결) */ | |
| (function(){ | |
| function rebind(){ | |
| document.querySelectorAll(".ax-file-chip").forEach(function(chip){ | |
| chip.onclick=function(){ window.axPreviewDemo(chip.dataset.demo, chip.dataset.label); }; | |
| }); | |
| } | |
| if(document.readyState!=="loading")setTimeout(rebind,300); | |
| else document.addEventListener("DOMContentLoaded",function(){setTimeout(rebind,300);}); | |
| // 탭 전환 후에도 재바인딩 | |
| document.addEventListener("click",function(e){ | |
| var t=e.target.closest&&e.target.closest('[data-view="ax"]'); | |
| if(t)setTimeout(rebind,400); | |
| }); | |
| })(); | |
| console.log("[JGOS] AX inline preview v611-INLINE loaded"); | |
| </script> | |
| <script> | |
| /* JGOS AX 예제 바로 실행 버튼 — DEMO-RUN-v1 */ | |
| (function(){ | |
| async function runDemoMerge(btn, resultId, demoType){ | |
| var result=document.getElementById(resultId); | |
| var old=btn.innerHTML; btn.disabled=true; btn.innerHTML="\u23F3 \uD1B5\uD569 \uC911..."; | |
| if(result){result.hidden=false;result.innerHTML='<div class="ax-loading">\u23F3 \uC608\uC81C \uD1B5\uD569 \uC911...</div>';} | |
| try{ | |
| var r=await fetch("/api/demo/merge/"+demoType+"?t="+Date.now()); | |
| var d=await r.json(); | |
| if(result){ | |
| var src=(d.sources||[]).map(function(s){return '<span class="ax-source-chip">'+(s["\uD3EC\uB9F7"]||"")+' '+s["\uD30C\uC77C"]+' ('+s["\uAC74\uC218"]+'\uAC74)</span>';}).join(""); | |
| result.innerHTML='<div class="ax-source-list">'+src+'</div>'+(d.html||""); | |
| } | |
| }catch(e){if(result)result.innerHTML="<p style=\'color:red\'>\uC624\uB958: "+(e&&e.message?e.message:e)+"</p>";} | |
| btn.disabled=false; btn.innerHTML=old; | |
| } | |
| function addBtn(runBtnId, resultId, demoType){ | |
| var runBtn=document.getElementById(runBtnId); | |
| if(!runBtn) return; | |
| if(document.getElementById(runBtnId+"-demo")) return; | |
| var btn=document.createElement("button"); | |
| btn.id=runBtnId+"-demo"; | |
| btn.className=runBtn.className; | |
| btn.style.background="#0d7377"; | |
| btn.style.marginLeft="8px"; | |
| btn.innerHTML="\uD83D\uDCC2 \uC608\uC81C\uB85C \uBC14\uB85C \uD1B5\uD569"; | |
| runBtn.parentNode.insertBefore(btn, runBtn.nextSibling); | |
| btn.onclick=function(){ runDemoMerge(btn, resultId, demoType); }; | |
| } | |
| function setup(){ | |
| addBtn("ax-excel-run","ax-excel-result","excel"); | |
| addBtn("ax-format-run","ax-format-result","formats"); | |
| } | |
| if(document.readyState!=="loading") setTimeout(setup,500); | |
| else document.addEventListener("DOMContentLoaded",function(){setTimeout(setup,500);}); | |
| document.addEventListener("click",function(e){ | |
| var t=e.target.closest&&e.target.closest('[data-view="ax"]'); | |
| if(t) setTimeout(setup,500); | |
| }); | |
| console.log("[JGOS] AX demo-run buttons v1 loaded"); | |
| })(); | |
| </script> | |
| <script> | |
| /* AX-INTEGRATE-v2: 통합 결과 표시 + XLSX 다운로드 */ | |
| (function(){ | |
| function chip(s){return '<span class="ax-source-chip">'+(s["\uD3EC\uB9F7"]||"")+" "+(s["\uD30C\uC77C"]||"")+" ("+(s["\uAC74\uC218"]||0)+"\uAC74)</span>";} | |
| window.__axShow=function(resultId,d){ | |
| var result=document.getElementById(resultId); | |
| if(!result)return; | |
| result.hidden=false; | |
| var src=(d.sources||[]).map(chip).join(""); | |
| result.innerHTML='<div class="ax-source-list">'+src+'</div>'+(d.html||""); | |
| if(d.records&&d.records.length){ | |
| var dl=document.createElement("button"); | |
| dl.style.cssText="background:#1d6f42;color:#fff;margin-top:14px;padding:11px 24px;border:none;border-radius:9px;font-weight:700;cursor:pointer;font-size:0.95rem"; | |
| dl.textContent="\uD83D\uDCE5 \uD1B5\uD569\uBCF8 XLSX \uB2E4\uC6B4\uB85C\uB4DC ("+d.records.length+"\uAC74)"; | |
| dl.onclick=async function(){ | |
| var o=dl.textContent; dl.disabled=true; dl.textContent="\u23F3 \uC0DD\uC131 \uC911..."; | |
| try{ | |
| var rr=await fetch("/api/export/xlsx",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({records:d.records,title:"JGOS \uD589\uC815AX \uD1B5\uD569\uACB0\uACFC"})}); | |
| if(!rr.ok)throw new Error("\uC11C\uBC84 "+rr.status); | |
| var b=await rr.blob(); var u=URL.createObjectURL(b); | |
| var a=document.createElement("a"); a.href=u; a.download="JGOS_\uD589\uC815AX_\uD1B5\uD569\uACB0\uACFC.xlsx"; | |
| document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(u); | |
| }catch(e){alert("\uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: "+(e.message||e));} | |
| dl.disabled=false; dl.textContent=o; | |
| }; | |
| result.appendChild(dl); | |
| } | |
| }; | |
| async function exampleMerge(btn,resultId,type){ | |
| var o=btn.innerHTML; btn.disabled=true; btn.innerHTML="\u23F3 \uD1B5\uD569 \uC911..."; | |
| var result=document.getElementById(resultId); | |
| if(result){result.hidden=false;result.innerHTML='<div class="ax-loading">\u23F3 \uC608\uC81C \uD1B5\uD569 \uC911...</div>';} | |
| try{var r=await fetch("/api/demo/merge/"+type+"?t="+Date.now());var d=await r.json();window.__axShow(resultId,d);} | |
| catch(e){if(result)result.innerHTML="<p style=\'color:red\'>\uC624\uB958: "+(e.message||e)+"</p>";} | |
| btn.disabled=false; btn.innerHTML=o; | |
| } | |
| async function uploadMerge(btn,inputId,resultId,apiPath){ | |
| var input=document.getElementById(inputId); | |
| if(!input||!input.files||!input.files.length){alert("\uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uC138\uC694");return;} | |
| var o=btn.innerHTML; btn.disabled=true; btn.innerHTML="\u23F3 \uD1B5\uD569 \uC911..."; | |
| var result=document.getElementById(resultId); | |
| if(result){result.hidden=false;result.innerHTML='<div class="ax-loading">\u23F3 \uD1B5\uD569 \uC911...</div>';} | |
| var fd=new FormData(); for(var i=0;i<input.files.length;i++)fd.append("files",input.files[i]); | |
| try{var r=await fetch(apiPath,{method:"POST",body:fd});var d=await r.json();window.__axShow(resultId,d);} | |
| catch(e){if(result)result.innerHTML="<p style=\'color:red\'>\uC624\uB958: "+(e.message||e)+"</p>";} | |
| btn.disabled=false; btn.innerHTML=o; | |
| } | |
| function setup(){ | |
| var er=document.getElementById("ax-excel-run"); | |
| if(er)er.onclick=function(){uploadMerge(er,"ax-excel-input","ax-excel-result","/api/excel/merge");}; | |
| var fr=document.getElementById("ax-format-run"); | |
| if(fr)fr.onclick=function(){uploadMerge(fr,"ax-format-input","ax-format-result","/api/files/merge-formats");}; | |
| [["ax-excel-run","ax-excel-result","excel"],["ax-format-run","ax-format-result","formats"]].forEach(function(x){ | |
| var run=document.getElementById(x[0]); if(!run)return; | |
| var id=x[0]+"-demo"; var b=document.getElementById(id); | |
| if(!b){b=document.createElement("button");b.id=id;b.className=run.className;b.style.background="#0d7377";b.style.marginLeft="8px";b.innerHTML="\uD83D\uDCC2 \uC608\uC81C\uB85C \uBC14\uB85C \uD1B5\uD569";run.parentNode.insertBefore(b,run.nextSibling);} | |
| b.onclick=function(){exampleMerge(b,x[1],x[2]);}; | |
| }); | |
| } | |
| if(document.readyState!=="loading")setTimeout(setup,600); | |
| else document.addEventListener("DOMContentLoaded",function(){setTimeout(setup,600);}); | |
| document.addEventListener("click",function(e){var t=e.target.closest&&e.target.closest('[data-view="ax"]');if(t)setTimeout(setup,600);}); | |
| console.log("[JGOS] AX integrate+download v2 loaded"); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |