openfree commited on
Commit
a5266aa
·
verified ·
1 Parent(s): ffe737f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -1882
app.py CHANGED
@@ -1,1895 +1,36 @@
1
- # app.py
2
- """Main PPT Generator with 3-AI collaboration system"""
3
-
4
  import os
5
- import re
6
- import json
7
- import time
8
- import logging
9
- import gradio as gr
10
- import requests
11
- from typing import List, Dict, Generator
12
- from datetime import datetime
13
-
14
- # Import separated modules
15
- from constants import (
16
- EXAMPLE_TOPICS, AUDIENCE_TYPES, DESIGN_THEMES,
17
- STYLE_TEMPLATES, PPT_TEMPLATES, SLIDE_TITLE_TRANSLATIONS
18
- )
19
- from content_utils import (
20
- parse_executor_response, extract_relevant_content_from_executor,
21
- extract_slide_section_from_executor, parse_slide_section,
22
- extract_speaker_notes_from_section, read_uploaded_file,
23
- generate_dynamic_slides, brave_search, generate_slide_content,
24
- generate_presentation_notes, generate_closing_notes,
25
- generate_conclusion_phrase, generate_prompt_with_llm,
26
- generate_image, create_slide_preview_html, create_pptx_file,
27
- PROCESS_FLOW_AVAILABLE
28
- )
29
- from ui_components import (
30
- create_custom_slides_ui, load_example, update_audience_info,
31
- update_theme_preview, update_template_info, update_custom_slides_visibility,
32
- clear_all, get_css, get_usage_instructions
33
- )
34
-
35
- # Logging setup
36
- logging.basicConfig(level=logging.INFO)
37
- logger = logging.getLogger(__name__)
38
-
39
- # Environment variables
40
- REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
41
- FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN")
42
- BRAVE_API_TOKEN = os.getenv("BAPI_TOKEN")
43
- API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
44
- BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search"
45
- MODEL_ID = "dep89a2fld32mcm"
46
- TEST_MODE = os.getenv("TEST_MODE", "false").lower() == "true"
47
-
48
- # Global variables
49
- current_slides_data = []
50
- current_topic = ""
51
- current_template = ""
52
- current_theme = ""
53
- uploaded_content = ""
54
- current_language = "English"
55
- conversation_history = []
56
-
57
- # ===== 핵심: 3자 협의 시스템 클래스 =====
58
- class PPTCreationSystem:
59
- def __init__(self):
60
- self.token = FRIENDLI_TOKEN
61
- self.bapi_token = BRAVE_API_TOKEN
62
- self.api_url = API_URL
63
- self.brave_url = BRAVE_SEARCH_URL
64
- self.model_id = MODEL_ID
65
- self.test_mode = TEST_MODE or (self.token == "YOUR_FRIENDLI_TOKEN")
66
-
67
- if self.test_mode:
68
- logger.warning("테스트 모드로 실행됩니다.")
69
- if self.bapi_token == "YOUR_BRAVE_API_TOKEN":
70
- logger.warning("Brave API 토큰이 설정되지 않았습니다.")
71
-
72
- def create_headers(self):
73
- """API 헤더 생성"""
74
- return {
75
- "Authorization": f"Bearer {self.token}",
76
- "Content-Type": "application/json"
77
- }
78
-
79
- def create_brave_headers(self):
80
- """Brave API 헤더 생성"""
81
- return {
82
- "Accept": "application/json",
83
- "Accept-Encoding": "gzip",
84
- "X-Subscription-Token": self.bapi_token
85
- }
86
-
87
- def create_supervisor_initial_prompt(self, ppt_topic: str) -> str:
88
- """감독자 AI 초기 프롬프트 생성 - PPT 구조 설계"""
89
- return f"""당신은 전문적인 PPT 구조를 설계하는 감독자 AI입니다.
90
-
91
- PPT 주제: {ppt_topic}
92
-
93
- 이 주제에 대한 전문적인 PPT를 만들기 위해:
94
-
95
- 1. **PPT 전체 구조 설계**
96
- - 목적과 핵심 메시지 정의
97
- - 타겟 청중 분석 및 톤 설정
98
- - 전체 슬라이드 개수 및 흐름 설계 (10-15장 권장)
99
-
100
- 2. **섹션별 구성 제안**
101
- - 도입부 (표지, 목차, 배경)
102
- - 본론 (핵심 내용 3-4개 섹션)
103
- - 결론부 (요약, 시사점, Q&A)
104
-
105
- 3. **조사가 필요한 핵심 키워드**
106
- 이 주제의 PPT를 위해 조사가 필요한 5-7개의 구체적인 키워드나 검색어를 제시하세요.
107
-
108
- 키워드는 다음 형식으로 제시하세요:
109
- [검색 키워드]: 키워드1, 키워드2, 키워드3, 키워드4, 키워드5"""
110
-
111
- def create_researcher_prompt(self, ppt_topic: str, supervisor_guidance: str, search_results: Dict[str, List[Dict]]) -> str:
112
- """조사자 AI 프롬프트 생성 - PPT 콘텐츠 조사"""
113
- search_summary = ""
114
- all_results = []
115
-
116
- for keyword, results in search_results.items():
117
- search_summary += f"\n\n**{keyword}에 대한 검색 결과:**\n"
118
- for i, result in enumerate(results[:10], 1): # 상위 10개만 표시
119
- search_summary += f"{i}. {result.get('title', 'N/A')} (신뢰도: {result.get('credibility_score', 0):.2f})\n"
120
- search_summary += f" - {result.get('description', 'N/A')}\n"
121
- search_summary += f" - 출처: {result.get('url', 'N/A')}\n"
122
- if result.get('published'):
123
- search_summary += f" - 게시일: {result.get('published')}\n"
124
-
125
- all_results.extend(results)
126
-
127
- # 모순 감지
128
- contradictions = self.detect_contradictions(all_results)
129
- contradiction_text = ""
130
- if contradictions:
131
- contradiction_text = "\n\n**발견된 정보 모순:**\n"
132
- for cont in contradictions[:3]: # 최대 3개만 표시
133
- contradiction_text += f"- {cont['type']}: {cont['source1']} vs {cont['source2']}\n"
134
-
135
- return f"""당신은 PPT 콘텐츠를 위한 정보를 조사하고 정리하는 조사자 AI입니다.
136
-
137
- PPT 주제: {ppt_topic}
138
-
139
- 감독자 AI의 구조 설계:
140
- {supervisor_guidance}
141
-
142
- 브레이브 검색 결과 (신뢰도 점수 포함):
143
- {search_summary}
144
- {contradiction_text}
145
-
146
- 위 검색 결과를 바탕으로 PPT에 포함할 핵심 콘텐츠를 정리하세요:
147
-
148
- 1. **시각적 자료 가능한 통계/수치**
149
- - 그래프나 차트로 표현 가능한 데이터
150
- - 인포그래픽으로 변환 가능한 정보
151
-
152
- 2. **핵심 인사이트와 트렌드**
153
- - 청중의 관심을 끌 수 있는 최신 정보
154
- - 업계 동향과 미래 전망
155
-
156
- 3. **사례 연구 및 성공 스토리**
157
- - 실제 적용 사례
158
- - 벤치마크할 수 있는 모범 사례
159
-
160
- 4. **전문가 의견 및 인용구**
161
- - 권위 있는 출처의 의견
162
- - PPT에 인용할 수 있는 명언
163
-
164
- 5. **시각 자료 추천**
165
- - 각 슬라이드에 적합한 이미지/아이콘 제안
166
- - 도표나 다이어그램 아이디어"""
167
-
168
- def create_supervisor_execution_prompt(self, ppt_topic: str, research_summary: str) -> str:
169
- """감독자 AI의 실행 지시 프롬프트 - PPT 제작 지시"""
170
- return f"""당신은 PPT 제작을 총괄하는 감독자 AI입니다.
171
-
172
- PPT 주제: {ppt_topic}
173
-
174
- 조사자 AI가 정리한 콘텐츠:
175
- {research_summary}
176
-
177
- 위 조사 내용을 기반으로 실행자 AI에게 구체적인 PPT 제작 지시를 내려주세요:
178
-
179
- 1. **슬라이드별 상세 구성**
180
- - 각 슬라이드의 제목과 핵심 메시지
181
- - 포함할 구체적인 콘텐츠 지정
182
- - 시각 자료 배치 방안
183
-
184
- 2. **디자인 가이드라인**
185
- - 전체적인 톤과 스타일 (전문적/창의적/미니멀 등)
186
- - 색상 팔레트 제안
187
- - 폰트 및 레이아웃 원칙
188
-
189
- 3. **스토리텔링 전략**
190
- - 도입부에서 청중의 관심을 끄는 방법
191
- - 핵심 메시지 전달 순서
192
- - 결론부의 임팩트 있는 마무리
193
-
194
- 4. **시각화 지침**
195
- - 각 데이터의 최적 시각화 방법
196
- - 복잡한 개념의 단순화 방안
197
- - 애니메이션 효과 제안"""
198
-
199
- def create_executor_prompt(self, ppt_topic: str, supervisor_guidance: str, research_summary: str) -> str:
200
- """실행자 AI 프롬프트 생성 - PPT 슬라이드 작성"""
201
- return f"""당신은 실제 PPT 슬라이드 내용을 작성하는 실행자 AI입니다.
202
-
203
- PPT 주제: {ppt_topic}
204
-
205
- 조사자 AI가 정리한 콘텐츠:
206
- {research_summary}
207
-
208
- 감독자 AI의 제작 지시:
209
- {supervisor_guidance}
210
-
211
- 위 정보를 바탕으로 실제 PPT 슬라이드를 작성하세요:
212
-
213
- 1. **각 슬라이드를 다음 형식으로 작성**:
214
- [슬라이드 X]
215
- 제목:
216
- 부제목: (선택사항)
217
- • 🎯 첫 번째 핵심 포인트
218
- • 💡 두 번째 핵심 포인트
219
- • 🚀 세 번째 핵심 포인트
220
- • ✅ 네 번째 핵심 포인트
221
- • 📊 다섯 번째 핵심 포인트
222
- 발표자 노트: (실제 발표할 때 사용할 구어체 스크립트 - 최소 3-4문장)
223
-
224
- 2. **작성 원칙**
225
- - 한 슬라이드에 하나의 핵심 메시지
226
- - 텍스트는 최소화, 시각적 요소 극대화
227
- - 6x6 규칙 준수 (6줄 이하, 줄당 6단어 이하)
228
- - 전문적이면서도 이해하기 쉬운 언어 사용
229
- - 각 불릿 포인트는 반드시 이모지로 시작
230
-
231
- 3. **발표자 노트 작성 지침**
232
- - 슬라이드 내용을 자연스럽게 설명하는 구어체 사용
233
- - 각 포인트를 풀어서 설명하고 연결
234
- - 청중과 소통하는 느낌의 대화체 ("여러분", "우리가", "함께")
235
- - 최소 3-4문장으로 풍부하게 작성
236
- - 예시: "자, 이제 우리가 주목해야 할 핵심 포인트들을 살펴보겠습니다. 첫 번째로 시장 규모를 보시면..."
237
-
238
- 4. **필수 포함 슬라이드**
239
- - 표지 (제목, 부제목, 발표자/날짜)
240
- - 목차
241
- - 핵심 내용 슬라이드들
242
- - 요약/결론
243
- - Q&A/감사 슬라이드"""
244
-
245
- def create_supervisor_review_prompt(self, ppt_topic: str, executor_response: str, research_summary: str) -> str:
246
- """감독자 AI의 검토 프롬프트 - PPT 검토 및 피드백"""
247
- return f"""당신은 PPT 품질을 검토하고 개선점을 제시하는 감독자 AI입니다.
248
-
249
- PPT 주제: {ppt_topic}
250
-
251
- 조사자 AI가 제공한 콘텐츠:
252
- {research_summary}
253
-
254
- 실행자 AI의 PPT 초안:
255
- {executor_response}
256
-
257
- 위 PPT 초안을 검토하고 다음 관점에서 구체적인 피드백을 제공하세요:
258
-
259
- 1. **콘텐츠 품질**
260
- - 조사 내용이 충분히 반영되었는지
261
- - 각 슬라이드의 메시지가 명확한지
262
- - 논리적 흐름이 자연���러운지
263
-
264
- 2. **시각적 효과**
265
- - 텍스트와 시각 요소의 균형
266
- - 슬라이드별 일관성
267
- - 청중의 이해를 돕는 구성인지
268
-
269
- 3. **발표 준비도**
270
- - 발표자 노트의 충실성
271
- - 예상 질문에 대한 대비
272
- - 시간 배분의 적절성
273
-
274
- 4. **개선 필요사항**
275
- - 구체적인 수정 지시사항
276
- - 추가해야 할 내용
277
- - 삭제하거나 간소화할 부분
278
-
279
- 5. **최종 점검사항**
280
- - 오타나 문법 오류
281
- - 데이터의 정확성
282
- - 전문성과 신뢰성"""
283
-
284
- def create_executor_final_prompt(self, ppt_topic: str, initial_response: str, supervisor_feedback: str, research_summary: str) -> str:
285
- """실행자 AI 최종 PPT 프롬프트"""
286
- return f"""당신은 최종 PPT를 완성하는 실행자 AI입니다.
287
-
288
- PPT 주제: {ppt_topic}
289
-
290
- 조사자 AI의 콘텐츠:
291
- {research_summary}
292
-
293
- 당신의 초기 PPT 초안:
294
- {initial_response}
295
-
296
- 감독자 AI의 피드백:
297
- {supervisor_feedback}
298
-
299
- 위 피드백을 완전히 반영하여 최종 PPT를 작성하세요:
300
-
301
- 1. **개선사항 반영**
302
- - 감독자의 모든 피드백 적용
303
- - 스토리라인 강화
304
- - 시각적 일관성 확보
305
-
306
- 2. **발표자 노트 강화**
307
- - 각 슬라이드마다 풍부한 구어체 발표 스크립트 작성
308
- - 최소 4-5문장으로 자세하게
309
- - 실제 발표 상황을 가정한 자연스러운 대화체
310
- - 예시: "자, 이제 시장 현황을 살펴보겠습니다. 여러분도 아시다시피 최근 디지털 전환이 가속화되면서..."
311
-
312
- 3. **최종 품질 체크**
313
- - 전문성과 완성도 향상
314
- - 오타 및 문법 검토
315
- - 논리적 흐름 재확인
316
-
317
- 4. **프레젠테이션 준비**
318
- - 각 슬라이드별 발표 스크립트 추가
319
- - 예상 질문과 답변 준비
320
- - 시간 배분 제안 (20분 발표 기준)
321
-
322
- 5. **추가 자료**
323
- - 백업 슬라이드 제안
324
- - 참고 자료 목록
325
- - 인쇄용 핸드아웃 버전 제안"""
326
-
327
- def extract_keywords(self, supervisor_response: str) -> List[str]:
328
- """감독자 응답에서 키워드 추출"""
329
- keywords = []
330
-
331
- # [검색 키워드]: 형식으로 키워드 찾기
332
- keyword_match = re.search(r'\[검색 키워드\]:\s*(.+)', supervisor_response, re.IGNORECASE)
333
- if keyword_match:
334
- keyword_str = keyword_match.group(1)
335
- keywords = [k.strip() for k in keyword_str.split(',') if k.strip()]
336
-
337
- # 키워드가 없으면 기본 키워드 생성
338
- if not keywords:
339
- keywords = ["industry trends", "market analysis", "case studies", "best practices", "future outlook"]
340
-
341
- return keywords[:7] # 최대 7개로 제한
342
-
343
- def generate_synonyms(self, keyword: str) -> List[str]:
344
- """키워드의 동의어/유사어 생성"""
345
- synonyms = {
346
- "trend": ["tendency", "direction", "movement", "pattern"],
347
- "analysis": ["evaluation", "assessment", "study", "research"],
348
- "strategy": ["approach", "plan", "tactics", "methodology"],
349
- "market": ["industry", "sector", "business", "commerce"],
350
- "innovation": ["breakthrough", "advancement", "revolution", "disruption"],
351
- "growth": ["expansion", "development", "progress", "advancement"],
352
- "presentation": ["pitch", "proposal", "demonstration", "showcase"],
353
- "data": ["statistics", "metrics", "analytics", "insights"],
354
- "technology": ["tech", "digital", "IT", "innovation"],
355
- "business": ["enterprise", "corporate", "commercial", "company"]
356
- }
357
-
358
- # 키워드 정규화
359
- keyword_lower = keyword.lower()
360
-
361
- # 직접 매칭되는 동의어가 있으면 반환
362
- if keyword_lower in synonyms:
363
- return synonyms[keyword_lower][:2] # 최대 2개
364
-
365
- # 부분 매칭 확인
366
- for key, values in synonyms.items():
367
- if key in keyword_lower or keyword_lower in key:
368
- return values[:2]
369
-
370
- # 동의어가 없으면 빈 리스트
371
- return []
372
-
373
- def calculate_credibility_score(self, result: Dict) -> float:
374
- """검색 결과의 신뢰도 점수 계산 (0-1)"""
375
- score = 0.5 # 기본 점수
376
-
377
- url = result.get('url', '')
378
- title = result.get('title', '')
379
- description = result.get('description', '')
380
-
381
- # URL 기반 점수
382
- trusted_domains = [
383
- '.edu', '.gov', '.org', 'wikipedia.org', 'harvard.edu',
384
- 'mckinsey.com', 'deloitte.com', 'pwc.com', 'gartner.com',
385
- 'forrester.com', 'statista.com', 'bloomberg.com', 'reuters.com'
386
- ]
387
-
388
- for domain in trusted_domains:
389
- if domain in url:
390
- score += 0.2
391
- break
392
-
393
- # HTTPS 사용 여부
394
- if url.startswith('https://'):
395
- score += 0.1
396
-
397
- # 제목과 설명의 길이 (너�� 짧으면 신뢰도 감소)
398
- if len(title) > 20:
399
- score += 0.05
400
- if len(description) > 50:
401
- score += 0.05
402
-
403
- # 광고/스팸 키워드 체크
404
- spam_keywords = ['buy now', 'sale', 'discount', 'click here', '100% free']
405
- if any(spam in (title + description).lower() for spam in spam_keywords):
406
- score -= 0.3
407
-
408
- # 날짜 정보가 있으면 가산점
409
- if any(year in description for year in ['2024', '2023', '2022']):
410
- score += 0.1
411
-
412
- return max(0, min(1, score)) # 0-1 범위로 제한
413
-
414
- def detect_contradictions(self, results: List[Dict]) -> List[Dict]:
415
- """검색 결과 간 모순 감지"""
416
- contradictions = []
417
-
418
- # 간단한 모순 감지 패턴
419
- opposite_pairs = [
420
- ("increase", "decrease"),
421
- ("improve", "worsen"),
422
- ("effective", "ineffective"),
423
- ("success", "failure"),
424
- ("benefit", "harm"),
425
- ("positive", "negative"),
426
- ("growth", "decline")
427
- ]
428
-
429
- # 결과들을 비교
430
- for i in range(len(results)):
431
- for j in range(i + 1, len(results)):
432
- desc1 = results[i].get('description', '').lower()
433
- desc2 = results[j].get('description', '').lower()
434
-
435
- # 반대 개념이 포함되어 있는지 확인
436
- for word1, word2 in opposite_pairs:
437
- if (word1 in desc1 and word2 in desc2) or (word2 in desc1 and word1 in desc2):
438
- # 같은 주제에 대해 반대 의견인지 확인
439
- common_words = set(desc1.split()) & set(desc2.split())
440
- if len(common_words) > 5: # 공통 단어가 5개 이상이면 같은 주제로 간주
441
- contradictions.append({
442
- 'source1': results[i]['url'],
443
- 'source2': results[j]['url'],
444
- 'type': f"{word1} vs {word2}",
445
- 'desc1': results[i]['description'][:100],
446
- 'desc2': results[j]['description'][:100]
447
- })
448
-
449
- return contradictions
450
-
451
- def brave_search(self, query: str) -> List[Dict]:
452
- """Brave Search API 호출"""
453
- if self.test_mode or self.bapi_token == "YOUR_BRAVE_API_TOKEN":
454
- # 테스트 모드에서는 시뮬레이션된 결과 반환
455
- test_results = []
456
- for i in range(5):
457
- test_results.append({
458
- "title": f"{query} - Industry Report {i+1}",
459
- "description": f"Comprehensive analysis of {query} including market trends, key statistics, and future projections for strategic planning.",
460
- "url": f"https://report{i+1}.com/{query.replace(' ', '-')}",
461
- "credibility_score": 0.7 + (i * 0.05)
462
- })
463
- return test_results
464
-
465
- try:
466
- params = {
467
- "q": query,
468
- "count": 20, # 20개로 증가
469
- "safesearch": "moderate",
470
- "freshness": "pw" # Past week for recent results
471
- }
472
-
473
- response = requests.get(
474
- self.brave_url,
475
- headers=self.create_brave_headers(),
476
- params=params,
477
- timeout=10
478
- )
479
-
480
- if response.status_code == 200:
481
- data = response.json()
482
- results = []
483
- for item in data.get("web", {}).get("results", [])[:20]:
484
- result = {
485
- "title": item.get("title", ""),
486
- "description": item.get("description", ""),
487
- "url": item.get("url", ""),
488
- "published": item.get("published", "")
489
- }
490
- # 신뢰도 점수 계산
491
- result["credibility_score"] = self.calculate_credibility_score(result)
492
- results.append(result)
493
-
494
- # 신뢰도 점수 기준으로 정렬
495
- results.sort(key=lambda x: x['credibility_score'], reverse=True)
496
- return results
497
- else:
498
- logger.error(f"Brave API 오류: {response.status_code}")
499
- return []
500
-
501
- except Exception as e:
502
- logger.error(f"Brave 검색 중 오류: {str(e)}")
503
- return []
504
-
505
- def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
506
- """테스트 모드에서 스트리밍 시뮬레이션"""
507
- words = text.split()
508
- for i in range(0, len(words), 3):
509
- chunk = " ".join(words[i:i+3])
510
- yield chunk + " "
511
- time.sleep(0.05)
512
-
513
- def call_llm_streaming(self, messages: List[Dict[str, str]], role: str) -> Generator[str, None, None]:
514
- """스트리밍 LLM API 호출"""
515
-
516
- # 테스트 모드
517
- if self.test_mode:
518
- logger.info(f"테스트 모드 스트리밍 - Role: {role}")
519
- test_responses = {
520
- "supervisor_initial": """전문적인 PPT 구조를 설계하겠습니다.
521
-
522
- ## 1. PPT 전체 구조 설계
523
-
524
- **목적**: 디지털 트랜스포메이션의 핵심 요소와 성공 전략을 명확하게 전달
525
- **타겟 청중**: 경영진 및 의사결정자
526
- **톤**: 전문적이고 설득력 있는 비즈니스 스타일
527
- **전체 슬라이드**: 12장
528
-
529
- ## 2. 섹션별 구성
530
-
531
- ### 도입부 (3장)
532
- - 슬라이드 1: 표지 - 임팩트 있는 제목과 비주얼
533
- - 슬라이드 2: 목차 - 전체 흐름 제시
534
- - 슬라이드 3: 디지털 트랜스포메이션의 필요성
535
-
536
- ### 본론 (7장)
537
- **섹션 1: 현황 분석 (2장)**
538
- - 슬라이드 4: 산업별 디지털화 현황
539
- - 슬라이드 5: 주요 도전과제
540
-
541
- **섹션 2: 핵심 전략 (3장)**
542
- - 슬라이드 6: 디지털 트랜스포메이션 프레임워크
543
- - 슬라이드 7: 기술 스택 및 인프라
544
- - 슬라이드 8: 변화 관리 전략
545
-
546
- **섹션 3: 성공 사례 (2장)**
547
- - 슬라이드 9: 글로벌 성공 사례
548
- - 슬라이드 10: ROI 및 성과 지표
549
-
550
- ### 결론부 (2장)
551
- - 슬라이드 11: 핵심 시사점 및 권고사항
552
- - 슬라이드 12: Q&A
553
-
554
- ## 3. 비주얼 전략
555
- - 색상: 기업 블루(#003366) + 악센트 오렌지(#FF6B35)
556
- - 아이콘: 모던하고 플랫한 디자인
557
- - 차트: 데이터 시각화 중심
558
-
559
- [검색 키워드]: digital transformation trends 2024, DX success cases, digital transformation ROI, change management digital, industry 4.0 statistics""",
560
-
561
- "researcher": """PPT 콘텐츠를 위한 핵심 정보를 정리했습니다.
562
-
563
- ## 1. 시각적 자료 가능한 통계/수치 (신뢰도 높음)
564
-
565
- **디지털 트랜스포메이션 투자 현황**
566
- - 2024년 글로벌 DX 투자 규모: $3.4조 (신뢰도: 0.92)
567
- - 연평균 성장률(CAGR): 16.5% (2023-2027)
568
- - 출처: IDC Digital Transformation Spending Guide
569
-
570
- **산업별 디지털화 수준**
571
- - 금융: 78% 디지털화 완료 (신뢰도: 0.88)
572
- - 제조: 52% 진행 중 (신뢰도: 0.85)
573
- - 헬스케어: 61% 가속화 단계
574
- - 출처: McKinsey Global Institute
575
-
576
- ## 2. 핵심 인사이트와 트렌드 (신뢰도 높음)
577
-
578
- **2024년 주요 트렌드**
579
- 1. AI/ML 통합: 87%의 기업이 AI를 DX 핵심 요소로 채택 (신뢰도: 0.90)
580
- 2. 클라우드 우선: 94%가 하이브리드 클라우드 전략 추진
581
- 3. 데이터 중심: 실시간 분석이 경쟁력의 핵심
582
- - 출처: Gartner Technology Trends 2024
583
-
584
- ## 3. 사례 연구 및 성공 스토리 (신뢰도 높음)
585
-
586
- **Amazon 사례**
587
- - 물류 자동화로 배송 시간 40% 단축 (신뢰도: 0.87)
588
- - AI 기반 추천으로 매출 35% 증가
589
- - 출처: Amazon Annual Report 2023
590
-
591
- **Starbucks 디지털 혁신**
592
- - 모바일 주문 비중: 전체 매출의 26% (신뢰도: 0.85)
593
- - 개인화 마케팅으로 고객 충성도 23% 향상
594
- - 출처: Starbucks Investor Relations
595
-
596
- ## 4. 전문가 의견 및 인용구
597
-
598
- > "디지털 트랜스포메이션은 기술이 아닌 사람과 문화의 변화다"
599
- - Satya Nadella, Microsoft CEO
600
-
601
- > "2025년까지 디지털 리더와 후발주자의 격차는 2배 이상 벌어질 것"
602
- - Michael Porter, Harvard Business School
603
-
604
- ## 5. 시각 자료 추천
605
-
606
- **슬라이드별 비주얼 제안**
607
- - 표지: 네트워크 연결 이미지 + 동적인 데이터 흐름
608
- - 통계: 인터랙티브한 대시보드 스타일 차트
609
- - 프레임워크: 계층적 다이어그램 + 아이콘
610
- - 사례: 기업 로고 + 핵심 지표 인포그래픽
611
- - 타임라인: 로드맵 형태의 단계별 진행도
612
-
613
- **아이콘 세트**
614
- - 기술: 클라우드, AI, 데이터, 보안
615
- - 비즈니스: 성장, 혁신, 협업, 효율성
616
- - 사람: 리더십, 팀워크, 스킬, 문화""",
617
-
618
- "supervisor_execution": """조사 내용을 바탕으로 PPT 제작을 위한 구체적인 지시사항입니다.
619
-
620
- ## 1. 슬라이드별 상세 구성
621
-
622
- **슬라이드 1: 표지**
623
- - 제목: "디지털 트랜스포메이션: 미래를 향한 도약"
624
- - 부제목: "성공적인 DX 전략과 실행 방안"
625
- - 비주얼: 연결된 디지털 네트워크 배경
626
- - 조사 자료 활용: $3.4조 시장 규모 강조
627
-
628
- **슬라이드 4: 산업별 현황**
629
- - 조사된 통계 활용: 금융(78%), 제조(52%), 헬스케어(61%)
630
- - 원형 차트 + 진행 바 조합
631
- - 각 산업별 대표 아이콘 배치
632
-
633
- **슬라이드 7: 기술 스택**
634
- - AI/ML(87% 채택률) 중심으로 구성
635
- - 클라우드(94%) 하이브리드 전략 시각화
636
- - 계층적 다이어그램 사용
637
-
638
- **슬라이드 9: 성공 사례**
639
- - Amazon과 Starbucks 사례 대비
640
- - 핵심 성과 지표 인포그래픽
641
- - Before/After 비교 형식
642
-
643
- ## 2. 디자인 가이드라인
644
-
645
- **색상 팔레트**
646
- - 주색상: 네이비 블루 (#1E3A8A)
647
- - 보조색상: 스카이 블루 (#3B82F6)
648
- - 강조색상: 오렌지 (#F59E0B)
649
- - 배경: 화이트/라이트 그레이
650
-
651
- **타이포그래피**
652
- - 제목: Montserrat Bold (32-40pt)
653
- - 본문: Open Sans Regular (18-24pt)
654
- - 강조: Open Sans Semi-Bold
655
-
656
- **레이아웃 원칙**
657
- - 여백 충분히 활용 (40% 이상)
658
- - 좌우 대칭 또는 황금비율
659
- - 한 슬라이드 한 메시지 원칙
660
-
661
- ## 3. 스토리텔링 전략
662
-
663
- **도입부 전략**
664
- - 충격적인 통계로 시작 ($3.4조 시장)
665
- - "왜 지금인가?" 질문 제기
666
- - 청중의 pain point 공감
667
-
668
- **전개 방식**
669
- - 문제 → 해결책 → 증거 → 행동
670
- - 각 섹션 간 자연스러운 전환
671
- - 사례를 통한 구체화
672
-
673
- **클라이맥스**
674
- - ROI 데이터로 설득력 극대화
675
- - 성공 기업과의 격차 시각화
676
- - 행동 촉구 메시지
677
-
678
- ## 4. 시각화 지침
679
-
680
- **데이터 시각화**
681
- - 산업별 현황: 방사형 차트
682
- - 성장률: 상승 곡선 그래프
683
- - ROI: 계산기 스타일 인포그래픽
684
- - 프로세스: 순환형 다이어그램
685
-
686
- **애니메이션 효과**
687
- - 슬라이드 전환: Morph 효과
688
- - 차트 등장: 순차적 빌드업
689
- - 강조 포인트: 줌인/하이라이트
690
- - 과도한 효과 지양""",
691
-
692
- "executor": """PPT 슬라이드를 작성합니다.
693
-
694
- [슬라이드 1] 표지
695
- 제목: 디지털 트랜스포메이션: 미래를 향한 도약
696
- 부제목: 성공적인 DX 전략과 실행 방안
697
- 핵심 내용:
698
- - 발표자: [이름]
699
- - 날짜: 2024년 3월
700
- - 기업명/로고
701
- 발표자 노트: 청중의 주목을 끌 수 있도록 자신감 있게 시작. $3.4조 규모의 거대한 변화의 물결임을 강조
702
- 시각 자료: 디지털 네트워크가 연결된 지구본 이미지, 동적인 데이터 흐름 효과
703
-
704
- [슬라이드 2] 목차
705
- 제목: Agenda
706
- 핵심 내용:
707
- - 디지털 트랜스포메이션의 현재
708
- - 산업별 디지털화 현황과 과제
709
- - 성공을 위한 핵심 전략
710
- - 글로벌 성공 사례와 교훈
711
- - 결론 및 다음 단계
712
- 발표자 노트: 전체 20분 발표 중 각 섹션별 시간 배분 설명
713
- 시각 자료: 번호가 매겨진 아이콘과 진행 표시 바
714
-
715
- [슬라이드 3] 도입
716
- 제목: 왜 지금 디지털 트랜스포메이션인가?
717
- 핵심 내용:
718
- - 2024년 글로벌 DX 투자: $3.4조
719
- - 디지털 리더 기업의 수익성: 평균 대비 2.5배
720
- - 팬데믹 이후 디지털 채택 가속화: 10년→2년
721
- - "변화하거나 도태되거나" - 생존의 문제
722
- 발표자 노트: 시장 규모와 긴급성을 강조하여 청중의 관심 유도
723
- 시각 자료: 급상승하는 그래프와 시계 아이콘으로 시급함 표현
724
-
725
- [슬라이드 4] 산업별 현황
726
- 제목: 산업별 디지털 트랜스포메이션 현황
727
- 핵심 내용:
728
- - 금융: 78% (선도 그룹)
729
- - 헬스케어: 61% (빠른 추격)
730
- - 제조: 52% (꾸준한 진행)
731
- - 소매: 45% (가속화 필요)
732
- 발표자 노트: 각 산업의 특성과 도전과제 간단히 언급
733
- 시각 자료: 도넛 차트와 산업별 아이콘, 진행률 바
734
-
735
- [슬라이드 5] 주요 도전과제
736
- 제목: 디지털 트랜스포메이션의 5대 장벽
737
- 핵심 내용:
738
- - 레거시 시스템의 복잡성 (67%)
739
- - 변화 저항과 문화적 관성 (54%)
740
- - 디지털 인재 부족 (48%)
741
- - 불명확한 ROI (41%)
742
- - 사이버 보안 우려 (38%)
743
- 발표자 노트: 각 장벽에 대한 극복 방안 미리 준비
744
- 시각 자료: 장벽을 나타내는 벽돌 아이콘과 백분율 표시
745
-
746
- [슬라이드 6] DX 프레임워크
747
- 제목: 디지털 트랜스포메이션 성공 프레임워크
748
- 핵심 내용:
749
- - 전략: 비즈니스 목표 정렬
750
- - 기술: AI/클라우드/데이터
751
- - 프로세스: 애자일 방법론
752
- - 문화: 혁신과 실험 장려
753
- - 거버넌스: 지속적 개선
754
- 발표자 노트: 각 요소가 상호 연결되어 있음을 강조
755
- 시각 자료: 5각형 다이어그램, 각 꼭지점에 아이콘 배치
756
-
757
- [슬라이드 7] 기술 스택
758
- 제목: 2024 핵심 기술 스택
759
- 핵심 내용:
760
- - AI/ML 채택률: 87%
761
- - 클라우드 전략: 94% 하이브리드
762
- - 데이터 분석: 실시간 처리
763
- - 보안: Zero Trust 아키텍처
764
- - 통합: API 우선 접근
765
- 발표자 노트: 각 기술의 비즈니스 가치 연결
766
- 시각 자료: 계층적 기술 스택 다이어그램
767
 
768
- [슬라이드 8] 변화 관리
769
- 제목: 사람 중심의 변화 관리
770
- 핵심 내용:
771
- - 리더십의 명확한 비전 공유
772
- - 단계별 교육 프로그램
773
- - 빠른 성과로 모멘텀 구축
774
- - 지속적 커뮤니케이션
775
- - 보상 체계 연계
776
- 발표자 노트: Satya Nadella 인용구 활용
777
- 시각 자료: 변화 곡선과 사람 아이콘
778
-
779
- [슬라이드 9] 성공 사례
780
- 제목: 글로벌 DX 성공 스토리
781
- 핵심 내용:
782
- - Amazon: 물류 자동화 → 배송 40% 단축
783
- - Starbucks: 모바일 주문 → 매출 26%
784
- - Netflix: AI 추천 → 시청 시간 80% 증가
785
- - Tesla: OTA 업데이트 → 서비스 비용 70% 절감
786
- 발표자 노트: 각 사례의 핵심 성공 요인 강조
787
- 시각 자료: 기업 로고와 핵심 지�� 인포그래픽
788
-
789
- [슬라이드 10] ROI 분석
790
- 제목: 디지털 트랜스포메이션의 명확한 ROI
791
- 핵심 내용:
792
- - 운영 효율성: 30-40% 개선
793
- - 고객 만족도: NPS 25% 상승
794
- - 신규 수익원: 전체 매출의 15%
795
- - 투자 회수 기간: 평균 2.5년
796
- 발표자 노트: 구체적인 수치로 투자 정당성 입증
797
- 시각 자료: ROI 계산기 스타일의 인포그래픽
798
-
799
- [슬라이드 11] 핵심 시사점
800
- 제목: 성공적인 DX를 위한 5가지 제언
801
- 핵심 내용:
802
- - 작게 시작하되 크게 생각하라
803
- - 기술보다 문화 변화에 투자하라
804
- - 데이터 기반 의사결정을 일상화하라
805
- - 실패를 두려워하지 말고 빠르게 배워라
806
- - 고객 중심으로 모든 것을 재설계하라
807
- 발표자 노트: 각 제언에 대한 실천 방안 준비
808
- 시각 자료: 체크리스트 스타일의 아이콘
809
-
810
- [슬라이드 12] Q&A
811
- 제목: Questions & Discussion
812
- 핵심 내용:
813
- - 감사합니다
814
- - 질문과 토론 환영
815
- - 연락처: [이메일/전화]
816
- - 추가 자료: [웹사이트/QR코드]
817
- 발표자 노트: 예상 질문 3-5개 미리 준비
818
- 시각 자료: 물음표 아이콘과 연락처 정보""",
819
-
820
- "supervisor_review": """실행자 AI의 PPT 초안을 검토했습니다. 전반적으로 잘 구성되었으나 다음 개선사항을 제안합니다.
821
-
822
- ## 강점
823
- - 조사된 통계와 수치가 효과적으로 활용됨
824
- - 스토리라인이 논리적이고 설득력 있음
825
- - 각 슬라이드의 핵심 메시지가 명확함
826
- - 시각 자료 제안이 구체적임
827
-
828
- ## 개선 필요사항
829
-
830
- ### 1. 임팩트 강화
831
- **슬라이드 3 개선**
832
- - 현재: 통계 나열
833
- - 개선: "귀사는 디지털 리더입니까, 후발주자입니까?"라는 도발적 질문 추가
834
- - 청중 참여를 위한 인터랙티브 요소 고려
835
-
836
- **슬라이드 5 개선**
837
- - 현재: 장벽만 제시
838
- - 개선: 각 장벽별 "Quick Win" 솔루션 한 줄 추가
839
- - 희망적 메시지로 균형 맞추기
840
-
841
- ### 2. 스토리텔링 강화
842
- **전환 문구 추가**
843
- - 슬라이드 간 연결 문구 필요
844
- - 예: "이제 이러한 현황을 어떻게 개선할 수 있을까요?"
845
-
846
- **감정적 연결**
847
- - 숫자와 통계 외에 인간적 스토리 추가
848
- - 변화를 겪은 직원/고객의 실제 경험담
849
-
850
- ### 3. 비주얼 일관성
851
- **색상 사용 정제**
852
- - 강조색(오렌지) 과도 사용 자제
853
- - 각 섹션별 색상 톤 구분
854
- - 데이터 시각화 색상 팔레트 통일
855
-
856
- **아이콘 스타일**
857
- - 모든 아이콘 동일 스타일로 통일
858
- - 라인 두께, 모서리 처리 일관성
859
-
860
- ### 4. 발표 준비 강화
861
- **시간 배분 명시**
862
- - 각 슬라이드별 예상 소요 시간 추가
863
- - 전체 20분 기준 상세 배분
864
-
865
- **전환 스크립트**
866
- - 슬라이드 간 자연스러운 전환 멘트 준비
867
- - 청중 반응 체크 포인트 설정
868
-
869
- ### 5. 추가 권장사항
870
- **백업 슬라이드 준비**
871
- - 상세 기술 스펙
872
- - 추가 사례 연구
873
- - 업종별 맞춤 전략
874
- - 상세 ROI 계산 방법
875
-
876
- **인쇄 자료**
877
- - 3슬라이드/페이지 형식
878
- - 메모 공간 포함
879
- - 핵심 수치 요약 페이지
880
-
881
- 이러한 개선사항을 반영하면 더욱 완성도 높고 설득력 있는 PPT가 될 것입니다.""",
882
-
883
- "executor_final": """감독자 AI의 피드백을 완전히 반영한 최종 PPT를 작성합니다.
884
-
885
- # 🎯 디지털 트랜스포메이션: 미래를 향한 도약
886
- ## 최종 완성 PPT (발표 시간: 20분)
887
-
888
- ---
889
-
890
- [슬라이드 1] 표지 (30초)
891
- **제목**: 디지털 트랜스포메이션: 미래를 향한 도약
892
- **부제목**: 성공적인 DX 전략과 실행 방안
893
- **비주얼**:
894
- - 배경: 연결된 디지털 네트워크와 데이터 흐름
895
- - 중앙: 회사 로고
896
- - 하단: 발표자 정보 및 날짜
897
- **발표자 노트**:
898
- "안녕하십니까. 오늘 여러분과 함께 $3.4조 규모의 디지털 혁명, 그 중심에서 우리가 어떻게 성공할 수 있을지 논의하고자 합니다."
899
-
900
- ---
901
-
902
- [슬라이드 2] 목차 (30초)
903
- **제목**: 오늘의 여정
904
- **핵심 내용**:
905
- 1. 📊 디지털 트랜스포메이션의 현재 (3분)
906
- 2. 🏭 산업별 현황과 도전과제 (3분)
907
- 3. 🎯 성공 전략과 프레임워크 (5분)
908
- 4. 🌟 글로벌 성공 사례 (4분)
909
- 5. 💡 핵심 시사점과 실행 방안 (4분)
910
- 6. 🤝 Q&A (1분)
911
- **발표자 노트**:
912
- "20분간의 여정을 통해 디지털 트랜스포메이션의 현재와 미래, 그리고 실행 방안을 함께 탐색하겠습니다."
913
- **시각 자료**: 진행 단계를 보여주는 로드맵 스타일 디자인
914
-
915
- ---
916
-
917
- [슬라이드 3] 도전적 질문 (1분 30초)
918
- **제목**: 귀사는 디지털 리더입니까, 후발주자입니까?
919
- **핵심 내용**:
920
- - 🌍 2024년 글로벌 DX 시장: **$3.4조**
921
- - 📈 디지털 리더의 수익성: 평균 대비 **2.5배**
922
- - ⏰ 변화의 속도: 10년 → **2년** (팬데믹 가속화)
923
- - ⚡ 핵심 질문: "우리는 변화를 주도하고 있는가?"
924
- **발표자 노트**:
925
- "잠시 생각해보십시오. 귀사는 이 거��한 변화의 물결을 타고 있습니까, 아니면 뒤처져 있습니까?"
926
- **시각 자료**:
927
- - 상단: 도발적 질문 크게 표시
928
- - 중앙: 리더 vs 후발주자 대비 인포그래픽
929
- - 전환: 청중 참여를 위한 3초 pause
930
-
931
- ---
932
-
933
- [슬라이드 4] 산업별 디지털화 현황 (1분 30초)
934
- **제목**: 당신의 산업은 어디에 위치합니까?
935
- **핵심 내용**:
936
- - 🏦 금융: 78% - "디지털이 새로운 표준"
937
- - 🏥 헬스케어: 61% - "원격의료가 일상으로"
938
- - 🏭 제조: 52% - "스마트 팩토리로 진화"
939
- - 🛍️ 소매: 45% - "옴니채널이 생존 조건"
940
- **발표자 노트**:
941
- "금융업은 이미 78%가 디지털화되었습니다. 귀사의 산업은 어떻습니까? 이제 각 산업의 성공 비결을 살펴보겠습니다."
942
- **시각 자료**:
943
- - 레이스 트랙 형태의 진행도 표시
944
- - 각 산업별 대표 아이콘과 진행률
945
- - 업종별 대표 기업 로고 작게 표시
946
-
947
- ---
948
-
949
- [슬라이드 5] 5대 장벽과 해결책 (2분)
950
- **제목**: 장벽을 기회로: 5대 도전과제와 Quick Win
951
- **핵심 내용**:
952
- - 🔧 레거시 시스템 (67%) → "단계적 현대화 전략"
953
- - 🚫 변화 저항 (54%) → "작은 성공 스토리 확산"
954
- - 👥 인재 부족 (48%) → "업스킬링 + 외부 파트너십"
955
- - 💰 불명확한 ROI (41%) → "파일럿 프로젝트로 증명"
956
- - 🔒 보안 우려 (38%) → "Security by Design 원칙"
957
- **발표자 노트**:
958
- "모든 장벽에는 해결책이 있습니다. 중요한 것은 어디서부터 시작할 것인가입니다."
959
- **전환 멘트**: "그렇다면 이러한 장벽을 넘어 성공하기 위한 검증된 프레임워크를 소개하겠습니다."
960
- **시각 자료**:
961
- - 장벽→해결책 화살표 애니메이션
962
- - 각 항목별 순차적 등장 효과
963
-
964
- ---
965
-
966
- [슬라이드 6] DX 성공 프레임워크 (2분)
967
- **제목**: 검증된 디지털 트랜스포메이션 프레임워크
968
- **핵심 내용**:
969
- 🎯 **전략**: 비즈니스 목표와 100% 정렬
970
- 🔧 **기술**: AI(87%) + 클라우드(94%) + 데이터
971
- 👥 **문화**: 혁신 DNA 주입
972
- ⚙️ **프로세스**: 애자일 + DevOps
973
- 📊 **거버넌스**: 측정 가능한 KPI
974
- **발표자 노트**:
975
- "이 5가지 요소는 톱니바퀴처럼 맞물려 있습니다. 하나라도 빠지면 전체가 작동하지 않습니다."
976
- **시각 자료**:
977
- - 중앙: 5각형 다이어그램
978
- - 각 요소 간 연결선으로 상호의존성 표현
979
- - 호버 효과로 각 요소 상세 설명
980
-
981
- ---
982
-
983
- [슬라이드 7] 2024 기술 스택 (1분 30초)
984
- **제목**: 미래를 만드는 기술 스택
985
- **핵심 내용**:
986
- **Layer 1 - 인프라**
987
- - ☁️ 하이브리드 클라우드 (94% 채택)
988
- **Layer 2 - 데이터**
989
- - 📊 실시간 분석 플랫폼
990
- **Layer 3 - 인텔리전스**
991
- - 🤖 AI/ML (87% 통합)
992
- **Layer 4 - 경험**
993
- - 📱 옴니채널 인터페이스
994
- **Layer 5 - 보안**
995
- - 🔐 Zero Trust 아키텍처
996
- **발표자 노트**:
997
- "각 레이어는 그 위의 레이어를 지원합니다. 탄탄한 기초 없이는 혁신도 없습니다."
998
- **시각 자료**:
999
- - 피라미드 형태의 기술 스택
1000
- - 각 레이어별 대표 기술 로고
1001
-
1002
- ---
1003
-
1004
- [슬라이드 8] 인간 중심 변화 관리 (1분 30초)
1005
- **제목**: 기술이 아닌 사람이 만드는 변화
1006
- **핵심 내용**:
1007
- > "디지털 트랜스포메이션은 기술이 아닌 사람과 문화의 변화다"
1008
- > - Satya Nadella, Microsoft CEO
1009
-
1010
- - 🎯 **비전**: 명확하고 고무적인 미래상
1011
- - 📚 **교육**: 지속적 학습 문화
1012
- - 🏆 **성과**: 작은 승리의 축적
1013
- - 💬 **소통**: 양방향 피드백 루프
1014
- - 🎁 **보상**: 혁신에 대한 인센티브
1015
- **발표자 노트**:
1016
- "가장 앞선 기술도 사람이 받아들이지 않으면 무용지물입니다."
1017
- **전환 멘트**: "이제 이러한 원칙들이 실제로 어떤 성과를 만들어냈는지 보시겠습니다."
1018
- **시각 자료**: 사람 중심의 원형 다이어그램
1019
-
1020
- ---
1021
-
1022
- [슬라이드 9] 글로벌 성공 스토리 (2분)
1023
- **제목**: 숫자로 증명된 DX 성공 사례
1024
- **핵심 내용**:
1025
- 📦 **Amazon**: 물류 자동화 → 배송 시간 **40% 단축**
1026
- ☕ **Starbucks**: 모바일 주문 → 매출의 **26% 차지**
1027
- 🎬 **Netflix**: AI 추천 → 시청 시간 **80% 증가**
1028
- 🚗 **Tesla**: OTA 업데이트 → 서비스 비용 **70% 절감**
1029
-
1030
- **공통 성공 요인**:
1031
- - 고객 경험 최우선
1032
- - 데이터 기반 의사결정
1033
- - 지속적 혁신 문화
1034
- **발표자 노트**:
1035
- "이들의 공통점은 무엇일까요? 기술을 목적이 아닌 수단으로 활용했다는 점입니다."
1036
- **시각 자료**:
1037
- - 각 기업별 Before/After 대비
1038
- - 핵심 지표 크게 강조
1039
-
1040
- ---
1041
-
1042
- [슬라이드 10] ROI의 진실 (2분)
1043
- **제목**: 투자 대비 수익, 숨겨진 가치까지
1044
- **정량적 효과**:
1045
- - 💰 운영 효율성: **30-40% 개선**
1046
- - 😊 고객 만족도: NPS **25포인트 상승**
1047
- - 🚀 신규 수익: 전체 매출의 **15% 창출**
1048
- - ⏱️ 투자 회수: 평균 **2.5년**
1049
-
1050
- **정성적 효과**:
1051
- - 직원 만족도 향상
1052
- - 브랜드 이미지 제고
1053
- - 미래 대응력 확보
1054
- **발표자 노트**:
1055
- "ROI는 단순히 숫자로만 측정되지 않습니다. 조직의 미래 경쟁력이 진정한 가치입니다."
1056
- **시각 자료**:
1057
- - ROI 계산기 인터페이스
1058
- - 정량/정성 효과 균형 표시
1059
-
1060
- ---
1061
-
1062
- [슬라이드 11] 실행을 위한 5가지 제언 (2분)
1063
- **제목**: 내일부터 시작할 수 있는 5가지 행동
1064
- **핵심 제언**:
1065
- 1. 🌱 **Start Small, Think Big**
1066
- - "파일럿 프로젝트로 시작하되 전사 확산 계획 수립"
1067
- 2. 👥 **Culture First, Technology Second**
1068
- - "최고의 기술도 문화가 뒷받침되지 않으면 실패"
1069
- 3. 📊 **Data-Driven Everything**
1070
- - "추측이 아닌 데이터로 의사결정"
1071
- 4. 🔄 **Fail Fast, Learn Faster**
1072
- - "실패를 두려워하지 말고 빠르게 피벗"
1073
- 5. 🎯 **Customer at the Center**
1074
- - "모든 변화의 중심에 고객 가치"
1075
- **발표자 노트**:
1076
- "이 중 단 하나만이라도 내일부터 실천한다면, 1년 후 조직은 완전히 달라져 있을 것입니다."
1077
- **시각 자료**: 체크박스 스타일, 하나씩 체크되는 애니메이션
1078
-
1079
- ---
1080
-
1081
- [슬라이드 12] 마무리 및 Q&A (1분)
1082
- **제목**: 함께 만드는 디지털 미래
1083
- **핵심 내용**:
1084
- 🙏 **감사합니다**
1085
-
1086
- 💭 **생각해볼 질문들**:
1087
- - 우리 조직의 디지털 성숙도는?
1088
- - 가장 먼저 해결해야 할 과제는?
1089
- - 첫 번째 파일럿 프로젝트는?
1090
-
1091
- 📧 연락처: [이메일]
1092
- 📱 LinkedIn: [프로필]
1093
- 🔗 추가 자료: [QR 코드]
1094
- **발표자 노트**:
1095
- "디지털 트랜스포메이션은 목적지가 아닌 여정입니다. 그 여정을 함께하게 되어 영광입니다."
1096
- **시각 자료**: QR 코드와 연락처 정보
1097
-
1098
- ---
1099
-
1100
- ## 📎 백업 슬라이드 (필요시 활용)
1101
-
1102
- [백업 1] 산업별 상세 전략
1103
- [백업 2] 기술 스택 상세 스펙
1104
- [백업 3] 추가 성공 사례 (국내 기업)
1105
- [백업 4] ROI 계산 방법론
1106
- [백업 5] 단계별 실행 로드맵
1107
-
1108
- ## 🎤 예상 Q&A 및 답변 준비
1109
-
1110
- **Q1: 우리 같은 중견기업도 가능한가요?**
1111
- A: 오히려 중견기업이 더 빠르게 변화할 수 있습니다. 작은 파일럿부터 시작하세요.
1112
-
1113
- **Q2: 예산이 제한적인데 어떻게 시작하나요?**
1114
- A: 클라우드 기반 SaaS를 활용하면 초기 투자를 최소화할 수 있습니다.
1115
-
1116
- **Q3: 직원들의 저항이 심한데 어떻게 극복하나요?**
1117
- A: 변화의 수혜자를 먼저 만들고, 그들이 전도사가 되게 하세요.
1118
-
1119
- ---
1120
- *이 PPT는 20분 발표를 기준으로 작성되었으며, 청중과의 상호작용을 극대화하도록 설계되었습니다.*"""
1121
- }
1122
-
1123
- # 프롬프트 내용에 따라 적절한 응답 선택
1124
- if role == "supervisor" and "조사자 AI가 정리한" in messages[0]["content"]:
1125
- response = test_responses["supervisor_execution"]
1126
- elif role == "supervisor" and messages[0]["content"].find("실행자 AI의 PPT 초안") > -1:
1127
- response = test_responses["supervisor_review"]
1128
- elif role == "supervisor":
1129
- response = test_responses["supervisor_initial"]
1130
- elif role == "researcher":
1131
- response = test_responses["researcher"]
1132
- elif role == "executor" and "최종 PPT" in messages[0]["content"]:
1133
- response = test_responses["executor_final"]
1134
- else:
1135
- response = test_responses["executor"]
1136
-
1137
- yield from self.simulate_streaming(response, role)
1138
- return
1139
-
1140
- # 실제 API 호출
1141
- try:
1142
- system_prompts = {
1143
- "supervisor": "당신은 전문적인 PPT 구조를 설계하고 지도하는 감독자 AI입니다.",
1144
- "researcher": "당신은 PPT 콘텐츠를 위한 정보를 조사하고 정리하는 조사자 AI입니다.",
1145
- "executor": "당신은 실제 PPT 슬라이드 내용을 작성하는 실행자 AI입니다."
1146
- }
1147
-
1148
- full_messages = [
1149
- {"role": "system", "content": system_prompts.get(role, "")},
1150
- *messages
1151
- ]
1152
-
1153
- payload = {
1154
- "model": self.model_id,
1155
- "messages": full_messages,
1156
- "max_tokens": 4096,
1157
- "temperature": 0.7,
1158
- "top_p": 0.8,
1159
- "stream": True,
1160
- "stream_options": {"include_usage": True}
1161
- }
1162
-
1163
- logger.info(f"API 스트리밍 호출 시작 - Role: {role}")
1164
-
1165
- response = requests.post(
1166
- self.api_url,
1167
- headers=self.create_headers(),
1168
- json=payload,
1169
- stream=True,
1170
- timeout=10
1171
- )
1172
-
1173
- if response.status_code != 200:
1174
- logger.error(f"API 오류: {response.status_code}")
1175
- yield f"❌ API 오류 ({response.status_code}): {response.text[:200]}"
1176
- return
1177
-
1178
- for line in response.iter_lines():
1179
- if line:
1180
- line = line.decode('utf-8')
1181
- if line.startswith("data: "):
1182
- data = line[6:]
1183
- if data == "[DONE]":
1184
- break
1185
- try:
1186
- chunk = json.loads(data)
1187
- if "choices" in chunk and chunk["choices"]:
1188
- content = chunk["choices"][0].get("delta", {}).get("content", "")
1189
- if content:
1190
- yield content
1191
- except json.JSONDecodeError:
1192
- continue
1193
-
1194
- except requests.exceptions.Timeout:
1195
- yield "⏱️ API 호출 시간이 초과되었습니다. 다시 시도해주세요."
1196
- except requests.exceptions.ConnectionError:
1197
- yield "🔌 API 서버에 연결할 수 없습니다. 인터넷 연결을 확인해주세요."
1198
- except Exception as e:
1199
- logger.error(f"스트리밍 중 오류: {str(e)}")
1200
- yield f"❌ 오류 발생: {str(e)}"
1201
-
1202
- # 시스템 인스턴스 생성
1203
- ppt_system = PPTCreationSystem()
1204
-
1205
- # ===== 메인 처리 함수 =====
1206
- def process_ppt_streaming(ppt_topic: str, template_name: str, audience_type: str, language: str,
1207
- custom_slides: List[Dict], slide_count: int, seed: int, uploaded_file,
1208
- use_web_search: bool, theme_name: str = "Minimal Light",
1209
- progress=gr.Progress()):
1210
- """통합된 PPT 생성 처리 - 3자 협의 시스템 사용"""
1211
- # Update global variables
1212
- global current_topic, uploaded_content, current_language
1213
- current_topic = ppt_topic
1214
- current_language = language
1215
-
1216
- # Read uploaded file content
1217
- uploaded_content = ""
1218
- if uploaded_file is not None:
1219
- try:
1220
- uploaded_content = read_uploaded_file(uploaded_file.name)
1221
- print(f"[File Upload] Content length: {len(uploaded_content)} characters")
1222
- except Exception as e:
1223
- print(f"[File Upload] Error: {str(e)}")
1224
- uploaded_content = ""
1225
-
1226
- # Template selection and slide configuration
1227
- try:
1228
- if template_name == "Custom" and custom_slides:
1229
- slides = [{"title": "Cover", "style": "Title Slide (Hero)", "prompt_hint": "Presentation cover"}]
1230
- slides.extend(custom_slides)
1231
- slides.append({"title": "Thank You", "style": "Thank You Slide", "prompt_hint": "Presentation closing and key message"})
1232
- else:
1233
- template = PPT_TEMPLATES.get(template_name)
1234
- if not template:
1235
- yield "", "", "", None, f"❌ Template '{template_name}' not found."
1236
- return
1237
- slides = generate_dynamic_slides(ppt_topic, template, slide_count)
1238
- except Exception as e:
1239
- print(f"[Slide Configuration] Error: {str(e)}")
1240
- yield "", "", "", None, f"❌ Error configuring slides: {str(e)}"
1241
- return
1242
-
1243
- if not slides:
1244
- yield "", "", "", None, "No slides defined."
1245
- return
1246
-
1247
- total_slides = len(slides)
1248
- print(f"\n[PPT Generation] Starting - Total {total_slides} slides (Cover + {slide_count} content + Thank You)")
1249
- print(f"[PPT Generation] Topic: {ppt_topic}")
1250
- print(f"[PPT Generation] Template: {template_name}")
1251
- print(f"[PPT Generation] Audience: {audience_type}")
1252
- print(f"[PPT Generation] Language: {language}")
1253
- print(f"[PPT Generation] Design Theme: {theme_name}")
1254
- print(f"[PPT Generation] Web Search: {'Enabled' if use_web_search else 'Disabled'}")
1255
-
1256
- conversation_log = []
1257
- all_responses = {"supervisor": [], "researcher": [], "executor": []}
1258
-
1259
  try:
