xavis 소스·DB 스키마·활용사례/F-Scan/프롬프트 라이브러리 등 기능 반영. @xavis.co.kr → @ncue.net, 관리자 토큰 ncue-admin, 런타임 data/ Git 추적 제외. Co-authored-by: Cursor <cursoragent@cursor.com>
430 lines
14 KiB
Python
430 lines
14 KiB
Python
#!/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}")
|