feat: xavis ai_platform 기능 이전 및 ncue 환경 전환
xavis 소스·DB 스키마·활용사례/F-Scan/프롬프트 라이브러리 등 기능 반영. @xavis.co.kr → @ncue.net, 관리자 토큰 ncue-admin, 런타임 data/ Git 추적 제외. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
429
scripts/build-menu-guide-ppt-user.py
Normal file
429
scripts/build-menu-guide-ppt-user.py
Normal file
@@ -0,0 +1,429 @@
|
||||
#!/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}")
|
||||
Reference in New Issue
Block a user