1260
- # 1단계: 감독자 AI 초기 분석 및 키워드 추출
1261
- progress(0.1, "🧠 감독자 AI가 PPT 구조를 설계 중...")
1262
- supervisor_prompt = ppt_system.create_supervisor_initial_prompt(ppt_topic)
1263
- supervisor_initial_response = ""
1264
-
1265
- supervisor_text = "[PPT 구조 설계] 🔄 생성 중...\n"
1266
- for chunk in ppt_system.call_llm_streaming(
1267
- [{"role": "user", "content": supervisor_prompt}],
1268
- "supervisor"
1269
- ):
1270
- supervisor_initial_response += chunk
1271
- supervisor_text = f"[PPT 구조 설계] - {datetime.now().strftime('%H:%M:%S')}\n{supervisor_initial_response}"
1272
- yield supervisor_text, "", "", None, "🔄 감독자 AI가 PPT 구조를 설계 중..."
1273
-
1274
- all_responses["supervisor"].append(supervisor_initial_response)
1275
-
1276
- # 2단계: 웹 검색 수행 (선택된 경우)
1277
- search_results = {}
1278
- if use_web_search and BRAVE_API_TOKEN:
1279
- progress(0.2, "🔍 PPT 콘텐츠 검색 중...")
1280
- keywords = ppt_system.extract_keywords(supervisor_initial_response)
1281
- logger.info(f"추출된 키워드: {keywords}")
1282
-
1283
- researcher_text = "[콘텐츠 검색] 🔍 검색 중...\n"
1284
- yield supervisor_text, researcher_text, "", None, "🔍 PPT 콘텐츠 검색 중..."
1285
-
1286
- total_search_count = 0
1287
-
1288
- # 원래 키워드로 검색
1289
- for keyword in keywords:
1290
- try:
1291
- results = ppt_system.brave_search(keyword)
1292
- if results:
1293
- search_results[keyword] = results
1294
- total_search_count += len(results)
1295
- researcher_text += f"✓ '{keyword}' 검색 완료 ({len(results)}개 결과)\n"
1296
- yield supervisor_text, researcher_text, "", None, f"🔍 '{keyword}' 검색 중..."
1297
- except Exception as e:
1298
- print(f"[Search] Error searching for '{keyword}': {str(e)}")
1299
-
1300
- researcher_text += f"\n📊 총 {total_search_count}개의 검색 결과 수집 완료\n"
1301
-
1302
- # 3단계: 조사자 AI가 검색 결과 정리
1303
- progress(0.3, "📝 조사자 AI가 콘텐츠 정리 중...")
1304
- researcher_prompt = ppt_system.create_researcher_prompt(ppt_topic, supervisor_initial_response, search_results)
1305
- researcher_response = ""
1306
-
1307
- researcher_text = "[PPT 콘텐츠 정리] 🔄 생성 중...\n"
1308
- for chunk in ppt_system.call_llm_streaming(
1309
- [{"role": "user", "content": researcher_prompt}],
1310
- "researcher"
1311
- ):
1312
- researcher_response += chunk
1313
- researcher_text = f"[PPT 콘텐츠 정리] - {datetime.now().strftime('%H:%M:%S')}\n{researcher_response}"
1314
- yield supervisor_text, researcher_text, "", None, "📝 조사자 AI가 콘텐츠 정리 중..."
1315
-
1316
- all_responses["researcher"].append(researcher_response)
1317
-
1318
- # 4단계: 감독자 AI가 조사 내용 기반으로 실행 지시
1319
- progress(0.4, "🎯 감독자 AI가 제작 지시 중...")
1320
- supervisor_execution_prompt = ppt_system.create_supervisor_execution_prompt(ppt_topic, researcher_response)
1321
- supervisor_execution_response = ""
1322
-
1323
- supervisor_text += "\n\n---\n\n[PPT 제작 지시] 🔄 생성 중...\n"
1324
- for chunk in ppt_system.call_llm_streaming(
1325
- [{"role": "user", "content": supervisor_execution_prompt}],
1326
- "supervisor"
1327
- ):
1328
- supervisor_execution_response += chunk
1329
- temp_text = f"{all_responses['supervisor'][0]}\n\n---\n\n[PPT 제작 지시] - {datetime.now().strftime('%H:%M:%S')}\n{supervisor_execution_response}"
1330
- supervisor_text = f"[PPT 구조 설계] - {datetime.now().strftime('%H:%M:%S')}\n{temp_text}"
1331
- yield supervisor_text, researcher_text, "", None, "🎯 감독자 AI가 제작 지시 중..."
1332
-
1333
- all_responses["supervisor"].append(supervisor_execution_response)
1334
-
1335
- # 5단계: 실행자 AI가 조사 내용과 지시를 기반으로 초기 PPT 작성
1336
- progress(0.5, "📊 실행자 AI가 PPT 작성 중...")
1337
- executor_prompt = ppt_system.create_executor_prompt(ppt_topic, supervisor_execution_response, researcher_response)
1338
- executor_response = ""
1339
-
1340
- executor_text = "[초기 PPT 작성] 🔄 생성 중...\n"
1341
- for chunk in ppt_system.call_llm_streaming(
1342
- [{"role": "user", "content": executor_prompt}],
1343
- "executor"
1344
- ):
1345
- executor_response += chunk
1346
- executor_text = f"[초기 PPT 작성] - {datetime.now().strftime('%H:%M:%S')}\n{executor_response}"
1347
- yield supervisor_text, researcher_text, executor_text, None, "📊 실행자 AI가 PPT 작성 중..."
1348
-
1349
- all_responses["executor"].append(executor_response)
1350
-
1351
- # 5.5단계: 실행자 AI 응답 파싱 (개선된 파서 사용)
1352
- progress(0.55, "📝 실행자 AI 응답을 슬라이드로 변환 중...")
1353
-
1354
- # 개선된 파서 사용
1355
- parsed_slides = parse_executor_response(executor_response, slides, language)
1356
- logger.info(f"파싱된 슬라이드 수: {len(parsed_slides)}")
1357
-
1358
- # 파싱 성공 여부 확인
1359
- parsing_success = len(parsed_slides) > 0
1360
-
1361
- # 파싱 결과 로그
1362
- for slide_idx, slide_data in parsed_slides.items():
1363
- logger.info(f"[파싱 결과] 슬라이드 {slide_idx + 1}:")
1364
- logger.info(f" - 제목: {slide_data.get('title', 'N/A')}")
1365
- logger.info(f" - 부제목: {slide_data.get('subtitle', 'N/A')}")
1366
- logger.info(f" - 불릿 포인트 수: {len(slide_data.get('bullet_points', []))}")
1367
- if slide_data.get('bullet_points'):
1368
- logger.info(f" - 첫 번째 불릿: {slide_data['bullet_points'][0]}")
1369
-
1370
- # 파싱 실패 시 경고 및 대체 방안
1371
- if not parsing_success:
1372
- logger.warning("[파싱] 실행자 응답 파싱 실패 - 실행자 응답 원문을 직접 사용 시도")
1373
-
1374
- # 실행자 응답을 직접 보여주기 (디버깅용)
1375
- yield supervisor_text, researcher_text, executor_text + "\n\n⚠️ 파싱 실패 - 대체 방법 사용 중...", None, "⚠️ 슬라이드 파싱 중 문제 발견, 대체 방법 사용..."
1376
 
