Spaces:
Running
Running
Update app.py
Browse files
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
|
6 |
-
import
|
7 |
-
import
|
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 |
-
|
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 |
-
#
|
1261 |
-
|
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 |
-
|
1378 |
-
|
1379 |
-
|
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 |
-
|
1583 |
-
|
1584 |
-
|
1585 |
-
|
1586 |
-
final_status += f"\n\n🔧 **Process flow diagrams are automatically generated ({language} support)**"
|
1587 |
|
1588 |
-
|
|
|
1589 |
|
1590 |
-
|
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 |
-
|
1609 |
-
|
|
|
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 |
-
|
1636 |
import traceback
|
1637 |
-
traceback.
|
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 |
-
|
1895 |
-
|
|
|
|
|
|
|
|
|
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 |
+
|