xavis 소스·DB 스키마·활용사례/F-Scan/프롬프트 라이브러리 등 기능 반영. @xavis.co.kr → @ncue.net, 관리자 토큰 ncue-admin, 런타임 data/ Git 추적 제외. Co-authored-by: Cursor <cursoragent@cursor.com>
286 lines
9.6 KiB
Python
286 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AI Platform 메뉴 안내 PPT 생성 (python-pptx)
|
|
스크린샷: docs/ppt-screenshots/*.png
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from pptx import Presentation
|
|
from pptx.dml.color import RGBColor
|
|
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"
|
|
OUT_PPT = ROOT / "docs" / "XAVIS-AI-Platform-메뉴안내.pptx"
|
|
|
|
# 슬라이드 정의: (제목, 불릿 목록, 스크린샷 파일명 또는 None)
|
|
SLIDES: list[tuple[str, list[str], str | None]] = [
|
|
(
|
|
"XAVIS AI Platform 메뉴 안내",
|
|
[
|
|
"사내 AI 도구·학습·과제·활용 사례 통합 포털",
|
|
"접속: https://ai.xavis.co.kr/",
|
|
"최초 1회: @ncue.net 이메일 → 인증 메일 링크(15분 유효)",
|
|
"좌측 메뉴: 회사규정 · WM · AI · 프롬프트 · 학습센터 · 과제신청 · AI 활용 사례 · 대시보드(허용자)",
|
|
],
|
|
None,
|
|
),
|
|
(
|
|
"1. 서비스 접속 (로그인)",
|
|
[
|
|
"경로: /login — 미인증 시 자동 이동",
|
|
"회사 이메일 입력 → [검증] → 메일 [인증 완료하기] 클릭",
|
|
"인증 후 학습센터 등 원하는 화면으로 이동",
|
|
"유의: @ncue.net 만 가능 · 링크 만료 시 재검증 · 로그아웃은 좌측 하단",
|
|
],
|
|
"login.png",
|
|
),
|
|
(
|
|
"2. 회사규정",
|
|
[
|
|
"좌측 [회사규정] 클릭 → Google NotebookLM (새 탭, 회사 Google 계정 로그인)",
|
|
"사내 규정·지침 AI 검색·요약·질의응답",
|
|
"캡처: 좌측 메뉴 [회사규정] 위치 (학습센터 화면)",
|
|
"AI 답변은 원문 규정·다우오피스 공식 문서와 반드시 대조",
|
|
],
|
|
"company-policy.png",
|
|
),
|
|
(
|
|
"3. WM",
|
|
[
|
|
"좌측 [WM] 클릭 → NotebookLM WM 노트북 (새 탭)",
|
|
"WM 프로세스·용어·가이드 질의",
|
|
"캡처: 좌측 메뉴 [WM] 위치 (AI 화면)",
|
|
"공식 매뉴얼·담당 부서 확인 병행",
|
|
],
|
|
"wm.png",
|
|
),
|
|
(
|
|
"4. AI (메인 허브)",
|
|
[
|
|
"경로: /ai-explore — AI 서비스 카드 허브",
|
|
"검색 + 타입 필터(전체/일반/XScan/FScan)",
|
|
"회의록 AI · 업무 체크리스트 · 일반 채팅 · FSCAN 선정도우미",
|
|
],
|
|
"ai-explore.png",
|
|
),
|
|
(
|
|
"5. 회의록 AI",
|
|
[
|
|
"텍스트 입력: 회의 원문 붙여넣기 → 회의록 생성 → 저장",
|
|
"음성 파일: mp3·m4a·wav(최대 300MB) → 업로드→전사→회의록 정리",
|
|
"저장 시 업무 체크리스트 AI와 자동 연동",
|
|
"대안: Claude 음성 업로드 / 클로버노트 전사 → 텍스트 입력 탭 붙여넣기",
|
|
"유의: 생성 결과 검토 필수 · 개인정보·대외비 주의",
|
|
],
|
|
"meeting-minutes.png",
|
|
),
|
|
(
|
|
"6. 업무 체크리스트 AI",
|
|
[
|
|
"회의록 AI 저장분의 액션·체크리스트 통합 관리",
|
|
"진행/완료 필터 · 회의별 필터 · 완료 처리 메모",
|
|
"흐름: 회의록 AI 저장 → 체크리스트에서 추적",
|
|
],
|
|
"task-checklist.png",
|
|
),
|
|
(
|
|
"7. 일반 채팅",
|
|
[
|
|
"ChatGPT 기반 사내 인앱 채팅 (로그인 필요)",
|
|
"업무 질의·초안·아이디어 · (설정 시) 웹 검색",
|
|
"반복 프롬프트는 [프롬프트] 메뉴 템플릿 활용",
|
|
"유의: AI 답변은 실수할 수 있음 · 기밀 입력 자제",
|
|
],
|
|
"chat.png",
|
|
),
|
|
(
|
|
"8. FSCAN 조사각 선정도우미",
|
|
[
|
|
"검사 대상물 H/W 치수 입력 → FSCAN 모델 1차 선정",
|
|
"영업·기술 검토 시 빠른 선정용",
|
|
"최종 스펙은 공식 카탈로그·기술팀 확인 필수",
|
|
],
|
|
"fscan.png",
|
|
),
|
|
(
|
|
"9. 프롬프트 라이브러리",
|
|
[
|
|
"공식 템플릿: 회의 요약·이메일·보고·OKR·코드리뷰 등 → 복사",
|
|
"워크플로: 4단계 입력 → 맞춤 프롬프트 초안",
|
|
"공유하기: 팀 프롬프트·참고 파일 (기밀 제외)",
|
|
],
|
|
"prompts.png",
|
|
),
|
|
(
|
|
"10. 학습센터",
|
|
[
|
|
"YouTube · PPT/PDF · 동영상 · 웹·뉴스 링크 강의",
|
|
"카테고리: AX 사고 전환 · AI 툴 활용 · AI Agent · 바이브 코딩",
|
|
"검색 예: claude, 클로드 → 도구별 강의 찾기",
|
|
],
|
|
"learning.png",
|
|
),
|
|
(
|
|
"11. AX 과제 신청",
|
|
[
|
|
"온라인 신청 + Word 양식 다운로드",
|
|
"Pain Point · AI 기대 · 데이터·효과 등 작성",
|
|
"조회로 본인 신청 확인·수정 · AI 활용 사례 참고",
|
|
],
|
|
"ax-apply.png",
|
|
),
|
|
(
|
|
"12. AI 활용 사례",
|
|
[
|
|
"부서별 도입·성과 사례 카드 열람",
|
|
"글쓰기: STAR 형식(1.Situation~4.Result)",
|
|
"AX 과제·팀 공유 레퍼런스",
|
|
],
|
|
"ai-cases.png",
|
|
),
|
|
(
|
|
"13. 대시보드 (허용 계정)",
|
|
[
|
|
"허용 이메일만 좌측 메뉴 표시",
|
|
"경영성과: 연도·분기 KPI 차트 + 매출일보 엑셀 업로드",
|
|
],
|
|
"dashboard.png",
|
|
),
|
|
(
|
|
"14. 경영성과 대시보드",
|
|
[
|
|
"상단: Chart.js KPI·차트 조회",
|
|
"하단: .xlsx 매출일보 업로드 → 스냅샷 저장",
|
|
],
|
|
"dashboard-business-performance.png",
|
|
),
|
|
(
|
|
"업무별 Quick Reference",
|
|
[
|
|
"음성 회의록 → AI → 회의록 AI (또는 클로버노트 → 텍스트 입력)",
|
|
"할 일 추적 → 업무 체크리스트 AI",
|
|
"빠른 질문 → 일반 채팅 · 메일·보고 → 프롬프트",
|
|
"규정 → 회사규정 · 학습 → 학습센터 · AI 과제 → 과제신청",
|
|
"문의: AI혁신팀",
|
|
],
|
|
None,
|
|
),
|
|
]
|
|
|
|
ACCENT = RGBColor(0x25, 0x63, 0xEB)
|
|
DARK = RGBColor(0x11, 0x18, 0x27)
|
|
GRAY = RGBColor(0x4B, 0x55, 0x63)
|
|
|
|
|
|
def add_title_slide(prs: Presentation, title: str, bullets: list[str]) -> None:
|
|
layout = prs.slide_layouts[6] # blank
|
|
slide = prs.slides.add_slide(layout)
|
|
slide.background.fill.solid()
|
|
slide.background.fill.fore_color.rgb = RGBColor(0xF0, 0xF4, 0xFF)
|
|
|
|
box = slide.shapes.add_textbox(Inches(0.8), Inches(2.2), Inches(11.5), Inches(1.2))
|
|
tf = box.text_frame
|
|
tf.text = title
|
|
p = tf.paragraphs[0]
|
|
p.font.size = Pt(40)
|
|
p.font.bold = True
|
|
p.font.color.rgb = DARK
|
|
p.alignment = PP_ALIGN.CENTER
|
|
|
|
sub = slide.shapes.add_textbox(Inches(1.2), Inches(3.6), Inches(10.8), Inches(2.5))
|
|
stf = sub.text_frame
|
|
stf.word_wrap = True
|
|
for i, line in enumerate(bullets[:4]):
|
|
para = stf.paragraphs[0] if i == 0 else stf.add_paragraph()
|
|
para.text = line
|
|
para.font.size = Pt(18)
|
|
para.font.color.rgb = GRAY
|
|
para.space_after = Pt(8)
|
|
para.alignment = PP_ALIGN.CENTER
|
|
|
|
|
|
def add_content_slide(
|
|
prs: Presentation,
|
|
title: str,
|
|
bullets: list[str],
|
|
shot_name: str | None,
|
|
) -> None:
|
|
layout = prs.slide_layouts[6]
|
|
slide = prs.slides.add_slide(layout)
|
|
|
|
# 제목
|
|
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.25), Inches(12.3), Inches(0.6))
|
|
ttf = title_box.text_frame
|
|
ttf.text = title
|
|
tp = ttf.paragraphs[0]
|
|
tp.font.size = Pt(26)
|
|
tp.font.bold = True
|
|
tp.font.color.rgb = ACCENT
|
|
|
|
has_shot = shot_name and (SHOT_DIR / shot_name).is_file()
|
|
text_left = Inches(0.5)
|
|
text_top = Inches(0.95)
|
|
text_w = Inches(5.8) if has_shot else Inches(12.3)
|
|
text_h = Inches(6.2)
|
|
|
|
body = slide.shapes.add_textbox(text_left, text_top, text_w, text_h)
|
|
btf = body.text_frame
|
|
btf.word_wrap = True
|
|
for i, line in enumerate(bullets):
|
|
para = btf.paragraphs[0] if i == 0 else btf.add_paragraph()
|
|
para.text = f"• {line}"
|
|
para.font.size = Pt(14)
|
|
para.font.color.rgb = DARK
|
|
para.space_after = Pt(6)
|
|
para.level = 0
|
|
|
|
if has_shot:
|
|
shot_path = str(SHOT_DIR / shot_name)
|
|
slide.shapes.add_picture(
|
|
shot_path,
|
|
Inches(6.6),
|
|
Inches(0.85),
|
|
width=Inches(6.2),
|
|
)
|
|
elif shot_name:
|
|
note = slide.shapes.add_textbox(Inches(6.6), Inches(2.5), Inches(6.0), Inches(1.0))
|
|
ntf = note.text_frame
|
|
ntf.text = f"(캡처 없음: {shot_name})"
|
|
ntf.paragraphs[0].font.size = Pt(12)
|
|
ntf.paragraphs[0].font.color.rgb = GRAY
|
|
|
|
# 슬라이드 번호
|
|
num = len(prs.slides)
|
|
footer = slide.shapes.add_textbox(Inches(12.0), Inches(7.05), Inches(0.8), Inches(0.3))
|
|
ftf = footer.text_frame
|
|
ftf.text = str(num)
|
|
ftf.paragraphs[0].font.size = Pt(10)
|
|
ftf.paragraphs[0].font.color.rgb = GRAY
|
|
ftf.paragraphs[0].alignment = PP_ALIGN.RIGHT
|
|
|
|
|
|
def build() -> Path:
|
|
prs = Presentation()
|
|
prs.slide_width = Inches(13.333)
|
|
prs.slide_height = Inches(7.5)
|
|
|
|
for idx, (title, bullets, shot) in enumerate(SLIDES):
|
|
if idx == 0:
|
|
add_title_slide(prs, title, bullets)
|
|
else:
|
|
add_content_slide(prs, title, bullets, shot)
|
|
|
|
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}")
|