1377
- # 6단계: 실제 슬라이드 생성 (개선된 로직)
1378
- progress(0.6, "🎨 슬라이드 이미지 생성 중...")
1379
- results = []
1380
- preview_html = """
1381
- <style>
1382
- .slides-container {
1383
- width: 100%;
1384
- max-width: 1400px;
1385
- margin: 0 auto;
1386
- }
1387
- </style>
1388
- <div class="slides-container">
1389
- """
1390
-
1391
- # Process each slide with improved logic
1392
- for i, slide in enumerate(slides):
1393
- try:
1394
- progress((0.6 + (0.3 * i / total_slides)), f"슬라이드 {i+1}/{total_slides} 처리 중...")
1395
-
1396
- # Translate slide title if Korean
1397
- display_title = slide.get('title', '')
1398
- if language == "Korean" and slide.get('title', '') in SLIDE_TITLE_TRANSLATIONS:
1399
- display_title = SLIDE_TITLE_TRANSLATIONS[slide.get('title', '')]
1400
-
1401
- slide_info = f"Slide {i+1}: {display_title}"
1402
- slide_context = f"{slide.get('title', '')} - {slide.get('prompt_hint', '')}"
1403
-
1404
- # 파싱된 데이터 확인 및 사용
1405
- content = None
1406
- speaker_notes = ""
1407
-
1408
- if i in parsed_slides and parsing_success:
1409
- # 파싱된 데이터 사용
1410
- parsed_data = parsed_slides[i]
1411
- logger.info(f"[슬라이드 {i+1}] 실행자 AI의 파싱된 콘텐츠 사용")
1412
-
1413
- # 불릿 포인트 검증
1414
- valid_bullets = []
1415
- for bp in parsed_data.get("bullet_points", []):
1416
- # 의미없는 placeholder 제거
1417
- if bp and not any(skip in bp for skip in ["Point 1", "Point 2", "Point 3", "Point 4", "Point 5", "📌 Point"]):
1418
- valid_bullets.append(bp)
1419
-
1420
- # 유효한 불릿 포인트가 있으면 사용
1421
- if len(valid_bullets) >= 3: # 최소 3개 이상일 때만 사용
1422
- content = {
1423
- "subtitle": parsed_data.get("subtitle") or display_title,
1424
- "bullet_points": valid_bullets[:5] # 최대 5개
1425
- }
1426
- speaker_notes = parsed_data.get("speaker_notes", "")
1427
- logger.info(f"[슬라이드 {i+1}] 파싱된 콘텐츠 사용 완료 - 불릿 {len(valid_bullets)}개")
1428
- else:
1429
- logger.warning(f"[슬라이드 {i+1}] 불릿 포인트 부족 ({len(valid_bullets)}개) - 실행자 응답에서 재추출 시도")
1430
-
1431
- # 실행자 응답에서 해당 슬라이드 관련 내용 재추출
1432
- slide_keywords = [display_title, slide.get('title', ''), slide.get('prompt_hint', '')]
1433
- extracted_content = extract_relevant_content_from_executor(
1434
- executor_response, slide_keywords, i+1, slide.get('title', '')
1435
- )
1436
-
1437
- if extracted_content and len(extracted_content.get("bullet_points", [])) >= 3:
1438
- content = extracted_content
1439
- speaker_notes = extracted_content.get("speaker_notes", "")
1440
- logger.info(f"[슬라이드 {i+1}] 실행자 응답에서 재추출 성공")
1441
-
1442
- # 파싱 실패하거나 콘텐츠가 없으면 실행자 응답에서 직접 추출
1443
- if not content:
1444
- logger.warning(f"[슬라이드 {i+1}] 파싱 실패 - 실행자 응답에서 직접 추출 시도")
1445
-
1446
- # 슬라이드 번호로 실행자 응답에서 해당 부분 찾기
1447
- slide_section = extract_slide_section_from_executor(executor_response, i+1)
1448
-
1449
- if slide_section:
1450
- content = parse_slide_section(slide_section, display_title)
1451
- speaker_notes = extract_speaker_notes_from_section(slide_section)
1452
- logger.info(f"[슬라이드 {i+1}] 실행자 응답에서 직접 추출 성공")
1453
-
1454
- # 그래도 콘텐츠가 없으면 최종 폴백
1455
- if not content or len(content.get("bullet_points", [])) < 3:
1456
- logger.warning(f"[슬라이드 {i+1}] 모든 방법 실패 - generate_slide_content 폴백 사용")
1457
-
1458
- # Thank You slide 특별 처리
1459
- if slide.get('title') == 'Thank You':
1460
- conclusion_phrase = generate_conclusion_phrase(
1461
- ppt_topic, audience_type, language,
1462
- FRIENDLI_TOKEN, API_URL, MODEL_ID, AUDIENCE_TYPES
1463
- )
1464
- content = {
1465
- "subtitle": conclusion_phrase,
1466
- "bullet_points": []
1467
- }
1468
- speaker_notes = generate_closing_notes(
1469
- ppt_topic, conclusion_phrase, audience_type, language,
1470
- FRIENDLI_TOKEN, API_URL, MODEL_ID, AUDIENCE_TYPES
1471
- )
1472
- else:
1473
- # 일반 슬라이드 폴백 생성
1474
- content = generate_slide_content(
1475
- ppt_topic, slide.get('title', ''), slide_context, audience_type, language,
1476
- uploaded_content, list(search_results.values()) if use_web_search else [],
1477
- FRIENDLI_TOKEN, API_URL, MODEL_ID, AUDIENCE_TYPES
1478
- )
1479
- speaker_notes = generate_presentation_notes(
1480
- ppt_topic, slide.get('title', ''), content, audience_type, language,
1481
- FRIENDLI_TOKEN, API_URL, MODEL_ID, AUDIENCE_TYPES
1482
- )
1483
-
1484
- # 발표자 노트가 없으면 생성
1485
- if not speaker_notes:
1486
- speaker_notes = generate_presentation_notes(
1487
- ppt_topic, slide.get('title', ''), content, audience_type, language,
1488
- FRIENDLI_TOKEN, API_URL, MODEL_ID, AUDIENCE_TYPES
1489
- )
1490
-
1491
- # Generate prompt and image
1492
- style_key = slide.get("style", "Colorful Mind Map")
1493
- if style_key in STYLE_TEMPLATES:
1494
- style_info = STYLE_TEMPLATES[style_key]
1495
- prompt = generate_prompt_with_llm(
1496
- ppt_topic, style_info["example"],
1497
- slide_context, uploaded_content,
1498
- FRIENDLI_TOKEN, API_URL, MODEL_ID
1499
- )
1500
-
1501
- # Generate image
1502
- slide_seed = seed + i
1503
- img, used_prompt = generate_image(
1504
- prompt, slide_seed, slide_info,
1505
- REPLICATE_API_TOKEN, current_topic, current_language,
1506
- FRIENDLI_TOKEN, API_URL, MODEL_ID
1507
- )
1508
-
1509
- # Prepare slide data
1510
- slide_data = {
1511
- "slide_number": i + 1,
1512
- "title": display_title,
1513
- "subtitle": content["subtitle"],
1514
- "bullet_points": content["bullet_points"][:5],
1515
- "image": img,
1516
- "style": style_info["name"],
1517
- "speaker_notes": speaker_notes,
1518
- "topic": ppt_topic
1519
- }
1520
-
1521
- # 디버깅: 실제 사용된 콘텐츠 로그
1522
- logger.info(f"[슬라이드 {i+1}] 최종 콘텐츠:")
1523
- logger.info(f" - 부제목: {slide_data['subtitle']}")
1524
- logger.info(f" - 불릿 포인트: {len(slide_data['bullet_points'])}개")
1525
- if slide_data['bullet_points']:
1526
- logger.info(f" - 첫 번째 불릿: {slide_data['bullet_points'][0]}")
1527
-
1528
- # Generate preview HTML
1529
- preview_html += create_slide_preview_html(slide_data)
1530
-
1531
- # Update current state
1532
- yield supervisor_text, researcher_text, executor_text, None, f"### 🔄 {slide_info} 생성 중..."
1533
-
1534
- results.append({
1535
- "slide_data": slide_data,
1536
- "success": img is not None
1537
- })
1538
- else:
1539
- logger.error(f"[슬라이드 {i+1}] 알 수 없는 스타일: {style_key}")
1540
-
1541
- except Exception as e:
1542
- logger.error(f"[슬라이드 {i+1}] 처리 중 오류: {str(e)}")
1543
- import traceback
1544
- traceback.print_exc()
1545
-
1546
- # 오류 시 기본값
1547
- results.append({
1548
- "slide_data": {
1549
- "slide_number": i + 1,
1550
- "title": display_title,
1551
- "subtitle": "콘텐츠 생성 중 오류 발생",
1552
- "bullet_points": ["• 오류로 인해 콘텐츠를 생성할 수 없습니다"],
1553
- "image": None,
1554
- "style": "Error",
1555
- "speaker_notes": "오류로 인해 발표자 노트를 생성할 수 없습니다.",
1556
- "topic": ppt_topic
1557
- },
1558
- "success": False
1559
- })
1560
-
1561
- # Create PPTX file
1562
- progress(0.95, "Creating PPTX file...")
1563
- pptx_path = None
1564
- try:
1565
- pptx_path = create_pptx_file(results, ppt_topic, template_name, theme_name, DESIGN_THEMES)
1566
- except Exception as e:
1567
- print(f"[PPTX] File creation error: {str(e)}")
1568
- import traceback
1569
- traceback.print_exc()
1570
-
1571
- preview_html += "</div>"
1572
- progress(1.0, "Complete!")
1573
- successful = sum(1 for r in results if r["success"])
1574
- final_status = f"### 🎉 Generation complete! {successful} out of {total_slides} slides succeeded"
1575
-
1576
- # Save global variables
1577
- global current_slides_data, current_template, current_theme
1578
- current_slides_data = results
1579
- current_template = template_name
1580
- current_theme = theme_name
1581
 
