12-slide PPTX (cover, overview, agenda, divider+flow per demo 1-4, closing) generated by scripts/gen_ai_demo_ppt.py. Content sourced from internal AI_DEMO_SCRIPT.pptx with internal-only info (accounts, checklist, troubleshooting) removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
324 lines
17 KiB
Python
324 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""고객 시연용 AI 파트 발표 PPT 생성 — 시연 시나리오 순 구성.
|
||
|
||
근거: docs/demo/AI_DEMO_SCRIPT.pptx(내부 절차서)의 4개 데모 흐름.
|
||
고객용이므로 계정·체크리스트·트러블슈팅 등 내부 정보는 제외.
|
||
출력: docs/demo/AI_시연_시나리오.pptx
|
||
"""
|
||
import os
|
||
|
||
BASE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "docs", "demo")
|
||
KOR_FONT = "맑은 고딕"
|
||
|
||
from pptx import Presentation
|
||
from pptx.util import Inches, Pt
|
||
from pptx.dml.color import RGBColor
|
||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||
from pptx.enum.shapes import MSO_SHAPE
|
||
from pptx.oxml.ns import qn
|
||
|
||
P_BLUE = RGBColor(0x00, 0x66, 0xB3)
|
||
P_DARK = RGBColor(0x00, 0x4C, 0x86)
|
||
P_AI = RGBColor(0x6D, 0x4A, 0xFF)
|
||
P_GREY = RGBColor(0x34, 0x40, 0x54)
|
||
P_LIGHT = RGBColor(0xEF, 0xF6, 0xFC)
|
||
P_WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||
P_SOFT = RGBColor(0xD9, 0xEA, 0xF7)
|
||
P_FOOT = RGBColor(0x66, 0x70, 0x85)
|
||
P_INK = RGBColor(0x10, 0x18, 0x28)
|
||
|
||
TITLE = "KINTEX AI 전시·행사시스템"
|
||
|
||
|
||
def pset(run, size, bold=False, color=P_GREY):
|
||
f = run.font
|
||
f.size = Pt(size)
|
||
f.bold = bold
|
||
f.color.rgb = color
|
||
f.name = KOR_FONT
|
||
rPr = run._r.get_or_add_rPr()
|
||
for tag in ("a:ea", "a:cs"):
|
||
el = rPr.find(qn(tag))
|
||
if el is None:
|
||
el = rPr.makeelement(qn(tag), {})
|
||
rPr.append(el)
|
||
el.set("typeface", KOR_FONT)
|
||
|
||
|
||
def add_box(slide, x, y, w, h):
|
||
tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
|
||
tb.text_frame.word_wrap = True
|
||
return tb.text_frame
|
||
|
||
|
||
def add_rect(slide, x, y, w, h, fill, line=False):
|
||
sh = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
|
||
sh.fill.solid()
|
||
sh.fill.fore_color.rgb = fill
|
||
if not line:
|
||
sh.line.fill.background()
|
||
sh.shadow.inherit = False
|
||
return sh
|
||
|
||
|
||
def footer(slide, idx, total):
|
||
tf = add_box(slide, 0.5, 7.05, 12.3, 0.35)
|
||
r = tf.paragraphs[0].add_run()
|
||
r.text = f"{TITLE} · AI 기능 고객 시연"
|
||
pset(r, 9, color=P_FOOT)
|
||
tf2 = add_box(slide, 12.3, 7.05, 0.7, 0.35)
|
||
p2 = tf2.paragraphs[0]
|
||
p2.alignment = PP_ALIGN.RIGHT
|
||
r2 = p2.add_run()
|
||
r2.text = f"{idx}/{total}"
|
||
pset(r2, 9, color=P_FOOT)
|
||
|
||
|
||
def blank(prs):
|
||
return prs.slides.add_slide(prs.slide_layouts[6])
|
||
|
||
|
||
def head(slide, kicker, title):
|
||
add_rect(slide, 0, 0, 13.333, 0.18, P_BLUE)
|
||
tf = add_box(slide, 0.55, 0.35, 12.2, 1.15)
|
||
r = tf.paragraphs[0].add_run(); r.text = kicker; pset(r, 12, True, P_AI)
|
||
p2 = tf.add_paragraph()
|
||
r = p2.add_run(); r.text = title; pset(r, 25, True, P_DARK)
|
||
add_rect(slide, 0.6, 1.62, 12.1, 0.03, P_SOFT)
|
||
|
||
|
||
def add_table(slide, x, y, w, header, rows, col_w=None, hdr_size=11, size=10.5, row_h=0.42):
|
||
shp = slide.shapes.add_table(1 + len(rows), len(header), Inches(x), Inches(y),
|
||
Inches(w), Inches(row_h * (1 + len(rows))))
|
||
t = shp.table
|
||
if col_w:
|
||
for j, cw in enumerate(col_w):
|
||
t.columns[j].width = Inches(cw)
|
||
for j, htxt in enumerate(header):
|
||
c = t.cell(0, j)
|
||
c.fill.solid(); c.fill.fore_color.rgb = P_DARK
|
||
c.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||
pa = c.text_frame.paragraphs[0]; pa.alignment = PP_ALIGN.CENTER
|
||
r = pa.add_run(); r.text = htxt; pset(r, hdr_size, True, P_WHITE)
|
||
for i, row in enumerate(rows):
|
||
for j, val in enumerate(row):
|
||
c = t.cell(i + 1, j)
|
||
c.fill.solid()
|
||
c.fill.fore_color.rgb = P_LIGHT if i % 2 == 0 else P_WHITE
|
||
c.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||
c.margin_top = Pt(3); c.margin_bottom = Pt(3)
|
||
lines = str(val).split("\n")
|
||
for k, ln in enumerate(lines):
|
||
pa = c.text_frame.paragraphs[0] if k == 0 else c.text_frame.add_paragraph()
|
||
r = pa.add_run(); r.text = ln
|
||
pset(r, size, j == 0, P_INK if j != 0 else P_DARK)
|
||
return t
|
||
|
||
|
||
def note_bar(slide, y, text, h=0.55):
|
||
add_rect(slide, 0.6, y, 12.1, h, P_LIGHT)
|
||
tf = add_box(slide, 0.8, y + 0.06, 11.8, h - 0.1)
|
||
r = tf.paragraphs[0].add_run(); r.text = text; pset(r, 12, True, P_DARK)
|
||
|
||
|
||
# ══════════════════════════════ 슬라이드 정의 ══════════════════════════════
|
||
|
||
DEMOS = [
|
||
dict(
|
||
no="DEMO 1", tag="공개 홈 · 로그인 불필요", time="5분",
|
||
title="AI 관람 도우미",
|
||
sub="자연어로 묻고, 실제 행사 데이터만 근거로 답한다",
|
||
divider_points=[
|
||
"관람객이 홈페이지에서 말하듯 질문 — 행사·일정·교통을 즉시 안내",
|
||
"답변 근거는 실제 적재된 행사 DB — 근거가 없으면 \"모른다\"고 답변(환각 차단)",
|
||
],
|
||
steps=[
|
||
("1", "공개 홈 접속 → 관람객 트랙 진입", "-", "관람객 서브홈 · AI 도우미 노출"),
|
||
("2", "AI 도우미에 질문 ①", "\"이번 달 킨텍스에서 볼만한 전시 알려줘\"", "행사명·기간·홀을 DB 근거로 요약 답변"),
|
||
("3", "질문 ② — 구체 카테고리", "\"유아용품 관련 박람회 언제 해?\"", "해당 카테고리 행사 목록 + 근거 표기"),
|
||
("4", "질문 ③ — 교통·편의", "\"대중교통으로 가는 방법 알려줘\"", "교통 가이드 데이터 기반 안내"),
|
||
("5", "환각 차단 확인 — 근거 없는 질문", "\"킨텍스에서 BTS 콘서트 언제 해?\"", "근거 없음 → 모른다고 답변 (추측 없음)"),
|
||
],
|
||
point="핵심 포인트 — 모든 답변은 실제 행사 데이터 근거. 근거가 없으면 지어내지 않습니다.",
|
||
),
|
||
dict(
|
||
no="DEMO 2", tag="플로어플랜 스튜디오 · ★핵심", time="12분",
|
||
title="자연어 AI 자동배치",
|
||
sub="말로 시키면 배치가 나온다 — 자연어 → 3안 생성 → AI 평가 → 조감 렌더",
|
||
divider_points=[
|
||
"\"부스 120개, 프리미엄 3할…\" 한 문장 → AI가 조건 해석 → 배치안 3안 자동 생성",
|
||
"배치안마다 활용률·통로폭 실계산 지표 근거의 AI 평가 요약",
|
||
"선택한 배치안은 AI가 홀 전경(조감) 이미지로 렌더링",
|
||
],
|
||
steps=[
|
||
("1", "시연 행사 → 부스 배치(플로어플랜 스튜디오) 진입", "-", "홀 평면 캔버스 표시"),
|
||
("2", "[AI 자동배치] → 자연어로 조건 입력 → [AI 해석]", "\"부스 120개, 프리미엄 3할, 무대 1개,\n입구 2개로 3안 뽑아줘\"", "AI가 조건 해석 → 폼 자동 채움 + 해석 근거 표시"),
|
||
("3", "해석된 값 확인(수정 가능) → [생성]", "목표 부스 수만 150으로 수정해 보기", "배치안 A/B/C 3안 생성 (제약 엔진)"),
|
||
("4", "배치안별 AI 평가 요약 확인", "-", "활용률·통로폭 등 실계산 지표 근거 장단점 요약"),
|
||
("5", "홀 전경(조감) 렌더 → 마음에 드는 안 [적용]", "(재시도) \"소형 부스 위주 200개, 라운지 2개\"", "AI 조감 이미지 생성 · 선택안 캔버스 반영"),
|
||
],
|
||
point="핵심 포인트 — 수일 걸리던 CAD 배치 작업이 자연어 한 문장으로. AI 해석 결과는 폼에서 수정 가능해 사람이 최종 결정합니다.",
|
||
),
|
||
dict(
|
||
no="DEMO 3", tag="부스설계 스튜디오 · ★핵심", time="10분",
|
||
title="부스 사진 시안 (AI)",
|
||
sub="사진 한 장이 시공 시안이 된다 — 업로드 → 스타일 선택 → 시공 후 사진",
|
||
divider_points=[
|
||
"부스·공간 사진 한 장 업로드 → 스타일 선택 → AI가 '시공 후 결과 사진' 생성",
|
||
"구조 보존 — 벽·바닥·기둥·구도는 그대로, 인테리어·조명·사이니지만 교체",
|
||
"Before/After 슬라이더로 비교 — 장치공사 발주(견적) 자료로 바로 연결",
|
||
],
|
||
steps=[
|
||
("1", "부스설계 스튜디오 → [사진 시안 (AI)] 섹션", "-", "01 업로드 → 02 스타일 → 03 지시 3단 UI"),
|
||
("2", "01 부스/공간 사진 업로드", "샘플 부스 사진 (드래그앤드롭)", "미리보기 표시"),
|
||
("3", "02 스타일 선택 + 03 자연어 지시(선택)", "스타일: \"테크\"\n지시: \"로고 월과 LED 사이니지, 우드 톤 바닥\"", "[사진 시안 생성] 활성화"),
|
||
("4", "[사진 시안 생성] 클릭", "-", "생성 진행 표시 (약 30~60초)"),
|
||
("5", "Before/After 슬라이더로 비교", "(재생성) 스타일 \"럭셔리\"로 비교", "구조·구도 보존, 인테리어만 교체된 시안 + AI 워터마크"),
|
||
],
|
||
point="핵심 포인트 — 참가업체가 현장 사진만 보내면 공사 발주 전에 시공 결과를 미리 봅니다. 이 시안이 그대로 견적(옥션) 근거자료가 됩니다.",
|
||
),
|
||
dict(
|
||
no="DEMO 4", tag="경영분석 대시보드 · AI 콘솔", time="8분",
|
||
title="자연어 데이터 조회 + 규정 챗",
|
||
sub="데이터와 규정에 말로 묻는다 — 조회는 DB 실집계, 규정은 룰셋 근거",
|
||
divider_points=[
|
||
"\"이번 행사 부스 판매율은?\" — 자연어 질문을 DB 실조회로 답변",
|
||
"\"천장 높이 제한은?\" — 등록된 규정 룰셋 버전을 근거 조항과 함께 답변",
|
||
],
|
||
steps=[
|
||
("1", "좌측 메뉴 → 경영분석 → 대시보드 하단 AI 콘솔", "-", "NL Query / 규정 챗 탭"),
|
||
("2", "자연어 조회 ①", "\"이번 행사 부스 판매율 알려줘\"", "DB 실조회 수치·표 응답"),
|
||
("3", "자연어 조회 ②", "\"홀별 배정 현황 요약해줘\"", "홀 H1~H5 배정 집계 응답"),
|
||
("4", "규정 챗 ①", "\"부스 천장 높이 제한이 몇 미터야?\"", "룰셋(규정 버전) 근거 답변 + 근거 조항"),
|
||
("5", "규정 챗 ②", "\"전기 증설 신청은 언제까지 해야 해?\"", "규정·마일스톤 근거 답변"),
|
||
],
|
||
point="핵심 포인트 — 답변 근거인 규정 룰셋 버전을 관리 화면에서 함께 확인할 수 있어, AI 답변의 출처가 투명합니다.",
|
||
),
|
||
]
|
||
|
||
|
||
def build():
|
||
prs = Presentation()
|
||
prs.slide_width = Inches(13.333)
|
||
prs.slide_height = Inches(7.5)
|
||
total = 4 + len(DEMOS) * 2 # 표지·개요·아젠다·클로징 + (간지+시나리오)×4
|
||
|
||
idx = 1
|
||
# ── 1. 표지 ──────────────────────────────────────────────
|
||
s = blank(prs)
|
||
add_rect(s, 0, 0, 13.333, 7.5, P_DARK)
|
||
add_rect(s, 0, 4.9, 13.333, 0.06, P_AI)
|
||
tf = add_box(s, 1.0, 2.0, 11.3, 2.6)
|
||
r = tf.paragraphs[0].add_run(); r.text = TITLE; pset(r, 40, True, P_WHITE)
|
||
p2 = tf.add_paragraph()
|
||
r = p2.add_run(); r.text = "AI 기능 고객 시연"; pset(r, 26, True, P_SOFT)
|
||
tf2 = add_box(s, 1.0, 5.1, 11.3, 1.6)
|
||
r = tf2.paragraphs[0].add_run()
|
||
r.text = "AI 관람 도우미 · 자연어 자동배치 · 부스 사진 시안 · 자연어 조회/규정 챗"
|
||
pset(r, 15, True, P_WHITE)
|
||
p2 = tf2.add_paragraph()
|
||
r = p2.add_run(); r.text = "\"신청서를 내는 시간, 시공 후 사진을 먼저 본다\" · 2026. 7."
|
||
pset(r, 13, False, P_SOFT)
|
||
idx += 1
|
||
|
||
# ── 2. 시연 개요 ─────────────────────────────────────────
|
||
s = blank(prs)
|
||
head(s, "OVERVIEW", "오늘 시연에서 보실 것")
|
||
add_table(s, 0.6, 2.0, 12.1,
|
||
["구분", "무엇을 보여드리나", "핵심 가치"],
|
||
[
|
||
("데모 1", "AI 관람 도우미 — 자연어 질문 → 행사·교통 안내", "근거 기반 답변 · 환각 차단"),
|
||
("데모 2 ★", "자연어 자동배치 — 한 문장 → 배치안 3안 + AI 평가 + 조감 렌더", "수일의 CAD 작업 → 수분"),
|
||
("데모 3 ★", "부스 사진 시안 — 사진 한 장 → 시공 후 결과 사진", "발주 전 결과 확인 · 견적 근거"),
|
||
("데모 4", "AI 콘솔 — 자연어 데이터 조회 + 규정 챗", "데이터·규정에 말로 질문"),
|
||
],
|
||
col_w=[1.5, 7.3, 3.3], row_h=0.62)
|
||
note_bar(s, 5.6,
|
||
"3가지 핵심 메시지 — ① 말로 시키면 배치가 나온다 ② 사진 한 장이 시공 시안이 된다 ③ 모든 답변은 근거 기반",
|
||
h=0.55)
|
||
tf = add_box(s, 0.6, 6.35, 12.1, 0.5)
|
||
r = tf.paragraphs[0].add_run()
|
||
r.text = "시연 환경: 라이브 시스템(실서버·실데이터) — 화면 캡처가 아닌 실제 동작을 시연합니다."
|
||
pset(r, 12, False, P_GREY)
|
||
footer(s, idx, total); idx += 1
|
||
|
||
# ── 3. 시연 순서 ─────────────────────────────────────────
|
||
s = blank(prs)
|
||
head(s, "AGENDA", "시연 순서 (약 40분)")
|
||
add_table(s, 0.6, 2.0, 12.1,
|
||
["순서", "내용", "시간", "위치"],
|
||
[
|
||
("데모 1", "AI 관람 도우미 — 근거 기반 자연어 응답 (로그인 불필요)", "5분", "공개 홈"),
|
||
("데모 2 ★", "자연어 AI 자동배치 → 3안 생성 → AI 평가 → 조감 렌더", "12분", "플로어플랜 스튜디오"),
|
||
("데모 3 ★", "부스 사진 시안 — 사진 업로드 → 시공 후 사진 + Before/After", "10분", "부스설계 스튜디오"),
|
||
("데모 4", "자연어 데이터 조회(NL Query) + 규정 챗", "8분", "경영분석 대시보드"),
|
||
("마무리", "질의응답 · 안전 설계(폴백) 소개", "5분", "-"),
|
||
],
|
||
col_w=[1.5, 7.3, 1.2, 2.1], row_h=0.6)
|
||
note_bar(s, 5.75, "각 데모는 실제 화면에서 라이브로 진행하며, 본 자료는 데모 사이 브리지(안내) 용도입니다.", h=0.55)
|
||
footer(s, idx, total); idx += 1
|
||
|
||
# ── 4~11. 데모별 간지 + 시나리오 ────────────────────────
|
||
for d in DEMOS:
|
||
# 간지 (라이브 시연 직전 브리지)
|
||
s = blank(prs)
|
||
add_rect(s, 0, 0, 13.333, 7.5, P_BLUE)
|
||
add_rect(s, 0, 5.9, 13.333, 0.05, P_AI)
|
||
tf = add_box(s, 1.2, 1.6, 11.0, 3.6)
|
||
r = tf.paragraphs[0].add_run()
|
||
r.text = f"{d['no']} · {d['tag']} · {d['time']}"
|
||
pset(r, 18, True, P_SOFT)
|
||
p2 = tf.add_paragraph()
|
||
r = p2.add_run(); r.text = d["title"]; pset(r, 40, True, P_WHITE)
|
||
p3 = tf.add_paragraph()
|
||
r = p3.add_run(); r.text = d["sub"]; pset(r, 17, False, P_SOFT)
|
||
tfp = add_box(s, 1.25, 4.6, 10.9, 1.9)
|
||
for k, pt in enumerate(d["divider_points"]):
|
||
pa = tfp.paragraphs[0] if k == 0 else tfp.add_paragraph()
|
||
pa.space_after = Pt(6)
|
||
r = pa.add_run(); r.text = f"▎ {pt}"; pset(r, 14, False, P_WHITE)
|
||
footer(s, idx, total); idx += 1
|
||
|
||
# 시나리오 흐름 (진행자용 겸 고객 안내)
|
||
s = blank(prs)
|
||
head(s, f"{d['no']} · 시나리오", f"{d['title']} — 진행 흐름")
|
||
add_table(s, 0.6, 1.95, 12.1,
|
||
["단계", "진행", "입력(자연어) 예시", "보실 결과"],
|
||
d["steps"],
|
||
col_w=[0.7, 4.0, 3.9, 3.5], row_h=0.62, size=10)
|
||
note_bar(s, 6.1, d["point"], h=0.6)
|
||
footer(s, idx, total); idx += 1
|
||
|
||
# ── 12. 클로징 ──────────────────────────────────────────
|
||
s = blank(prs)
|
||
add_rect(s, 0, 0, 13.333, 7.5, P_DARK)
|
||
add_rect(s, 0, 1.9, 13.333, 0.05, P_AI)
|
||
tf = add_box(s, 1.2, 1.0, 11.0, 0.8)
|
||
r = tf.paragraphs[0].add_run(); r.text = "시연을 마치며 — 3가지 핵심 메시지"; pset(r, 28, True, P_WHITE)
|
||
msgs = [
|
||
("① 말로 시키면 배치가 나온다", "자연어 → 제약 엔진 → 배치안 3안 + 실계산 지표 근거 AI 평가"),
|
||
("② 사진 한 장이 시공 시안이 된다", "구조 보존 image-to-image — 발주 전 결과 확인, 견적(옥션) 근거자료로 연결"),
|
||
("③ 모든 답변은 근거 기반", "행사 DB·규정 룰셋만 근거로 답변 — 근거가 없으면 모른다고 답한다(환각 차단)"),
|
||
]
|
||
y = 2.5
|
||
for t1, t2 in msgs:
|
||
add_rect(s, 1.2, y, 10.9, 1.05, RGBColor(0x0A, 0x3A, 0x63))
|
||
tf = add_box(s, 1.5, y + 0.12, 10.4, 0.9)
|
||
r = tf.paragraphs[0].add_run(); r.text = t1; pset(r, 17, True, P_WHITE)
|
||
p2 = tf.add_paragraph()
|
||
r = p2.add_run(); r.text = t2; pset(r, 12.5, False, P_SOFT)
|
||
y += 1.25
|
||
tf = add_box(s, 1.2, 6.35, 10.9, 0.6)
|
||
r = tf.paragraphs[0].add_run()
|
||
r.text = "AI가 실패해도 화면은 멈추지 않습니다 — 모든 AI 기능은 수동 폴백을 갖춘 안전 설계 · AI 생성물 라벨·워터마크 표기"
|
||
pset(r, 12, False, P_SOFT)
|
||
idx += 1
|
||
|
||
out = os.path.join(BASE, "AI_시연_시나리오.pptx")
|
||
prs.save(out)
|
||
print("saved:", out, "slides:", len(prs.slides))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
build()
|