#!/usr/bin/env python3 """ AI Platform 메뉴 안내 PPT — 일반 임직원(관리자·특수 메뉴 제외) - 가이드봇·WM·대시보드·업무 체크리스트: 허용 계정만 (본 PPT 범위 외) """ from __future__ import annotations from pathlib import Path from pptx import Presentation from pptx.dml.color import RGBColor from pptx.enum.shapes import MSO_SHAPE from pptx.enum.text import MSO_ANCHOR, PP_ALIGN from pptx.util import Inches, Pt ROOT = Path(__file__).resolve().parent.parent SHOT_DIR = ROOT / "docs" / "ppt-screenshots-user" OUT_PPT = ROOT / "docs" / "XAVIS-AI-Platform-메뉴안내-일반사용자.pptx" # Brand palette NAVY = RGBColor(0x0F, 0x17, 0x2A) BLUE = RGBColor(0x25, 0x63, 0xEB) BLUE_LIGHT = RGBColor(0xDB, 0xEA, 0xFE) SLATE = RGBColor(0x47, 0x55, 0x69) SLATE_LIGHT = RGBColor(0x94, 0xA3, 0xB8) WHITE = RGBColor(0xFF, 0xFF, 0xFF) BG = RGBColor(0xF8, 0xFA, 0xFC) SLIDE_W = Inches(13.333) SLIDE_H = Inches(7.5) SLIDES: list[tuple[str, str, list[str], str | None]] = [ ( "cover", "XAVIS AI Platform", [ "일반 사용자 이용 가이드", "https://ai.xavis.co.kr/", "@ncue.net 이메일 인증 후 이용 · 관리자 모드 불필요", ], None, ), ( "section", "시작하기", [], None, ), ( "content", "이메일 인증으로 접속", [ "1. ai.xavis.co.kr 접속 → 회사 이메일 입력 → [검증]", "2. 메일함 [인증 완료하기] 클릭 (링크 15분 유효)", "3. 인증 완료 후 AI·학습센터 등 메뉴 이용", "로그아웃: 좌측 하단 · 좌측 [관리자]는 운영 담당용(일반 사용자 불필요)", ], "login.png", ), ( "content", "일반 사용자 메뉴 구성", [ "AI — 회의록·채팅·FSCAN 등 AI 서비스", "프롬프트 — 업무용 템플릿·팀 공유", "학습센터 — YouTube·PPT·동영상 강의", "과제신청 — AX 과제 온라인 신청", "AI 활용 사례 — 임직원 간 실무 AI 활용 노하우 공유·열람", "※ 가이드봇·WM·대시보드·업무 체크리스트는 허용 계정만 표시", ], "menu-overview.png", ), ( "section", "AI 서비스", [], None, ), ( "content", "AI — 서비스 허브", [ "경로: /ai-explore", "검색 + 타입 필터(전체 / 일반 / XScan / FScan)", "카드 클릭으로 각 AI 도구 실행", ], "ai-explore.png", ), ( "content", "회의록 AI", [ "텍스트: 회의 원문 붙여넣기 → [회의록 생성] → 수정 후 [저장]", "음성: mp3·m4a·wav(최대 300MB) → 업로드 → 전사 → 회의록 정리", "대안: 클로버노트·Claude 전사 → 텍스트 입력 탭에 붙여넣기", "유의: 결과 검토 필수 · 개인정보·대외비 주의", ], "meeting-minutes.png", ), ( "content", "일반 채팅", [ "이메일 인증 후 ChatGPT 기반 사내 채팅", "업무 질의·초안·아이디어 · (설정 시) 웹 검색", "반복 업무는 [프롬프트] 템플릿 복사 후 활용", ], "chat.png", ), ( "content", "FSCAN 조사각 선정도우미", [ "검사물 H/W 치수 입력 → FSCAN 모델 1차 선정", "영업·기술 검토용 · 최종 스펙은 공식 카탈로그·기술팀 확인", ], "fscan.png", ), ( "section", "업무 지원", [], None, ), ( "content", "프롬프트 라이브러리", [ "공식 템플릿(회의·메일·보고·OKR 등) 미리보기 → 복사", "워크플로: 4단계 입력으로 맞춤 지시문 초안", "공유하기: 팀 프롬프트 등록(로그인 필요, 기밀 제외)", ], "prompts.png", ), ( "content", "학습센터", [ "YouTube · PPT/PDF · 동영상 · 웹 링크 강의 검색·시청", "카테고리: AX 사고 전환 · AI 툴 활용 · AI Agent · 바이브 코딩", "캡처: 등록 강의 목록 전체(무한 스크롤 로드 후)", "강의 등록·수정은 운영 담당 — 일반 사용자는 시청·검색", ], "learning.png", ), ( "content", "AX 과제 신청", [ "Word 양식 다운로드 + 온라인 신청서 작성", "본인 신청 조회·수정(부서·이름·이메일)", "유사 사례는 AI 활용 사례 메뉴 참고", ], "ax-apply.png", ), ( "content", "AI 활용 사례 — 공유 공간과 열람", [ "각자의 실무 AI 활용법을 전사 임직원과 공유하는 공간(학습센터=교육, 여기=현장 검증 사례)", "로그인 임직원 누구나 열람·글쓰기 · 부서·태그·검색으로 유사 사례 탐색", "AX 과제·새 업무 전 참고 · 카드 클릭 → STAR 상세·재현 방법 확인", "경로: 좌측 [AI 활용 사례] · Before/After·활용 도구·성과 중심", ], "ai-cases.png", ), ( "content", "AI 활용 사례 — 나의 경험 공유하기", [ "[글쓰기] → STAR 본문(배경→과제→AI 활용→성과) · 활용 AI 태그 · 썸네일", "동료가 내일 바로 따라 할 수 있도록 도구·절차·수치를 구체적으로(과장 금지)", "※ 개인정보·고객·기밀 미포함 · AI 결과는 본인 검증 후 · 문의 AI혁신팀", ], "ai-cases-compose.png", ), ( "section", "정리", [], None, ), ( "content", "업무별 Quick Reference", [ "음성 회의록 → AI → 회의록 AI (또는 클로버노트 → 텍스트 입력)", "빠른 질문 → 일반 채팅 · 메일·보고 초안 → 프롬프트", "학습 → 학습센터 · AX 과제 → 과제신청 · 사례 → AI 활용 사례", "문의: AI혁신팀", ], None, ), ( "closing", "감사합니다", [ "XAVIS AI Platform", "https://ai.xavis.co.kr/", "AI혁신팀", ], None, ), ] def _set_solid_fill(shape, color: RGBColor) -> None: shape.fill.solid() shape.fill.fore_color.rgb = color def _add_header_bar(slide, title: str) -> None: bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0), SLIDE_W, Inches(0.95)) _set_solid_fill(bar, NAVY) bar.line.fill.background() accent = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0.95), SLIDE_W, Inches(0.06)) _set_solid_fill(accent, BLUE) accent.line.fill.background() tb = slide.shapes.add_textbox(Inches(0.55), Inches(0.18), Inches(10.5), Inches(0.55)) tf = tb.text_frame tf.text = title p = tf.paragraphs[0] p.font.name = "Apple SD Gothic Neo" p.font.size = Pt(24) p.font.bold = True p.font.color.rgb = WHITE def _add_footer(slide, page_num: int) -> None: line = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.55), Inches(7.05), Inches(12.2), Inches(0.015)) _set_solid_fill(line, BLUE_LIGHT) line.line.fill.background() left = slide.shapes.add_textbox(Inches(0.55), Inches(7.12), Inches(5), Inches(0.28)) ltf = left.text_frame ltf.text = "XAVIS AI Platform · 일반 사용자 가이드" lp = ltf.paragraphs[0] lp.font.size = Pt(9) lp.font.color.rgb = SLATE_LIGHT right = slide.shapes.add_textbox(Inches(11.8), Inches(7.12), Inches(1.0), Inches(0.28)) rtf = right.text_frame rtf.text = str(page_num) rp = rtf.paragraphs[0] rp.font.size = Pt(9) rp.font.color.rgb = SLATE_LIGHT rp.alignment = PP_ALIGN.RIGHT def _add_bullets(slide, bullets: list[str], left: float, top: float, width: float, height: float, font_size: int = 14) -> None: box = slide.shapes.add_textbox(Inches(left), Inches(top), Inches(width), Inches(height)) tf = box.text_frame tf.word_wrap = True for i, line in enumerate(bullets): para = tf.paragraphs[0] if i == 0 else tf.add_paragraph() para.text = line para.font.name = "Apple SD Gothic Neo" para.font.size = Pt(font_size) para.font.color.rgb = SLATE para.space_after = Pt(8) para.level = 0 if not line.startswith("※"): para.text = f"• {line}" def _add_screenshot(slide, shot_name: str, left: float, top: float, width: float) -> bool: path = SHOT_DIR / shot_name if not path.is_file(): return False pic = slide.shapes.add_picture(str(path), Inches(left), Inches(top), width=Inches(width)) max_height = Inches(5.85) if pic.height > max_height: scale = max_height / pic.height pic.width = int(pic.width * scale) pic.height = int(pic.height * scale) frame = slide.shapes.add_shape( MSO_SHAPE.RECTANGLE, pic.left - Inches(0.04), pic.top - Inches(0.04), pic.width + Inches(0.08), pic.height + Inches(0.08), ) frame.fill.background() frame.line.color.rgb = BLUE_LIGHT frame.line.width = Pt(1.25) # picture on top slide.shapes._spTree.remove(pic._element) slide.shapes._spTree.insert(-1, pic._element) return True def add_cover_slide(prs: Presentation, title: str, bullets: list[str]) -> None: slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0), SLIDE_W, SLIDE_H) _set_solid_fill(bg, NAVY) bg.line.fill.background() stripe = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(2.8), SLIDE_W, Inches(0.08)) _set_solid_fill(stripe, BLUE) stripe.line.fill.background() tb = slide.shapes.add_textbox(Inches(0.9), Inches(1.5), Inches(11.5), Inches(1.0)) tf = tb.text_frame tf.text = title p = tf.paragraphs[0] p.font.size = Pt(44) p.font.bold = True p.font.color.rgb = WHITE sub = slide.shapes.add_textbox(Inches(0.9), Inches(3.2), Inches(11.0), Inches(2.5)) stf = sub.text_frame for i, line in enumerate(bullets): para = stf.paragraphs[0] if i == 0 else stf.add_paragraph() para.text = line para.font.size = Pt(20 if i == 0 else 16) para.font.bold = i == 0 para.font.color.rgb = BLUE_LIGHT if i == 0 else SLATE_LIGHT para.space_after = Pt(10) def add_section_slide(prs: Presentation, title: str, page_num: int) -> None: slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0), SLIDE_W, SLIDE_H) _set_solid_fill(bg, BLUE) bg.line.fill.background() tb = slide.shapes.add_textbox(Inches(0.9), Inches(3.0), Inches(11), Inches(1.2)) tf = tb.text_frame tf.text = title p = tf.paragraphs[0] p.font.size = Pt(36) p.font.bold = True p.font.color.rgb = WHITE _add_footer(slide, page_num) def add_text_slide(prs: Presentation, title: str, bullets: list[str], page_num: int) -> None: """스크린샷 없이 본문만 — 설명·가이드용""" slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0.95), SLIDE_W, Inches(6.55)) _set_solid_fill(bg, BG) bg.line.fill.background() _add_header_bar(slide, title) highlight = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.55), Inches(1.15), Inches(0.08), Inches(5.6)) _set_solid_fill(highlight, BLUE) highlight.line.fill.background() _add_bullets(slide, bullets, 0.75, 1.25, 11.8, 5.5, font_size=15) _add_footer(slide, page_num) def add_content_slide(prs: Presentation, title: str, bullets: list[str], shot_name: str | None, page_num: int) -> None: slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0.95), SLIDE_W, Inches(6.55)) _set_solid_fill(bg, BG) bg.line.fill.background() _add_header_bar(slide, title) has_shot = shot_name and (SHOT_DIR / shot_name).is_file() text_w = 5.6 if has_shot else 12.0 fs = 13 if len(bullets) >= 5 else 14 _add_bullets(slide, bullets, 0.55, 1.25, text_w, 5.5, font_size=fs) if has_shot and shot_name: _add_screenshot(slide, shot_name, 6.45, 1.15, 6.35) _add_footer(slide, page_num) def add_closing_slide(prs: Presentation, title: str, bullets: list[str], page_num: int) -> None: slide = prs.slides.add_slide(prs.slide_layouts[6]) bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0), SLIDE_W, SLIDE_H) _set_solid_fill(bg, NAVY) bg.line.fill.background() tb = slide.shapes.add_textbox(Inches(0.9), Inches(2.6), Inches(11.5), Inches(1.0)) tf = tb.text_frame tf.text = title p = tf.paragraphs[0] p.font.size = Pt(40) p.font.bold = True p.font.color.rgb = WHITE p.alignment = PP_ALIGN.CENTER sub = slide.shapes.add_textbox(Inches(0.9), Inches(3.8), Inches(11.5), Inches(1.5)) stf = sub.text_frame for i, line in enumerate(bullets): para = stf.paragraphs[0] if i == 0 else stf.add_paragraph() para.text = line para.font.size = Pt(18) para.font.color.rgb = SLATE_LIGHT para.alignment = PP_ALIGN.CENTER para.space_after = Pt(6) _add_footer(slide, page_num) def build() -> Path: prs = Presentation() prs.slide_width = SLIDE_W prs.slide_height = SLIDE_H page = 0 for kind, title, bullets, shot in SLIDES: page += 1 if kind == "cover": add_cover_slide(prs, title, bullets) elif kind == "section": add_section_slide(prs, title, page) elif kind == "closing": add_closing_slide(prs, title, bullets, page) elif kind == "text": add_text_slide(prs, title, bullets, page) else: add_content_slide(prs, title, bullets, shot, page) OUT_PPT.parent.mkdir(parents=True, exist_ok=True) prs.save(str(OUT_PPT)) return OUT_PPT if __name__ == "__main__": out = build() print(f"PPT saved: {out}")