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:
dsyoon
2026-05-26 22:27:48 +09:00
parent 7bee72f287
commit 073a8343dd
84 changed files with 10883 additions and 1043 deletions

View 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}")