1582
- if pptx_path:
1583
- final_status += f"\n\n### 📥 PPTX file is ready!"
1584
- final_status += f"\n\n🎤 **Speaker notes for {audience_type} included in each slide!**"
1585
- if PROCESS_FLOW_AVAILABLE:
1586
- final_status += f"\n\n🔧 **Process flow diagrams are automatically generated ({language} support)**"
1587
 
1588
- yield supervisor_text, researcher_text, executor_text, pptx_path, final_status
 
1589
 
1590
- except Exception as e:
1591
- error_msg = f"❌ 처리 중 오류: {str(e)}"
1592
- print(f"[PPT Generation] Critical error: {str(e)}")
1593
- import traceback
1594
- traceback.print_exc()
1595
- yield "", "", "", None, error_msg
1596
-
1597
- def generate_ppt_handler(topic, template_name, audience_type, language, theme_name, slide_count,
1598
- seed, file_upload, use_web_search, custom_slide_count,
1599
- progress=gr.Progress(), *custom_inputs):
1600
- if not topic.strip():
1601
- yield "", "", "", None, "❌ Please enter a topic."
1602
- return
1603
-
1604
- # Process custom slides
1605
- custom_slides = []
1606
- if template_name == "Custom" and custom_inputs:
1607
  try:
1608
- # Convert custom_inputs to list to avoid tuple slicing issues
1609
- custom_inputs_list = list(custom_inputs)
 
