zioinfo-mail/manual/gen_ppt.js
DESKTOP-TKLFCPR\ython e228faabf5 feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현
G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현
G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건)
G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST)
G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직
G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트
G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트
G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API
G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트
G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트
G-10: core/push_notify.py + routers/push.py + PushSubscription 모델
G-11: approvals 다중승인 (위임/서명/기한초과/마감연장)
G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh

하네스: guardia-orchestrator 확장기능 Phase 반영
봇명령어: /sr /status /license /bulk 슬래시 명령어 추가
설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:18:52 +09:00

1210 lines
46 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// GUARDiA × Paperclip AI 에이전트 구현 보고서 PPT 생성
const pptxgen = require("pptxgenjs");
const pres = new pptxgen();
pres.layout = "LAYOUT_16x9";
pres.author = "GUARDiA Team";
pres.title = "GUARDiA × Paperclip AI 에이전트 구현 보고서";
// ─── 색상 팔레트 ─────────────────────────────────────────
const C = {
navyDark: "0D1B3E", // 타이틀 배경
navy: "1E2761", // 사이드바
blue: "2E5BBA", // 강조 파랑
lightBlue: "4A90D9", // 서브 강조
ice: "CADCFC", // 연한 파랑 (배경 요소)
white: "FFFFFF",
offWhite: "F4F7FF", // 콘텐츠 슬라이드 배경
gray: "64748B",
lightGray: "E2E8F0",
green: "059669",
amber: "D97706",
red: "DC2626",
purple: "7C3AED",
teal: "0891B2",
orange: "EA580C",
};
const makeShadow = () => ({
type: "outer", blur: 6, offset: 3, angle: 135, color: "000000", opacity: 0.12
});
// ─── 슬라이드 헬퍼 ───────────────────────────────────────
function addTitleSlide(bgColor, title, subtitle, badge) {
const slide = pres.addSlide();
slide.background = { color: bgColor };
// 좌측 강조 바
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 0.12, h: 5.625,
fill: { color: C.lightBlue }, line: { color: C.lightBlue }
});
// 우측 장식 원
slide.addShape(pres.shapes.OVAL, {
x: 7.5, y: -1.2, w: 4.5, h: 4.5,
fill: { color: C.navy, transparency: 60 }, line: { color: C.navy, transparency: 60 }
});
slide.addShape(pres.shapes.OVAL, {
x: 8.5, y: 2.8, w: 2.5, h: 2.5,
fill: { color: C.blue, transparency: 70 }, line: { color: C.blue, transparency: 70 }
});
if (badge) {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.4, y: 1.3, w: 2.2, h: 0.38,
fill: { color: C.lightBlue }, line: { color: C.lightBlue }
});
slide.addText(badge, {
x: 0.4, y: 1.3, w: 2.2, h: 0.38,
fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", margin: 0
});
}
slide.addText(title, {
x: 0.4, y: 1.9, w: 8.5, h: 1.4,
fontSize: 36, color: C.white, bold: true, align: "left", fontFace: "Calibri"
});
if (subtitle) {
slide.addText(subtitle, {
x: 0.4, y: 3.45, w: 7.5, h: 0.7,
fontSize: 16, color: C.ice, align: "left", fontFace: "Calibri"
});
}
// 하단 날짜 라인
slide.addShape(pres.shapes.LINE, {
x: 0.4, y: 5.0, w: 9.2, h: 0,
line: { color: C.lightBlue, width: 1 }
});
slide.addText("2026.05.25 | GUARDiA ITSM", {
x: 0.4, y: 5.1, w: 5, h: 0.35,
fontSize: 10, color: "8FAADC", align: "left", fontFace: "Calibri"
});
return slide;
}
function addContentSlide(title) {
const slide = pres.addSlide();
slide.background = { color: C.offWhite };
// 상단 헤더 바
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 10, h: 0.72,
fill: { color: C.navy }, line: { color: C.navy }
});
// 좌측 포인트 바
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0.72, w: 0.08, h: 4.9,
fill: { color: C.lightBlue }, line: { color: C.lightBlue }
});
slide.addText(title, {
x: 0.25, y: 0, w: 9.5, h: 0.72,
fontSize: 20, color: C.white, bold: true, align: "left", valign: "middle",
fontFace: "Calibri", margin: 0
});
return slide;
}
function addSectionSlide(number, title, subtitle) {
const slide = pres.addSlide();
slide.background = { color: C.blue };
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 0.08, h: 5.625,
fill: { color: C.lightBlue }, line: { color: C.lightBlue }
});
slide.addText(`Phase ${number}`, {
x: 0.4, y: 1.4, w: 9, h: 0.7,
fontSize: 16, color: C.ice, bold: false, fontFace: "Calibri"
});
slide.addText(title, {
x: 0.4, y: 2.0, w: 9, h: 1.2,
fontSize: 32, color: C.white, bold: true, fontFace: "Calibri"
});
if (subtitle) {
slide.addText(subtitle, {
x: 0.4, y: 3.3, w: 8, h: 0.7,
fontSize: 15, color: C.ice, fontFace: "Calibri"
});
}
return slide;
}
function card(slide, x, y, w, h, headerColor, headerText, bodyLines) {
// 카드 본체
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w, h,
fill: { color: C.white },
line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
// 카드 상단 컬러 바
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w, h: 0.32,
fill: { color: headerColor },
line: { color: headerColor }
});
slide.addText(headerText, {
x, y, w, h: 0.32,
fontSize: 11, color: C.white, bold: true,
align: "center", valign: "middle", fontFace: "Calibri", margin: 0
});
const textArr = bodyLines.map((line, i) => ({
text: line,
options: {
fontSize: 10.5,
color: i === 0 ? C.gray : "1E293B",
bold: i === 0,
breakLine: true
}
}));
// 마지막 요소는 breakLine 제거
if (textArr.length > 0) textArr[textArr.length - 1].options.breakLine = false;
slide.addText(textArr, {
x: x + 0.1, y: y + 0.38, w: w - 0.2, h: h - 0.45,
valign: "top", fontFace: "Calibri"
});
}
function infoBox(slide, x, y, w, h, accentColor, title, desc) {
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w, h,
fill: { color: C.white },
line: { color: accentColor, width: 2 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 0.1, h,
fill: { color: accentColor },
line: { color: accentColor }
});
slide.addText(title, {
x: x + 0.18, y: y + 0.07, w: w - 0.25, h: 0.3,
fontSize: 11.5, color: "1E293B", bold: true, fontFace: "Calibri"
});
slide.addText(desc, {
x: x + 0.18, y: y + 0.36, w: w - 0.25, h: h - 0.42,
fontSize: 10.5, color: C.gray, fontFace: "Calibri", valign: "top"
});
}
// ═══════════════════════════════════════════════════════
// Slide 1: 타이틀
// ═══════════════════════════════════════════════════════
addTitleSlide(
C.navyDark,
"GUARDiA × Paperclip\nAI 에이전트 구현 보고서",
"Phase 1~4 설계·구현·테스트 완료 보고",
"2026.05.25 완료"
);
// ═══════════════════════════════════════════════════════
// Slide 2: 목차
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("목 차");
const items = [
{ n: "01", t: "GUARDiA ITSM 개요 & 배경", c: C.blue },
{ n: "02", t: "Paperclip 프레임워크 소개", c: C.purple },
{ n: "03", t: "Phase 1 — Paperclip 개발 도구 설정", c: C.teal },
{ n: "04", t: "Phase 2 — Ollama 로컬 LLM 구성", c: C.green },
{ n: "05", t: "Phase 3 — GUARDiA 에이전트 엔진", c: C.orange },
{ n: "06", t: "Phase 4 — 자율 운영 대시보드", c: C.blue },
{ n: "07", t: "테스트 결과 요약", c: C.green },
{ n: "08", t: "보안 제약사항 & 향후 로드맵", c: C.navy },
];
items.forEach((item, i) => {
const col = i < 4 ? 0 : 1;
const row = i % 4;
const x = 0.3 + col * 4.85;
const y = 0.88 + row * 1.1;
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 4.5, h: 0.9,
fill: { color: C.white },
line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 0.55, h: 0.9,
fill: { color: item.c },
line: { color: item.c }
});
slide.addText(item.n, {
x, y, w: 0.55, h: 0.9,
fontSize: 16, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
slide.addText(item.t, {
x: x + 0.62, y, w: 3.8, h: 0.9,
fontSize: 12.5, color: "1E293B", bold: false,
align: "left", valign: "middle", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 3: GUARDiA ITSM 개요
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("01 GUARDiA ITSM 개요");
// 왼쪽: 기존 기능
slide.addText("기존 GUARDiA 기능", {
x: 0.25, y: 0.85, w: 4.5, h: 0.4,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const existingFeatures = [
["SR 접수·처리", "서비스 요청 워크플로우"],
["SSL 인증서 관리", "만료 감시 & 갱신 알림"],
["PM 정기점검", "예방 유지보수 일정"],
["SI 프로젝트 관리", "WBS·이슈·리스크·테스트"],
["CMDB", "서버 자산 관리"],
["메신저·알림", "내부 커뮤니케이션"],
];
existingFeatures.forEach(([title, desc], i) => {
const y = 1.3 + i * 0.63;
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y, w: 4.5, h: 0.55,
fill: { color: C.offWhite }, line: { color: C.lightGray, width: 1 }
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y, w: 0.07, h: 0.55,
fill: { color: C.blue }, line: { color: C.blue }
});
slide.addText([
{ text: title, options: { bold: true, color: "1E293B", breakLine: true } },
{ text: desc, options: { color: C.gray } }
], {
x: 0.4, y, w: 4.2, h: 0.55,
fontSize: 10.5, valign: "middle", fontFace: "Calibri"
});
});
// 화살표
slide.addShape(pres.shapes.RECTANGLE, {
x: 4.85, y: 2.5, w: 0.35, h: 0.6,
fill: { color: C.lightBlue }, line: { color: C.lightBlue }
});
slide.addText("AI\n추가", {
x: 4.82, y: 2.5, w: 0.41, h: 0.6,
fontSize: 9, color: C.white, bold: true, align: "center", valign: "middle", fontFace: "Calibri", margin: 0
});
// 오른쪽: AI 추가 목표
slide.addText("AI 에이전트 추가 목표", {
x: 5.3, y: 0.85, w: 4.4, h: 0.4,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const aiGoals = [
{ icon: "⚡", title: "반복 업무 자동화", desc: "장애 분류, KB 등록, SR 자동 생성", color: C.orange },
{ icon: "👁", title: "능동적 모니터링", desc: "SSL·WBS·PM 상태를 AI가 주기 감시", color: C.blue },
{ icon: "🤝", title: "사람-AI 협업", desc: "고위험 액션은 사람 승인 게이트 통과", color: C.green },
{ icon: "🔒", title: "완전 온프레미스", desc: "Ollama 로컬 LLM, 외부 API 완전 차단", color: C.purple },
];
aiGoals.forEach((g, i) => {
const y = 1.3 + i * 0.98;
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.3, y, w: 4.4, h: 0.85,
fill: { color: C.white }, line: { color: g.color, width: 2 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.3, y, w: 0.1, h: 0.85,
fill: { color: g.color }, line: { color: g.color }
});
slide.addText([
{ text: `${g.icon} ${g.title}`, options: { bold: true, color: "1E293B", breakLine: true } },
{ text: g.desc, options: { color: C.gray } }
], {
x: 5.48, y, w: 4.1, h: 0.85,
fontSize: 11, valign: "middle", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 4: Paperclip 프레임워크 소개
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("02 Paperclip 프레임워크 소개");
// 메인 설명
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y: 0.85, w: 9.5, h: 0.72,
fill: { color: "EEF2FF" }, line: { color: C.lightBlue, width: 1 }
});
slide.addText([
{ text: "Paperclip", options: { bold: true, color: C.blue } },
{ text: " — AI 에이전트 오케스트레이션 오픈소스 프레임워크 (github.com/paperclipai/paperclip)", options: { color: "1E293B" } }
], {
x: 0.35, y: 0.85, w: 9.3, h: 0.72,
fontSize: 13, valign: "middle", fontFace: "Calibri"
});
const features = [
{ title: "조직도 구조", desc: "CEO → CTO → 개발자/QA\n계층적 에이전트 관리", color: C.purple, icon: "🏢" },
{ title: "하트비트 시스템", desc: "에이전트가 주기적으로\n깨어나 작업 수행 후 대기", color: C.blue, icon: "💓" },
{ title: "이슈 추적", desc: "GitHub 스타일\n태스크/이슈 관리", color: C.teal, icon: "📋" },
{ title: "거버넌스 게이트", desc: "위험 수준에 따른\n사람 승인 워크플로우", color: C.orange, icon: "🔐" },
];
features.forEach((f, i) => {
const x = 0.25 + i * 2.4;
slide.addShape(pres.shapes.RECTANGLE, {
x, y: 1.72, w: 2.25, h: 1.95,
fill: { color: C.white }, line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y: 1.72, w: 2.25, h: 0.45,
fill: { color: f.color }, line: { color: f.color }
});
slide.addText(`${f.icon} ${f.title}`, {
x, y: 1.72, w: 2.25, h: 0.45,
fontSize: 11, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
slide.addText(f.desc, {
x: x + 0.1, y: 2.22, w: 2.05, h: 1.4,
fontSize: 11, color: "1E293B", align: "center", valign: "top", fontFace: "Calibri"
});
});
// GUARDiA 적용 전략
slide.addText("GUARDiA 적용 전략", {
x: 0.25, y: 3.8, w: 9.5, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const strategies = [
["개발 시", "Paperclip CLI로 에이전트 페르소나 정의 (CEO/CTO/Dev/QA)", C.purple],
["런타임", "AgentEngine (Python) 내장 — 외부 Paperclip 서버 불필요", C.blue],
["LLM", "Ollama localhost:11434 전용 — 외부 LLM API 완전 차단", C.red],
];
strategies.forEach(([label, desc, color], i) => {
const x = 0.25 + i * 3.2;
slide.addShape(pres.shapes.RECTANGLE, {
x, y: 4.2, w: 3.0, h: 1.05,
fill: { color: C.white }, line: { color: color, width: 2 },
shadow: makeShadow()
});
slide.addText([
{ text: label, options: { bold: true, color, breakLine: true } },
{ text: desc, options: { color: "1E293B" } }
], {
x: x + 0.1, y: 4.2, w: 2.8, h: 1.05,
fontSize: 10.5, valign: "middle", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 5: Phase 1 섹션 표지
// ═══════════════════════════════════════════════════════
addSectionSlide("1", "Paperclip 개발 도구 설정", "조직도 구성 · 에이전트 페르소나 · 거버넌스 규칙");
// ═══════════════════════════════════════════════════════
// Slide 6: Phase 1 상세
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("03 Phase 1 — Paperclip 개발 도구 설정");
// 조직도 트리
slide.addText("에이전트 조직도", {
x: 0.25, y: 0.85, w: 4.5, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const orgNodes = [
{ label: "CEO", x: 1.9, y: 1.3, color: C.purple },
{ label: "CTO", x: 0.5, y: 2.4, color: C.blue },
{ label: "PM_AGENT", x: 3.3, y: 2.4, color: C.teal },
{ label: "DEVELOPER", x: 0.0, y: 3.5, color: C.green },
{ label: "QA", x: 1.2, y: 3.5, color: C.amber },
];
// 연결선
slide.addShape(pres.shapes.LINE, { x: 2.4, y: 1.9, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } });
slide.addShape(pres.shapes.LINE, { x: 1.5, y: 2.25, w: 1.8, h: 0, line: { color: C.gray, width: 1.5 } });
slide.addShape(pres.shapes.LINE, { x: 1.5, y: 2.25, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } });
slide.addShape(pres.shapes.LINE, { x: 3.3, y: 2.25, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } });
slide.addShape(pres.shapes.LINE, { x: 0.65, y: 3.05, w: 1.55, h: 0, line: { color: C.gray, width: 1.5 } });
slide.addShape(pres.shapes.LINE, { x: 0.65, y: 3.05, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } });
slide.addShape(pres.shapes.LINE, { x: 1.7, y: 3.05, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } });
orgNodes.forEach(node => {
slide.addShape(pres.shapes.RECTANGLE, {
x: node.x, y: node.y, w: 1.2, h: 0.52,
fill: { color: node.color }, line: { color: node.color },
shadow: makeShadow()
});
slide.addText(node.label, {
x: node.x, y: node.y, w: 1.2, h: 0.52,
fontSize: 10, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
});
// 파일 구조
slide.addText("생성 파일 구조", {
x: 5.0, y: 0.85, w: 4.7, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.0, y: 1.25, w: 4.7, h: 3.2,
fill: { color: "1E293B" }, line: { color: "334155", width: 1 }
});
slide.addText([
{ text: "C:\\GUARDiA\\paperclip\\", options: { color: C.lightBlue, bold: true, breakLine: true } },
{ text: "├─ paperclip.config.json", options: { color: "A3E635", breakLine: true } },
{ text: "│ (조직도 · LLM 설정 · 거버넌스)", options: { color: "94A3B8", breakLine: true } },
{ text: "├─ README.md", options: { color: "A3E635", breakLine: true } },
{ text: "└─ agents/", options: { color: "FB923C", breakLine: true } },
{ text: " ├─ ceo.md", options: { color: "A3E635", breakLine: true } },
{ text: " ├─ cto.md", options: { color: "A3E635", breakLine: true } },
{ text: " ├─ developer.md", options: { color: "A3E635", breakLine: true } },
{ text: " └─ qa.md", options: { color: "A3E635" } },
], {
x: 5.15, y: 1.35, w: 4.4, h: 3.0,
fontSize: 10.5, valign: "top", fontFace: "Courier New"
});
// 거버넌스 표
slide.addText("거버넌스 규칙", {
x: 0.25, y: 4.1, w: 4.5, h: 0.32,
fontSize: 11, color: C.navy, bold: true, fontFace: "Calibri"
});
const govRows = [
[{ text: "액션", options: { bold: true, fill: { color: C.navy }, color: C.white } },
{ text: "승인 방식", options: { bold: true, fill: { color: C.navy }, color: C.white } }],
["code_commit / deploy", "사람 승인 필수"],
["delete_data", "CEO 승인 필수"],
["CRITICAL 장애 분류", "담당자 승인 필수"],
["KB 등록 / SSL SR", "자동 승인"],
];
slide.addTable(govRows, {
x: 0.25, y: 4.45, w: 4.5, h: 1.0,
colW: [2.5, 2.0],
border: { pt: 1, color: C.lightGray },
fontSize: 10, fontFace: "Calibri",
fill: { color: C.white }
});
}
// ═══════════════════════════════════════════════════════
// Slide 7: Phase 2 섹션 표지
// ═══════════════════════════════════════════════════════
addSectionSlide("2", "Ollama 로컬 LLM 설정", "guardia-agent 커스텀 모델 · 보안 온프레미스 추론");
// ═══════════════════════════════════════════════════════
// Slide 8: Phase 2 상세
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("04 Phase 2 — Ollama 로컬 LLM");
// 왼쪽: 아키텍처
slide.addText("추론 아키텍처", {
x: 0.25, y: 0.85, w: 4.8, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const archItems = [
{ label: "GUARDiA AgentEngine", color: C.blue, y: 1.3 },
{ label: "OllamaClient (HTTP)", color: C.teal, y: 2.0 },
{ label: "Ollama Server :11434", color: C.green, y: 2.7 },
{ label: "guardia-agent 모델", color: C.purple, y: 3.4 },
];
archItems.forEach((item, i) => {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.5, y: item.y, w: 3.8, h: 0.55,
fill: { color: item.color }, line: { color: item.color },
shadow: makeShadow()
});
slide.addText(item.label, {
x: 0.5, y: item.y, w: 3.8, h: 0.55,
fontSize: 12, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
if (i < archItems.length - 1) {
slide.addShape(pres.shapes.RECTANGLE, {
x: 2.25, y: item.y + 0.55, w: 0.3, h: 0.15,
fill: { color: C.gray }, line: { color: C.gray }
});
}
});
// 보안 배지
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y: 4.1, w: 4.5, h: 0.9,
fill: { color: "FEF2F2" }, line: { color: C.red, width: 2 }
});
slide.addText([
{ text: "🔒 외부 LLM API 완전 차단", options: { bold: true, color: C.red, breakLine: true } },
{ text: "모든 AI 추론은 localhost:11434 (Ollama) 만 허용", options: { color: "7F1D1D" } }
], {
x: 0.4, y: 4.1, w: 4.3, h: 0.9,
fontSize: 12, valign: "middle", fontFace: "Calibri"
});
// 오른쪽: 모델 설정
slide.addText("guardia-agent 모델 파라미터", {
x: 5.05, y: 0.85, w: 4.7, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.05, y: 1.25, w: 4.7, h: 2.0,
fill: { color: "1E293B" }, line: { color: "334155", width: 1 }
});
slide.addText([
{ text: "FROM llama3.1:8b", options: { color: "A3E635", breakLine: true } },
{ text: "SYSTEM \"\"\"", options: { color: "FB923C", breakLine: true } },
{ text: " GUARDiA ITSM AI 운영 에이전트", options: { color: "94A3B8", breakLine: true } },
{ text: " 한국어 응답 · ITSM 전문화", options: { color: "94A3B8", breakLine: true } },
{ text: " 외부 API 호출 금지", options: { color: C.red, breakLine: true } },
{ text: "\"\"\"", options: { color: "FB923C", breakLine: true } },
{ text: "PARAMETER temperature 0.2", options: { color: "38BDF8", breakLine: true } },
{ text: "PARAMETER num_predict 2048", options: { color: "38BDF8" } },
], {
x: 5.2, y: 1.35, w: 4.4, h: 1.8,
fontSize: 10.5, valign: "top", fontFace: "Courier New"
});
// OllamaClient API
slide.addText("OllamaClient API", {
x: 5.05, y: 3.35, w: 4.7, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const methods = [
["health_check()", "서버 상태 확인"],
["resolve_model(preferred)", "fallback 모델 선택"],
["json_generate(prompt)", "JSON 추출 (코드블록 자동 제거)"],
["pull_model(model)", "모델 다운로드"],
];
methods.forEach(([method, desc], i) => {
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.05, y: 3.75 + i * 0.43, w: 4.7, h: 0.38,
fill: { color: i % 2 === 0 ? C.offWhite : C.white },
line: { color: C.lightGray, width: 1 }
});
slide.addText([
{ text: method, options: { bold: true, color: C.blue } },
{ text: " → " + desc, options: { color: C.gray } }
], {
x: 5.15, y: 3.75 + i * 0.43, w: 4.5, h: 0.38,
fontSize: 10, valign: "middle", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 9: Phase 3 섹션 표지
// ═══════════════════════════════════════════════════════
addSectionSlide("3", "GUARDiA 에이전트 엔진", "6종 자율 에이전트 · 하트비트 사이클 · 승인 게이트");
// ═══════════════════════════════════════════════════════
// Slide 10: Phase 3 — 에이전트 종류
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("05 Phase 3 — 6종 에이전트 역할");
const agents = [
{
role: "INCIDENT_TRIAGE",
schedule: "매 15분",
color: C.orange,
lines: ["미배정 장애 자동 분류", "severity / category / reason", "CRITICAL → 사람 승인"]
},
{
role: "KB_CURATOR",
schedule: "매시간 정각",
color: C.teal,
lines: ["완료 SR → KB 자동 생성", "증상·원인·해결책 초안", "published=False (검토 대기)"]
},
{
role: "SSL_WATCHER",
schedule: "매일 08:30",
color: C.red,
lines: ["SSL 만료 0~30일 서버 감시", "자동 갱신 SR 생성", "7일↓=CRITICAL, 30일↓=HIGH"]
},
{
role: "WBS_MONITOR",
schedule: "매일 08:00",
color: C.blue,
lines: ["SI WBS 지연 감지", "3일+ 주의, 10일+ CRITICAL", "리스크 자동 등록"]
},
{
role: "PM_SUGGESTER",
schedule: "매일 09:00",
color: C.green,
lines: ["PM 미등록 서버 탐지", "권장 점검 일정 제안", "AgentTask 기록"]
},
{
role: "DEVELOPER",
schedule: "수동 트리거",
color: C.purple,
lines: ["PENDING 태스크 처리", "LLM 코드 생성", "CODE_CHANGE → 사람 승인"]
},
];
agents.forEach((a, i) => {
const col = i % 3;
const row = Math.floor(i / 3);
const x = 0.25 + col * 3.25;
const y = 0.88 + row * 2.35;
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 3.1, h: 2.18,
fill: { color: C.white }, line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 3.1, h: 0.5,
fill: { color: a.color }, line: { color: a.color }
});
slide.addText(a.role, {
x, y, w: 2.3, h: 0.5,
fontSize: 11, color: C.white, bold: true,
align: "left", valign: "middle", fontFace: "Calibri",
indent: 0.1, margin: 0
});
slide.addText(a.schedule, {
x: x + 2.3, y, w: 0.8, h: 0.5,
fontSize: 9, color: "FFEAA7",
align: "center", valign: "middle", fontFace: "Calibri", margin: 0
});
a.lines.forEach((line, li) => {
slide.addText([
{ text: "▸ ", options: { color: a.color, bold: true } },
{ text: line, options: { color: "1E293B" } }
], {
x: x + 0.1, y: y + 0.55 + li * 0.52, w: 2.9, h: 0.48,
fontSize: 10.5, valign: "top", fontFace: "Calibri"
});
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 11: Phase 3 — 하트비트 & 승인 게이트
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("05 Phase 3 — 하트비트 & 승인 게이트");
// 하트비트 플로우
slide.addText("하트비트 사이클", {
x: 0.25, y: 0.85, w: 4.7, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const flowSteps = [
{ label: "APScheduler\nCron 트리거", color: C.blue, y: 1.3 },
{ label: "status = ACTIVE", color: C.teal, y: 2.05 },
{ label: "Ollama\nhealth_check()", color: C.green, y: 2.8 },
{ label: "_handler() 실행", color: C.orange, y: 3.55 },
{ label: "status = IDLE\n(완료)", color: C.purple, y: 4.3 },
];
flowSteps.forEach((step, i) => {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.5, y: step.y, w: 3.0, h: 0.6,
fill: { color: step.color }, line: { color: step.color },
shadow: makeShadow()
});
slide.addText(step.label, {
x: 0.5, y: step.y, w: 3.0, h: 0.6,
fontSize: 10.5, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
if (i < flowSteps.length - 1) {
slide.addShape(pres.shapes.RECTANGLE, {
x: 1.85, y: step.y + 0.6, w: 0.3, h: 0.15,
fill: { color: C.gray }, line: { color: C.gray }
});
}
});
// 오른쪽: 승인 게이트
slide.addText("승인 게이트 설계", {
x: 4.0, y: 0.85, w: 5.7, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
// AUTO_APPROVED
slide.addShape(pres.shapes.RECTANGLE, {
x: 4.0, y: 1.25, w: 5.7, h: 1.3,
fill: { color: "F0FDF4" }, line: { color: C.green, width: 2 }
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 4.0, y: 1.25, w: 0.1, h: 1.3,
fill: { color: C.green }, line: { color: C.green }
});
slide.addText([
{ text: "✅ AUTO_APPROVED ", options: { bold: true, color: C.green } },
{ text: "— 사람 개입 없이 즉시 실행", options: { color: "166534" } }
], {
x: 4.18, y: 1.3, w: 5.4, h: 0.35,
fontSize: 12, fontFace: "Calibri"
});
slide.addText([
{ text: "• KB 등록 초안 생성", options: { breakLine: true } },
{ text: "• SSL 갱신 SR 생성 ", options: { breakLine: true } },
{ text: "• PM 일정 제안 ", options: { breakLine: true } },
{ text: "• WBS 리스크 등록 (일반)", options: {} },
], {
x: 4.18, y: 1.68, w: 5.4, h: 0.82,
fontSize: 10.5, color: "166534", valign: "top", fontFace: "Calibri"
});
// PENDING (사람 승인)
slide.addShape(pres.shapes.RECTANGLE, {
x: 4.0, y: 2.7, w: 5.7, h: 1.5,
fill: { color: "FFF7ED" }, line: { color: C.red, width: 2 }
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 4.0, y: 2.7, w: 0.1, h: 1.5,
fill: { color: C.red }, line: { color: C.red }
});
slide.addText([
{ text: "⏳ PENDING ", options: { bold: true, color: C.red } },
{ text: "— 담당자 승인 후 실행", options: { color: "7C2D12" } }
], {
x: 4.18, y: 2.75, w: 5.4, h: 0.35,
fontSize: 12, fontFace: "Calibri"
});
slide.addText([
{ text: "• CRITICAL 장애 분류 적용", options: { breakLine: true } },
{ text: "• CODE_CHANGE (개발자 에이전트)", options: { breakLine: true } },
{ text: "• CRITICAL 리스크 등록 (WBS 10일+ 지연)", options: { breakLine: true } },
{ text: "• code_commit / deploy / delete_data", options: {} }
], {
x: 4.18, y: 3.12, w: 5.4, h: 1.0,
fontSize: 10.5, color: "7C2D12", valign: "top", fontFace: "Calibri"
});
// 데이터 모델 요약
slide.addText("DB 테이블 구조", {
x: 4.0, y: 4.35, w: 5.7, h: 0.32,
fontSize: 11, color: C.navy, bold: true, fontFace: "Calibri"
});
const tables = [
["tb_agent_config", "에이전트 설정 · 상태 · 통계"],
["tb_agent_task", "실행된 태스크 · LLM 입출력"],
["tb_agent_approval", "승인 대기·완료 기록"],
];
tables.forEach(([t, d], i) => {
slide.addShape(pres.shapes.RECTANGLE, {
x: 4.0 + i * 1.9, y: 4.7, w: 1.8, h: 0.7,
fill: { color: C.navy }, line: { color: C.navy },
shadow: makeShadow()
});
slide.addText([
{ text: t, options: { bold: true, color: C.ice, breakLine: true } },
{ text: d, options: { color: "8FAADC" } }
], {
x: 4.05 + i * 1.9, y: 4.72, w: 1.7, h: 0.66,
fontSize: 9, align: "center", valign: "middle", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 12: Phase 4 섹션 표지
// ═══════════════════════════════════════════════════════
addSectionSlide("4", "자율 운영 대시보드", "agents.html SPA · 실시간 상태 · 승인 워크플로우");
// ═══════════════════════════════════════════════════════
// Slide 13: Phase 4 상세
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("06 Phase 4 — 자율 운영 대시보드");
// 접근 경로 배너
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y: 0.85, w: 9.5, h: 0.5,
fill: { color: C.navy }, line: { color: C.navy }
});
slide.addText([
{ text: "접근: ", options: { color: C.ice } },
{ text: "http://localhost:8001/agents", options: { bold: true, color: "A3E635" } },
{ text: " | 자동 갱신: 30초 간격", options: { color: C.ice } }
], {
x: 0.35, y: 0.85, w: 9.3, h: 0.5,
fontSize: 12, valign: "middle", fontFace: "Calibri"
});
// 대시보드 구성 카드
const panels = [
{ title: "LLM 상태 배너", desc: "Ollama 온라인/오프라인\n실시간 연결 상태 표시", color: C.teal },
{ title: "통계 카드 (5종)", desc: "총 에이전트·활성·오늘\n태스크·토큰·승인 대기", color: C.blue },
{ title: "에이전트 탭", desc: "역할 배지 + 상태 펄스\n하트비트/정지/재개 버튼", color: C.purple },
{ title: "조직도 탭", desc: "계층적 트리 렌더링\n에이전트 관계 시각화", color: C.orange },
{ title: "승인 대기 탭", desc: "PENDING 승인 목록\nCRITICAL 강조 표시", color: C.red },
{ title: "태스크 피드 탭", desc: "전체 에이전트 태스크\n실시간 피드 통합 뷰", color: C.green },
];
panels.forEach((p, i) => {
const col = i % 3;
const row = Math.floor(i / 3);
const x = 0.25 + col * 3.25;
const y = 1.48 + row * 1.85;
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 3.1, h: 1.65,
fill: { color: C.white }, line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 3.1, h: 0.4,
fill: { color: p.color }, line: { color: p.color }
});
slide.addText(p.title, {
x, y, w: 3.1, h: 0.4,
fontSize: 11, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
slide.addText(p.desc, {
x: x + 0.1, y: y + 0.45, w: 2.9, h: 1.15,
fontSize: 11, color: "1E293B", align: "center", valign: "middle", fontFace: "Calibri"
});
});
// API 엔드포인트 수
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y: 5.15, w: 9.5, h: 0.35,
fill: { color: "EEF2FF" }, line: { color: C.lightBlue, width: 1 }
});
slide.addText("REST API: 16개 엔드포인트 | GET·POST·PATCH·DELETE | CUSTOMER 역할 전체 차단 | RBAC 기반 권한 제어", {
x: 0.35, y: 5.15, w: 9.3, h: 0.35,
fontSize: 10.5, color: C.blue, align: "center", valign: "middle", fontFace: "Calibri"
});
}
// ═══════════════════════════════════════════════════════
// Slide 14: 테스트 결과
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("07 테스트 결과 요약");
// 구문 검사 결과
slide.addText("구문 검사 (Python Syntax Check)", {
x: 0.25, y: 0.85, w: 9.5, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const syntaxFiles = [
"models.py", "main.py", "core/llm_client.py",
"core/agents.py", "core/scheduler.py", "routers/agents.py"
];
syntaxFiles.forEach((f, i) => {
const x = 0.25 + (i % 3) * 3.25;
const y = 1.25 + Math.floor(i / 3) * 0.6;
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 3.1, h: 0.5,
fill: { color: "F0FDF4" }, line: { color: C.green, width: 1 }
});
slide.addText([
{ text: "✅ ", options: { color: C.green, bold: true } },
{ text: f, options: { color: "1E293B" } }
], {
x: x + 0.1, y, w: 2.9, h: 0.5,
fontSize: 11, valign: "middle", fontFace: "Calibri"
});
});
// 심볼 검증
slide.addText("심볼 검증", {
x: 0.25, y: 2.55, w: 4.7, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const symbols = [
["AgentRole enum", "10가지 역할", C.purple],
["AgentEngine 핸들러", "6개 구현", C.orange],
["OllamaClient 메서드", "7개 구현", C.teal],
["API 엔드포인트", "16개 라우터", C.blue],
["스케줄러 잡", "9개 (기존4+신규5)", C.green],
["main.py 라우터", "34개 등록", C.navy],
];
symbols.forEach((s, i) => {
const x = 0.25 + (i % 2) * 2.45;
const y = 2.95 + Math.floor(i / 2) * 0.6;
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 2.3, h: 0.52,
fill: { color: C.white }, line: { color: s[2], width: 2 },
shadow: makeShadow()
});
slide.addText([
{ text: s[0] + "\n", options: { bold: true, color: "1E293B", fontSize: 10 } },
{ text: s[1], options: { color: s[2], bold: true, fontSize: 11 } }
], {
x: x + 0.08, y, w: 2.14, h: 0.52,
valign: "middle", fontFace: "Calibri"
});
});
// 스케줄러 잡 목록
slide.addText("스케줄러 잡 (총 9개)", {
x: 5.2, y: 2.55, w: 4.5, h: 0.35,
fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri"
});
const jobs = [
["cert_check", "매일 09:00", C.gray],
["pm_check", "매일 09:05", C.gray],
["on_call_notify", "매일 08:55", C.gray],
["batch_cleanup", "매일 02:00", C.gray],
["agent_incident_triage", "매 15분", C.orange],
["agent_kb_curator", "매시간", C.teal],
["agent_ssl_watcher", "매일 08:30", C.red],
["agent_wbs_monitor", "매일 08:00", C.blue],
["agent_pm_suggester", "매일 09:00", C.green],
];
jobs.forEach((j, i) => {
const y = 2.95 + i * 0.3;
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.2, y, w: 4.5, h: 0.27,
fill: { color: i < 4 ? C.offWhite : "F0FDF4" },
line: { color: C.lightGray, width: 1 }
});
slide.addShape(pres.shapes.RECTANGLE, {
x: 5.2, y, w: 0.07, h: 0.27,
fill: { color: j[2] }, line: { color: j[2] }
});
slide.addText([
{ text: j[0], options: { bold: i >= 4, color: i >= 4 ? "1E293B" : C.gray } },
{ text: " " + j[1], options: { color: j[2], bold: i >= 4 } }
], {
x: 5.32, y, w: 4.3, h: 0.27,
fontSize: 9.5, valign: "middle", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 15: 보안 제약사항
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("08 보안 제약사항");
const secRules = [
{
cat: "LLM 보안",
color: C.red,
rules: [
"외부 LLM API 완전 차단 (OpenAI/Claude 등)",
"Ollama localhost:11434 전용 사용",
"temperature 0.2 고정 (결정론적 응답)",
"HTTP 요청 30초 타임아웃"
]
},
{
cat: "서버 정보 보호",
color: C.orange,
rules: [
"ip_addr — ServerOut 스키마 미포함",
"ssh_user — API 응답 절대 미노출",
"os_pw_enc — AES-256-GCM 암호화 필수",
"file_path — API 응답 완전 제거"
]
},
{
cat: "에이전트 액션 제어",
color: C.purple,
rules: [
"CRITICAL 액션 → 사람 승인 후 실행",
"위험 명령어 차단: rm -rf /, shutdown, mkfs",
"fork bomb 패턴 감지 & 차단",
"경로 순회 방지 (resolve().relative_to)"
]
},
{
cat: "접근 제어 (RBAC)",
color: C.blue,
rules: [
"CUSTOMER 역할 — 에이전트 API 전체 차단",
"에이전트 생성/수정/삭제 — ADMIN만 허용",
"승인 처리 — USER 이상 허용",
"스택트레이스 — API 응답 노출 금지"
]
}
];
secRules.forEach((sec, i) => {
const x = 0.25 + (i % 2) * 4.9;
const y = 0.88 + Math.floor(i / 2) * 2.35;
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 4.65, h: 2.2,
fill: { color: C.white }, line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 4.65, h: 0.42,
fill: { color: sec.color }, line: { color: sec.color }
});
slide.addText(sec.cat, {
x, y, w: 4.65, h: 0.42,
fontSize: 12, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
const textArr = sec.rules.map((r, ri) => ({
text: "▸ " + r,
options: {
color: ri === 0 ? "0F172A" : "1E293B",
bold: ri === 0,
fontSize: 10.5,
breakLine: ri < sec.rules.length - 1
}
}));
slide.addText(textArr, {
x: x + 0.12, y: y + 0.48, w: 4.4, h: 1.65,
valign: "top", fontFace: "Calibri"
});
});
}
// ═══════════════════════════════════════════════════════
// Slide 16: 향후 로드맵
// ═══════════════════════════════════════════════════════
{
const slide = addContentSlide("08 향후 로드맵");
const roadmap = [
{ ver: "v1.1", period: "2026 Q3", title: "에이전트 간 메시지 전달", desc: "CEO→CTO 위임 워크플로우\n태스크 자동 위임 체계", color: C.blue },
{ ver: "v1.2", period: "2026 Q3", title: "에이전트 학습", desc: "이전 태스크 기반 파인튜닝\n조직 맞춤형 응답 최적화", color: C.teal },
{ ver: "v1.3", period: "2026 Q4", title: "멀티모달 지원", desc: "스크린샷·로그 이미지 분석\n시각 장애 자동 탐지", color: C.green },
{ ver: "v2.0", period: "2027 Q1", title: "에이전트 마켓플레이스", desc: "커뮤니티 에이전트 공유\nPlug-in 방식 등록/사용", color: C.purple },
{ ver: "v2.1", period: "2027 Q1", title: "엣지 배포", desc: "경량 모델 (llama3.2:1b)\n저사양 서버 지원", color: C.orange },
];
roadmap.forEach((r, i) => {
const x = 0.25 + i * 1.92;
slide.addShape(pres.shapes.RECTANGLE, {
x, y: 0.88, w: 1.75, h: 4.5,
fill: { color: C.white }, line: { color: C.lightGray, width: 1 },
shadow: makeShadow()
});
slide.addShape(pres.shapes.RECTANGLE, {
x, y: 0.88, w: 1.75, h: 0.55,
fill: { color: r.color }, line: { color: r.color }
});
slide.addText(r.ver, {
x, y: 0.88, w: 1.75, h: 0.55,
fontSize: 16, color: C.white, bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
slide.addShape(pres.shapes.RECTANGLE, {
x: x + 0.1, y: 1.5, w: 1.55, h: 0.38,
fill: { color: r.color, transparency: 80 }, line: { color: r.color, width: 1 }
});
slide.addText(r.period, {
x: x + 0.1, y: 1.5, w: 1.55, h: 0.38,
fontSize: 10, color: "1E293B", bold: true,
align: "center", valign: "middle", margin: 0, fontFace: "Calibri"
});
slide.addText(r.title, {
x: x + 0.1, y: 2.0, w: 1.55, h: 0.65,
fontSize: 11, color: "0F172A", bold: true,
align: "center", fontFace: "Calibri"
});
slide.addShape(pres.shapes.LINE, {
x: x + 0.2, y: 2.7, w: 1.35, h: 0,
line: { color: C.lightGray, width: 1 }
});
slide.addText(r.desc, {
x: x + 0.1, y: 2.75, w: 1.55, h: 1.55,
fontSize: 10.5, color: C.gray,
align: "center", valign: "top", fontFace: "Calibri"
});
});
// 하단 현재 완료 배너
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.25, y: 5.1, w: 9.5, h: 0.38,
fill: { color: C.green }, line: { color: C.green }
});
slide.addText("현재 완료: Phase 1~4 | 6종 에이전트 | 16개 API | 9개 스케줄러 잡 | 자율 운영 대시보드", {
x: 0.35, y: 5.1, w: 9.3, h: 0.38,
fontSize: 11, color: C.white, bold: true,
align: "center", valign: "middle", fontFace: "Calibri"
});
}
// ═══════════════════════════════════════════════════════
// Slide 17: 마무리
// ═══════════════════════════════════════════════════════
{
const slide = addTitleSlide(
C.navyDark,
"GUARDiA × Paperclip\n구현 완료",
"Phase 1~4 완료 | 온프레미스 AI 자율 운영 체계 구축",
"완료"
);
// 요약 통계 카드
const stats = [
{ num: "6", label: "자율 에이전트", color: C.orange },
{ num: "16", label: "REST API 엔드포인트", color: C.blue },
{ num: "9", label: "스케줄러 잡", color: C.green },
{ num: "100%", label: "구문 검사 통과", color: C.teal },
];
stats.forEach((s, i) => {
const x = 0.4 + i * 2.3;
slide.addShape(pres.shapes.RECTANGLE, {
x, y: 4.05, w: 2.1, h: 1.3,
fill: { color: s.color, transparency: 15 },
line: { color: s.color, width: 2 }
});
slide.addText(s.num, {
x, y: 4.08, w: 2.1, h: 0.7,
fontSize: 28, color: C.white, bold: true,
align: "center", valign: "middle", fontFace: "Calibri", margin: 0
});
slide.addText(s.label, {
x, y: 4.75, w: 2.1, h: 0.55,
fontSize: 10, color: C.ice,
align: "center", valign: "top", fontFace: "Calibri", margin: 0
});
});
}
// ─── 파일 저장 ───────────────────────────────────────
pres.writeFile({ fileName: "C:/GUARDiA/manual/GUARDiA_Paperclip_AI에이전트_구현보고서.pptx" })
.then(() => console.log("PPT 생성 완료: GUARDiA_Paperclip_AI에이전트_구현보고서.pptx"))
.catch(err => { console.error("PPT 생성 실패:", err); process.exit(1); });