1610
 
1611
- for i in range(0, min(custom_slide_count * 3, len(custom_inputs_list)), 3):
1612
- title = custom_inputs_list[i] if i < len(custom_inputs_list) else ""
1613
- style = custom_inputs_list[i+1] if i+1 < len(custom_inputs_list) else "Colorful Mind Map"
1614
- hint = custom_inputs_list[i+2] if i+2 < len(custom_inputs_list) else ""
1615
-
1616
- if title and style:
1617
- custom_slides.append({
1618
- "title": title,
1619
- "style": style,
1620
- "prompt_hint": hint
1621
- })
1622
- except Exception as e:
1623
- print(f"[Custom Slides] Error processing custom inputs: {str(e)}")
1624
- yield "", "", "", None, f"❌ Error processing custom slides: {str(e)}"
1625
- return
1626
-
1627
- # Generate PPT
1628
- try:
1629
- for supervisor, researcher, executor, pptx_file, status in process_ppt_streaming(
1630
- topic, template_name, audience_type, language, custom_slides, slide_count,
1631
- seed, file_upload, use_web_search, theme_name, progress
1632
- ):
1633
- yield supervisor, researcher, executor, pptx_file, status
1634
  except Exception as e:
1635
- print(f"[PPT Generation] Error: {str(e)}")
1636
  import traceback
1637
- traceback.print_exc()
1638
- yield "", "", "", None, f"❌ Error during PPT generation: {str(e)}"
1639
-
1640
- # ===== Gradio UI =====
1641
- with gr.Blocks(title="PPT Generator", theme=gr.themes.Soft(), css=get_css()) as demo:
1642
- gr.Markdown("""
1643
- # 🎯 Open GAMMA 'GAMJA' - AI Presentation Generator with 3-AI Collaboration
1644
-
1645
- ### Create professional presentations with AI-powered collaboration system!
1646
-
1647
- ** Community: https://discord.gg/openfreeai
1648
- """)
1649
-
1650
- # API token status check
1651
- token_status = []
1652
- if not REPLICATE_API_TOKEN:
1653
- token_status.append("⚠️ RAPI_TOKEN environment variable not set.")
1654
- if not FRIENDLI_TOKEN:
1655
- token_status.append("⚠️ FRIENDLI_TOKEN environment variable not set.")
1656
- if not BRAVE_API_TOKEN:
1657
- token_status.append("ℹ️ BAPI_TOKEN not available. Web search disabled.")
1658
- if not PROCESS_FLOW_AVAILABLE:
1659
- token_status.append("ℹ️ process_flow_generator not available. Process flow diagrams disabled.")
1660
-
1661
- if token_status:
1662
- gr.Markdown("\n".join(token_status))
1663
-
1664
- with gr.Row():
1665
- with gr.Column(scale=1):
1666
- # Language selection (at the top)
1667
- language_select = gr.Radio(
1668
- choices=["English", "Korean"],
1669
- label="🌐 Content Language",
1670
- value="English",
1671
- info="Select the language for your presentation content"
1672
- )
1673
-
1674
- # Example selection
1675
- with gr.Row():
1676
- example_btn = gr.Button("📋 Load Example", size="sm", variant="secondary")
1677
-
1678
- # Basic inputs
1679
- topic_input = gr.Textbox(
1680
- label="Presentation Topic",
1681
- placeholder="e.g., AI Startup Investment Pitch, New Product Launch, Digital Transformation Strategy",
1682
- lines=2
1683
- )
1684
-
1685
- # Audience selection
1686
- audience_select = gr.Dropdown(
1687
- choices=list(AUDIENCE_TYPES.keys()),
1688
- label="🎭 Target Audience",
1689
- value="General Staff",
1690
- info="Content and tone will be automatically optimized for your audience"
1691
- )
1692
-
1693
- # Audience description display
1694
- audience_info = gr.Markdown()
1695
-
1696
- # PPT template selection
1697
- template_select = gr.Dropdown(
1698
- choices=list(PPT_TEMPLATES.keys()),
1699
- label="PPT Template",
1700
- value="Business Proposal",
1701
- info="Select a template that matches your purpose"
1702
- )
1703
-
1704
- # Design theme selection
1705
- theme_select = gr.Dropdown(
1706
- choices=list(DESIGN_THEMES.keys()),
1707
- label="Design Theme",
1708
- value="Minimal Light",
1709
- info="Overall design style for your presentation"
1710
- )
1711
-
1712
- # Theme preview
1713
- theme_preview = gr.HTML()
1714
-
1715
- # Slide count selection
1716
- slide_count = gr.Slider(
1717
- minimum=6,
1718
- maximum=20,
1719
- value=8,
1720
- step=1,
1721
- label="Number of Content Slides (excluding Cover and Thank You)",
1722
- info="Select the number of content slides to generate (not used in Custom template)"
1723
- )
1724
-
1725
- # File upload
1726
- file_upload = gr.File(
1727
- label="Reference Material Upload (Optional)",
1728
- file_types=[".pdf", ".csv", ".txt"],
1729
- type="filepath",
1730
- value=None
1731
- )
1732
-
1733
- # Web search option
1734
- use_web_search = gr.Checkbox(
1735
- label="Use Web Search",
1736
- value=True,
1737
- info="Use Brave Search to include latest information"
1738
- )
1739
-
1740
- # Template description
1741
- template_info = gr.Markdown()
1742
-
1743
- # Seed value
1744
- seed_input = gr.Slider(
1745
- minimum=1,
1746
- maximum=100,
1747
- value=10,
1748
- step=1,
1749
- label="Seed Value"
1750
- )
1751
-
1752
- generate_btn = gr.Button("🚀 Generate PPT (AI creates all content automatically)", variant="primary", size="lg")
1753
-
1754
- # Usage instructions
1755
- with gr.Accordion("📖 How to Use", open=False):
1756
- gr.Markdown(get_usage_instructions())
1757
-
1758
- # Status display
1759
- with gr.Row():
1760
- with gr.Column():
1761
- status_output = gr.Markdown(
1762
- value="### 👆 Select a template and click the generate button!"
1763
- )
1764
-
1765
- # PPTX download area
1766
- download_file = gr.File(
1767
- label="📥 Download Generated PPTX File",
1768
- visible=True,
1769
- elem_id="download-file"
1770
- )
1771
-
1772
- # AI outputs in tabs
1773
- with gr.Tabs():
1774
- with gr.TabItem("🤖 3-AI Collaboration Process"):
1775
- with gr.Row():
1776
- # 감독자 AI 출력
1777
- with gr.Column():
1778
- gr.Markdown("### 🧠 Supervisor AI (Structure Design)")
1779
- supervisor_output = gr.Textbox(
1780
- label="",
1781
- lines=20,
1782
- max_lines=25,
1783
- interactive=False,
1784
- elem_classes=["supervisor-box"]
1785
- )
1786
-
1787
- # 조사자 AI 출력
1788
- with gr.Column():
1789
- gr.Markdown("### 🔍 Researcher AI (Content Collection)")
1790
- researcher_output = gr.Textbox(
1791
- label="",
1792
- lines=20,
1793
- max_lines=25,
1794
- interactive=False,
1795
- elem_classes=["researcher-box"]
1796
- )
1797
-
1798
- # 실행자 AI 출력
1799
- with gr.Column():
1800
- gr.Markdown("### 📝 Executor AI (Slide Creation)")
1801
- executor_output = gr.Textbox(
1802
- label="",
1803
- lines=20,
1804
- max_lines=25,
1805
- interactive=False,
1806
- elem_classes=["executor-box"]
1807
- )
1808
-
1809
- with gr.TabItem("📊 PPT Preview"):
1810
- # Preview area
1811
- preview_output = gr.HTML(
1812
- label="PPT Preview (16:9)",
1813
- elem_classes="preview-container"
1814
- )
1815
-
1816
- # Custom slides section
1817
- with gr.Accordion("📝 Custom Slide Configuration", open=False, elem_id="custom_accordion") as custom_accordion:
1818
- gr.Markdown("Build your presentation without using templates. (3-20 slides)")
1819
-
1820
- # Custom slide count selection
1821
- custom_slide_count = gr.Slider(
1822
- minimum=3,
1823
- maximum=20,
1824
- value=5,
1825
- step=1,
1826
- label="Number of Custom Slides",
1827
- info="Select the number of custom slides to create"
1828
- )
1829
-
1830
- custom_slides_components = create_custom_slides_ui(initial_count=5)
1831
-
1832
- # Connect events
1833
- example_btn.click(
1834
- fn=load_example,
1835
- inputs=[template_select, language_select],
1836
- outputs=[topic_input]
1837
- )
1838
-
1839
- audience_select.change(
1840
- fn=update_audience_info,
1841
- inputs=[audience_select],
1842
- outputs=[audience_info]
1843
- )
1844
-
1845
- theme_select.change(
1846
- fn=update_theme_preview,
1847
- inputs=[theme_select],
1848
- outputs=[theme_preview]
1849
- )
1850
-
1851
- template_select.change(
1852
- fn=update_template_info,
1853
- inputs=[template_select, slide_count],
1854
- outputs=[template_info, slide_count, custom_accordion]
1855
- )
1856
-
1857
- slide_count.change(
1858
- fn=lambda t, s: update_template_info(t, s)[0],
1859
- inputs=[template_select, slide_count],
1860
- outputs=[template_info]
1861
- )
1862
-
1863
- custom_slide_count.change(
1864
- fn=update_custom_slides_visibility,
1865
- inputs=[custom_slide_count],
1866
- outputs=[slide["row"] for slide in custom_slides_components]
1867
- )
1868
-
1869
- # Flatten custom slide input components
1870
- all_custom_inputs = []
1871
- for slide in custom_slides_components:
1872
- all_custom_inputs.extend([slide["title"], slide["style"], slide["hint"]])
1873
-
1874
- generate_btn.click(
1875
- fn=generate_ppt_handler,
1876
- inputs=[
1877
- topic_input, template_select, audience_select, language_select, theme_select,
1878
- slide_count, seed_input, file_upload, use_web_search, custom_slide_count
1879
- ] + all_custom_inputs,
1880
- outputs=[supervisor_output, researcher_output, executor_output, download_file, status_output]
1881
- )
1882
-
1883
- # Load initial info on startup
1884
- demo.load(
1885
- fn=lambda: (update_template_info(template_select.value, slide_count.value)[0],
1886
- update_theme_preview(theme_select.value),
1887
- update_audience_info(audience_select.value)),
1888
- inputs=[],
1889
- outputs=[template_info, theme_preview, audience_info]
1890
- )
1891
 
1892
- # Run app
1893
  if __name__ == "__main__":
1894
- demo.queue() # Enable queue for streaming
1895
- demo.launch(ssr_mode=False)
 
 
 
 
1
  import os
2
+ import sys
3
+ import streamlit as st
4
+ from tempfile import NamedTemporaryFile
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
+ def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  try:
8
+ # Get the code from secrets
9
+ code = os.environ.get("MAIN_CODE")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ if not code:
12
+ st.error("⚠️ The application code wasn't found in secrets. Please add the MAIN_CODE secret.")
13
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # Create a temporary Python file
16
+ with NamedTemporaryFile(suffix='.py', delete=False, mode='w') as tmp:
17
+ tmp.write(code)
18
+ tmp_path = tmp.name
 
19
 
20
+ # Execute the code
21
+ exec(compile(code, tmp_path, 'exec'), globals())
22
 
23
+ # Clean up the temporary file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  try:
25
+ os.unlink(tmp_path)
26
+ except:
27
+ pass
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  except Exception as e:
30
+ st.error(f"⚠️ Error loading or executing the application: {str(e)}")
31
  import traceback
32
+ st.code(traceback.format_exc())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
 
34
  if __name__ == "__main__":
35
+ main()
36
+