Delete app-BACKUP2.py
Browse files- app-BACKUP2.py +0 -613
app-BACKUP2.py
DELETED
@@ -1,613 +0,0 @@
|
|
1 |
-
import gradio as gr
|
2 |
-
import replicate
|
3 |
-
import requests
|
4 |
-
import os
|
5 |
-
import json
|
6 |
-
import asyncio
|
7 |
-
import concurrent.futures
|
8 |
-
from io import BytesIO
|
9 |
-
from PIL import Image
|
10 |
-
from typing import List, Tuple, Dict
|
11 |
-
import zipfile
|
12 |
-
from datetime import datetime
|
13 |
-
import time
|
14 |
-
import traceback
|
15 |
-
|
16 |
-
# 환경 변수에서 토큰 가져오기
|
17 |
-
REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
|
18 |
-
FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN")
|
19 |
-
|
20 |
-
# 스타일 정의
|
21 |
-
STYLE_TEMPLATES = {
|
22 |
-
"3D Style (Pixar-like)": {
|
23 |
-
"name": "3D Style",
|
24 |
-
"description": "Pixar-esque 3D render with volumetric lighting",
|
25 |
-
"use_case": "표지, 비전, 미래 컨셉",
|
26 |
-
"example": "A fluffy ginger cat wearing a tiny spacesuit, floating amidst a vibrant nebula in a 3D render. The cat is gazing curiously at a swirling planet with rings made of candy. Background is filled with sparkling stars and colorful gas clouds, lit with soft, volumetric lighting. Style: Pixar-esque, highly detailed, playful. Colors: Deep blues, purples, oranges, and pinks. Rendered in Octane, 8k resolution."
|
27 |
-
},
|
28 |
-
"Elegant SWOT Quadrant": {
|
29 |
-
"name": "SWOT Analysis",
|
30 |
-
"description": "Flat-design 4-grid layout with minimal shadows",
|
31 |
-
"use_case": "현황 분석, 전략 평가",
|
32 |
-
"example": "Elegant SWOT quadrant: flat-design 4-grid on matte-white backdrop, thin pastel separators, top-left 'Strengths' panel shows glowing shield icon and subtle motif, top-right 'Weaknesses' panel with cracked chain icon in soft crimson, bottom-left 'Opportunities' panel with sunrise-over-horizon icon in optimistic teal, bottom-right 'Threats' panel with storm-cloud & lightning icon in deep indigo, minimal shadows, no text, no watermark, 16:9, 4K"
|
33 |
-
},
|
34 |
-
"Colorful Mind Map": {
|
35 |
-
"name": "Mind Map",
|
36 |
-
"description": "Hand-drawn educational style with vibrant colors",
|
37 |
-
"use_case": "브레인스토밍, 아이디어 정리",
|
38 |
-
"example": "A handrawn colorful mind map diagram: educational style, vibrant colors, clear hierarchy, golden ratio layout. Central concept with branching sub-topics, each branch with unique color coding, organic flowing connections, doodle-style icons for each node"
|
39 |
-
},
|
40 |
-
"Business Workflow": {
|
41 |
-
"name": "Business Process",
|
42 |
-
"description": "End-to-end business workflow with clear phases",
|
43 |
-
"use_case": "프로세스 설명, 단계별 진행",
|
44 |
-
"example": "A detailed hand-drawn diagram illustrating an end-to-end business workflow with Market Analysis, Strategy Development, Product Design, Implementation, and Post-Launch Review phases. Clear directional arrows, iconography for each component, vibrant educational yet professional style"
|
45 |
-
},
|
46 |
-
"Industrial Design": {
|
47 |
-
"name": "Product Design",
|
48 |
-
"description": "Sleek industrial design concept sketch",
|
49 |
-
"use_case": "제품 소개, 컨셉 디자인",
|
50 |
-
"example": "A sleek industrial design concept: Curved metallic body with minimal bezel, Touchscreen panel for settings, Modern matte black finish, Hand-drawn concept sketch style with annotations and dimension lines"
|
51 |
-
},
|
52 |
-
"3D Bubble Chart": {
|
53 |
-
"name": "Bubble Chart",
|
54 |
-
"description": "Clean 3D bubble visualization",
|
55 |
-
"use_case": "비교 분석, 포지셔닝",
|
56 |
-
"example": "3-D bubble chart on clean white 2×2 grid, quadrant titles hidden, four translucent spheres in lime, azure, amber, magenta, gentle depth-of-field, modern consulting aesthetic, no text, 4K"
|
57 |
-
},
|
58 |
-
"Timeline Ribbon": {
|
59 |
-
"name": "Timeline",
|
60 |
-
"description": "Horizontal ribbon timeline with cyber-futuristic vibe",
|
61 |
-
"use_case": "일정, 로드맵, 마일스톤",
|
62 |
-
"example": "Horizontal ribbon timeline, milestone pins glowing hot pink on charcoal, year markers as circles, faint motion streaks, cyber-futuristic vibe, no text, 1920×1080"
|
63 |
-
},
|
64 |
-
"Risk Heat Map": {
|
65 |
-
"name": "Heat Map",
|
66 |
-
"description": "Risk assessment heat map with gradient colors",
|
67 |
-
"use_case": "리스크 분석, 우선순위",
|
68 |
-
"example": "Risk Heat Map: square grid, smooth gradient from mint to fire-red, cells beveled, simple legend strip hidden, long subtle shadow, sterile white frame, no text"
|
69 |
-
},
|
70 |
-
"Pyramid/Funnel": {
|
71 |
-
"name": "Funnel Chart",
|
72 |
-
"description": "Multi-layer gradient funnel visualization",
|
73 |
-
"use_case": "단계별 축소, 핵심 도출",
|
74 |
-
"example": "Pyramid / Funnel: 5-layer gradient funnel narrowing downwards, top vivid sky-blue, mid mint-green, bottom sunset-orange, glass reflection, minimal background, no text"
|
75 |
-
},
|
76 |
-
"KPI Dashboard": {
|
77 |
-
"name": "Dashboard",
|
78 |
-
"description": "Dark-mode analytics dashboard with sci-fi interface",
|
79 |
-
"use_case": "성과 지표, 실적 대시보드",
|
80 |
-
"example": "KPI Dashboard: Dark-mode analytic dashboard, three glass speedometers glowing neon lime, two sparkline charts under, black glass background, sci-fi interface, no text, 4K"
|
81 |
-
},
|
82 |
-
"Value Chain": {
|
83 |
-
"name": "Value Chain",
|
84 |
-
"description": "Horizontal value chain with industrial look",
|
85 |
-
"use_case": "가치 사슬, 비즈니스 모델",
|
86 |
-
"example": "Value Chain Diagram: Horizontal value chain blocks, steel-blue gradient bars with subtle bevel, small gear icons above each segment, sleek industrial look, shadow cast, no text"
|
87 |
-
},
|
88 |
-
"Gantt Chart": {
|
89 |
-
"name": "Gantt Chart",
|
90 |
-
"description": "Hand-drawn style Gantt chart with playful colors",
|
91 |
-
"use_case": "프로젝트 일정, 작업 관리",
|
92 |
-
"example": "Gantt Chart: Hand-drawn style Gantt bars sketched with vibrant markers on dotted grid notebook page, sticky-note color palette, playful yet organized, perspective tilt, no text"
|
93 |
-
},
|
94 |
-
"Mobile App Mockup": {
|
95 |
-
"name": "App Mockup",
|
96 |
-
"description": "Clean wireframe for mobile app design",
|
97 |
-
"use_case": "앱/웹 UI, 화면 설계",
|
98 |
-
"example": "MOCKUP DESIGN: A clean hand-drawn style wireframe for a mobile app with Title screen, Login screen, Dashboard with sections, Bottom navigation bar, minimalist design with annotations"
|
99 |
-
},
|
100 |
-
"Flowchart": {
|
101 |
-
"name": "Flowchart",
|
102 |
-
"description": "Vibrant flowchart with minimalistic icons",
|
103 |
-
"use_case": "의사결정, 프로세스 흐름",
|
104 |
-
"example": "FLOWCHART DESIGN: A hand-drawn style flowchart, vibrant colors, minimalistic icons showing process flow from START to END with decision points, branches, and clear directional arrows"
|
105 |
-
}
|
106 |
-
}
|
107 |
-
|
108 |
-
# PPT 템플릿 정의
|
109 |
-
PPT_TEMPLATES = {
|
110 |
-
"비즈니스 제안서": {
|
111 |
-
"description": "투자 유치, 사업 제안용",
|
112 |
-
"slides": [
|
113 |
-
{"title": "표지", "style": "3D Style (Pixar-like)", "prompt_hint": "회사 비전과 미래"},
|
114 |
-
{"title": "목차", "style": "Flowchart", "prompt_hint": "프레젠테이션 구조"},
|
115 |
-
{"title": "문제 정의", "style": "Colorful Mind Map", "prompt_hint": "현재 시장의 문제점"},
|
116 |
-
{"title": "현황 분석", "style": "Elegant SWOT Quadrant", "prompt_hint": "강점, 약점, 기회, 위협"},
|
117 |
-
{"title": "솔루션", "style": "Industrial Design", "prompt_hint": "제품/서비스 컨셉"},
|
118 |
-
{"title": "프로세스", "style": "Business Workflow", "prompt_hint": "실행 단계"},
|
119 |
-
{"title": "일정", "style": "Timeline Ribbon", "prompt_hint": "주요 마일스톤"},
|
120 |
-
{"title": "성과 예측", "style": "KPI Dashboard", "prompt_hint": "예상 성과 지표"},
|
121 |
-
{"title": "투자 요청", "style": "Pyramid/Funnel", "prompt_hint": "투자 규모와 활용"}
|
122 |
-
]
|
123 |
-
},
|
124 |
-
"제품 소개": {
|
125 |
-
"description": "신제품 런칭, 서비스 소개용",
|
126 |
-
"slides": [
|
127 |
-
{"title": "제품 컨셉", "style": "Industrial Design", "prompt_hint": "제품 디자인"},
|
128 |
-
{"title": "사용자 니즈", "style": "Colorful Mind Map", "prompt_hint": "고객 페인포인트"},
|
129 |
-
{"title": "기능 소개", "style": "Mobile App Mockup", "prompt_hint": "UI/UX 화면"},
|
130 |
-
{"title": "작동 원리", "style": "Flowchart", "prompt_hint": "기능 플로우"},
|
131 |
-
{"title": "시장 포지션", "style": "3D Bubble Chart", "prompt_hint": "경쟁사 비교"},
|
132 |
-
{"title": "출시 일정", "style": "Timeline Ribbon", "prompt_hint": "런칭 로드맵"}
|
133 |
-
]
|
134 |
-
},
|
135 |
-
"프로젝트 보고": {
|
136 |
-
"description": "진행 상황, 성과 보고용",
|
137 |
-
"slides": [
|
138 |
-
{"title": "프로젝트 개요", "style": "Business Workflow", "prompt_hint": "전체 프로세스"},
|
139 |
-
{"title": "진행 현황", "style": "Gantt Chart", "prompt_hint": "작업 일정"},
|
140 |
-
{"title": "리스크 관리", "style": "Risk Heat Map", "prompt_hint": "위험 요소"},
|
141 |
-
{"title": "성과 지표", "style": "KPI Dashboard", "prompt_hint": "달성 실적"},
|
142 |
-
{"title": "향후 계획", "style": "Timeline Ribbon", "prompt_hint": "다음 단계"}
|
143 |
-
]
|
144 |
-
},
|
145 |
-
"전략 기획": {
|
146 |
-
"description": "중장기 전략, 비전 수립용",
|
147 |
-
"slides": [
|
148 |
-
{"title": "비전", "style": "3D Style (Pixar-like)", "prompt_hint": "미래 비전"},
|
149 |
-
{"title": "환경 분석", "style": "Elegant SWOT Quadrant", "prompt_hint": "내외부 환경"},
|
150 |
-
{"title": "전략 체계", "style": "Colorful Mind Map", "prompt_hint": "전략 구조"},
|
151 |
-
{"title": "가치 사슬", "style": "Value Chain", "prompt_hint": "비즈니스 모델"},
|
152 |
-
{"title": "실행 로드맵", "style": "Timeline Ribbon", "prompt_hint": "단계별 계획"},
|
153 |
-
{"title": "목표 지표", "style": "KPI Dashboard", "prompt_hint": "KPI 목표"}
|
154 |
-
]
|
155 |
-
},
|
156 |
-
"사용자 정의": {
|
157 |
-
"description": "직접 구성하기",
|
158 |
-
"slides": []
|
159 |
-
}
|
160 |
-
}
|
161 |
-
|
162 |
-
def generate_prompt_with_llm(topic: str, style_example: str = None, slide_context: str = None) -> str:
|
163 |
-
"""주제와 스타일 예제를 받아서 LLM을 사��해 이미지 프롬프트를 생성"""
|
164 |
-
print(f"[LLM] 프롬프트 생성 시작: {slide_context}")
|
165 |
-
|
166 |
-
url = "https://api.friendli.ai/dedicated/v1/chat/completions"
|
167 |
-
headers = {
|
168 |
-
"Authorization": f"Bearer {FRIENDLI_TOKEN}",
|
169 |
-
"Content-Type": "application/json"
|
170 |
-
}
|
171 |
-
|
172 |
-
system_prompt = """You are an expert image prompt engineer specializing in creating prompts for professional presentation slides.
|
173 |
-
|
174 |
-
Your task is to create prompts that:
|
175 |
-
1. Are highly specific and visual, perfect for PPT backgrounds or main visuals
|
176 |
-
2. Consider the slide's purpose and maintain consistency across a presentation
|
177 |
-
3. Include style references matching the given example
|
178 |
-
4. Focus on clean, professional visuals that won't distract from text overlays
|
179 |
-
5. Ensure high contrast areas for text readability when needed
|
180 |
-
6. Maintain brand consistency and professional aesthetics
|
181 |
-
|
182 |
-
Important guidelines:
|
183 |
-
- If given a style example, adapt the topic to match that specific visual style
|
184 |
-
- Consider the slide context (e.g., "표지", "현황 분석") to create appropriate visuals
|
185 |
-
- Always output ONLY the prompt without any explanation
|
186 |
-
- Keep prompts between 50-150 words for optimal results
|
187 |
-
- Ensure the visual supports rather than overwhelms the slide content"""
|
188 |
-
|
189 |
-
user_message = f"Topic: {topic}"
|
190 |
-
if style_example:
|
191 |
-
user_message += f"\n\nStyle reference to follow:\n{style_example}"
|
192 |
-
if slide_context:
|
193 |
-
user_message += f"\n\nSlide context: {slide_context}"
|
194 |
-
|
195 |
-
payload = {
|
196 |
-
"model": "dep89a2fld32mcm",
|
197 |
-
"messages": [
|
198 |
-
{
|
199 |
-
"role": "system",
|
200 |
-
"content": system_prompt
|
201 |
-
},
|
202 |
-
{
|
203 |
-
"role": "user",
|
204 |
-
"content": user_message
|
205 |
-
}
|
206 |
-
],
|
207 |
-
"max_tokens": 300,
|
208 |
-
"top_p": 0.8,
|
209 |
-
"temperature": 0.7,
|
210 |
-
"stream": False
|
211 |
-
}
|
212 |
-
|
213 |
-
try:
|
214 |
-
response = requests.post(url, json=payload, headers=headers, timeout=30)
|
215 |
-
if response.status_code == 200:
|
216 |
-
result = response.json()
|
217 |
-
prompt = result['choices'][0]['message']['content'].strip()
|
218 |
-
print(f"[LLM] 프롬프트 생성 완료: {prompt[:50]}...")
|
219 |
-
return prompt
|
220 |
-
else:
|
221 |
-
error_msg = f"프롬프트 생성 실패: {response.status_code}"
|
222 |
-
print(f"[LLM] {error_msg}")
|
223 |
-
return error_msg
|
224 |
-
except Exception as e:
|
225 |
-
error_msg = f"프롬프트 생성 중 오류 발생: {str(e)}"
|
226 |
-
print(f"[LLM] {error_msg}")
|
227 |
-
return error_msg
|
228 |
-
|
229 |
-
def translate_to_english(text: str) -> str:
|
230 |
-
"""한글 텍스트를 영어로 번역 (LLM 사용)"""
|
231 |
-
if not any(ord('가') <= ord(char) <= ord('힣') for char in text):
|
232 |
-
return text
|
233 |
-
|
234 |
-
print(f"[번역] 한글 감지, 영어로 번역 시작")
|
235 |
-
|
236 |
-
url = "https://api.friendli.ai/dedicated/v1/chat/completions"
|
237 |
-
headers = {
|
238 |
-
"Authorization": f"Bearer {FRIENDLI_TOKEN}",
|
239 |
-
"Content-Type": "application/json"
|
240 |
-
}
|
241 |
-
|
242 |
-
payload = {
|
243 |
-
"model": "dep89a2fld32mcm",
|
244 |
-
"messages": [
|
245 |
-
{
|
246 |
-
"role": "system",
|
247 |
-
"content": "You are a translator. Translate the given Korean text to English. Only return the translation without any explanation."
|
248 |
-
},
|
249 |
-
{
|
250 |
-
"role": "user",
|
251 |
-
"content": text
|
252 |
-
}
|
253 |
-
],
|
254 |
-
"max_tokens": 500,
|
255 |
-
"top_p": 0.8,
|
256 |
-
"stream": False
|
257 |
-
}
|
258 |
-
|
259 |
-
try:
|
260 |
-
response = requests.post(url, json=payload, headers=headers, timeout=30)
|
261 |
-
if response.status_code == 200:
|
262 |
-
result = response.json()
|
263 |
-
translated = result['choices'][0]['message']['content'].strip()
|
264 |
-
print(f"[번역] 완료")
|
265 |
-
return translated
|
266 |
-
else:
|
267 |
-
print(f"[번역] 실패, 원본 사용")
|
268 |
-
return text
|
269 |
-
except Exception as e:
|
270 |
-
print(f"[번역] 오류: {str(e)}, 원본 사용")
|
271 |
-
return text
|
272 |
-
|
273 |
-
def generate_image(prompt: str, seed: int = 10, slide_info: str = "") -> Tuple[Image.Image, str]:
|
274 |
-
"""Replicate API를 사용해 이미지 생성"""
|
275 |
-
print(f"\n[이미지 생성] {slide_info}")
|
276 |
-
print(f"[이미지 생성] 프롬프트: {prompt[:50]}...")
|
277 |
-
|
278 |
-
try:
|
279 |
-
english_prompt = translate_to_english(prompt)
|
280 |
-
|
281 |
-
if not REPLICATE_API_TOKEN:
|
282 |
-
error_msg = "RAPI_TOKEN 환경변수가 설정되지 않았습니다."
|
283 |
-
print(f"[이미지 생성] 오류: {error_msg}")
|
284 |
-
return None, error_msg
|
285 |
-
|
286 |
-
print(f"[이미지 생성] Replicate API 호출 중...")
|
287 |
-
client = replicate.Client(api_token=REPLICATE_API_TOKEN)
|
288 |
-
|
289 |
-
input_params = {
|
290 |
-
"seed": seed,
|
291 |
-
"prompt": english_prompt,
|
292 |
-
"speed_mode": "Extra Juiced 🚀 (even more speed)",
|
293 |
-
"output_quality": 100
|
294 |
-
}
|
295 |
-
|
296 |
-
start_time = time.time()
|
297 |
-
output = client.run(
|
298 |
-
"prunaai/hidream-l1-fast:17c237d753218fed0ed477cb553902b6b75735f48c128537ab829096ef3d3645",
|
299 |
-
input=input_params
|
300 |
-
)
|
301 |
-
|
302 |
-
elapsed = time.time() - start_time
|
303 |
-
print(f"[이미지 생성] API 응답 받음 ({elapsed:.1f}초)")
|
304 |
-
|
305 |
-
if output:
|
306 |
-
if isinstance(output, str) and output.startswith('http'):
|
307 |
-
print(f"[이미지 생성] URL에서 이미지 다운로드 중...")
|
308 |
-
response = requests.get(output, timeout=30)
|
309 |
-
img = Image.open(BytesIO(response.content))
|
310 |
-
print(f"[이미지 생성] 완료!")
|
311 |
-
return img, english_prompt
|
312 |
-
else:
|
313 |
-
print(f"[이미지 생성] 바이너리 데이터 처리 중...")
|
314 |
-
img = Image.open(BytesIO(output.read()))
|
315 |
-
print(f"[이미지 생성] 완료!")
|
316 |
-
return img, english_prompt
|
317 |
-
else:
|
318 |
-
error_msg = "이미지 생성 실패 - 빈 응답"
|
319 |
-
print(f"[이미지 생성] {error_msg}")
|
320 |
-
return None, error_msg
|
321 |
-
|
322 |
-
except Exception as e:
|
323 |
-
error_msg = f"오류: {str(e)}"
|
324 |
-
print(f"[이미지 생성] {error_msg}")
|
325 |
-
print(f"[이미지 생성] 상세 오류:\n{traceback.format_exc()}")
|
326 |
-
return None, error_msg
|
327 |
-
|
328 |
-
def generate_ppt_images_sequential(topic: str, template_name: str, custom_slides: List[Dict], seed: int, progress=gr.Progress()):
|
329 |
-
"""PPT 템플릿에 따라 이미지를 순차적으로 생성하며 진행 상황 표시"""
|
330 |
-
results = []
|
331 |
-
images_for_gallery = []
|
332 |
-
|
333 |
-
# 템플릿 선택
|
334 |
-
if template_name == "사용자 정의" and custom_slides:
|
335 |
-
slides = custom_slides
|
336 |
-
else:
|
337 |
-
slides = PPT_TEMPLATES[template_name]["slides"]
|
338 |
-
|
339 |
-
if not slides:
|
340 |
-
yield [], "슬라이드가 정의되지 않았습니다."
|
341 |
-
return
|
342 |
-
|
343 |
-
total_slides = len(slides)
|
344 |
-
print(f"\n[PPT 생성] 시작 - 총 {total_slides}개 슬라이드")
|
345 |
-
print(f"[PPT 생성] 주제: {topic}")
|
346 |
-
print(f"[PPT 생성] 템플릿: {template_name}")
|
347 |
-
|
348 |
-
# 각 슬라이드 순차 처리
|
349 |
-
for i, slide in enumerate(slides):
|
350 |
-
progress((i + 1) / (total_slides + 1), f"슬라이드 {i+1}/{total_slides} 처리 중...")
|
351 |
-
|
352 |
-
slide_info = f"슬라이드 {i+1}: {slide['title']}"
|
353 |
-
status_msg = f"\n### 🔄 {slide_info} 생성 중...\n"
|
354 |
-
|
355 |
-
# 현재까지의 상태 업데이트
|
356 |
-
yield images_for_gallery, status_msg + format_results_status(results)
|
357 |
-
|
358 |
-
style_key = slide["style"]
|
359 |
-
if style_key in STYLE_TEMPLATES:
|
360 |
-
style_info = STYLE_TEMPLATES[style_key]
|
361 |
-
slide_context = f"{slide['title']} - {slide.get('prompt_hint', '')}"
|
362 |
-
|
363 |
-
# 프롬프트 생성
|
364 |
-
prompt = generate_prompt_with_llm(topic, style_info["example"], slide_context)
|
365 |
-
|
366 |
-
# 이미지 생성
|
367 |
-
slide_seed = seed + i
|
368 |
-
img, used_prompt = generate_image(prompt, slide_seed, slide_info)
|
369 |
-
|
370 |
-
# 결과 저장
|
371 |
-
result = {
|
372 |
-
"slide_title": slide["title"],
|
373 |
-
"style": slide["style"],
|
374 |
-
"image": img,
|
375 |
-
"prompt": prompt,
|
376 |
-
"used_prompt": used_prompt,
|
377 |
-
"success": img is not None
|
378 |
-
}
|
379 |
-
results.append(result)
|
380 |
-
|
381 |
-
# 성공한 이미지는 갤러리에 추가
|
382 |
-
if img is not None:
|
383 |
-
caption = f"{i+1}. {slide['title']} ({style_info['name']})"
|
384 |
-
images_for_gallery.append((img, caption))
|
385 |
-
|
386 |
-
status_msg = f"\n### ✅ {slide_info} 완료!\n"
|
387 |
-
else:
|
388 |
-
status_msg = f"\n### ❌ {slide_info} 실패: {used_prompt}\n"
|
389 |
-
|
390 |
-
# 즉시 업데이트
|
391 |
-
yield images_for_gallery, status_msg + format_results_status(results)
|
392 |
-
|
393 |
-
# 최종 결과
|
394 |
-
progress(1.0, "완료!")
|
395 |
-
successful = sum(1 for r in results if r["success"])
|
396 |
-
final_status = f"\n## 🎉 생성 완료!\n총 {total_slides}개 슬라이드 중 {successful}개 성공\n\n"
|
397 |
-
final_status += format_results_status(results)
|
398 |
-
|
399 |
-
yield images_for_gallery, final_status
|
400 |
-
|
401 |
-
def format_results_status(results: List[Dict]) -> str:
|
402 |
-
"""결과를 상태 메시지로 포맷"""
|
403 |
-
if not results:
|
404 |
-
return ""
|
405 |
-
|
406 |
-
status_lines = ["### 📊 생성 결과:\n"]
|
407 |
-
for i, result in enumerate(results):
|
408 |
-
if result["success"]:
|
409 |
-
status_lines.append(f"✅ **슬라이드 {i+1}: {result['slide_title']}**")
|
410 |
-
status_lines.append(f" - 스타일: {result['style'].split('(')[0].strip()}")
|
411 |
-
status_lines.append(f" - 프롬프트: {result['prompt'][:60]}...\n")
|
412 |
-
else:
|
413 |
-
status_lines.append(f"❌ **슬라이드 {i+1}: {result['slide_title']}** - 실패")
|
414 |
-
status_lines.append(f" - 오류: {result['used_prompt']}\n")
|
415 |
-
|
416 |
-
return "\n".join(status_lines)
|
417 |
-
|
418 |
-
def create_custom_slides_ui():
|
419 |
-
"""사용자 정의 슬라이드 구성 UI"""
|
420 |
-
slides = []
|
421 |
-
for i in range(10):
|
422 |
-
with gr.Row():
|
423 |
-
with gr.Column(scale=2):
|
424 |
-
title = gr.Textbox(
|
425 |
-
label=f"슬라이드 {i+1} 제목",
|
426 |
-
placeholder="예: 표지, 목차, 현황 분석...",
|
427 |
-
visible=(i < 3)
|
428 |
-
)
|
429 |
-
with gr.Column(scale=3):
|
430 |
-
style = gr.Dropdown(
|
431 |
-
choices=list(STYLE_TEMPLATES.keys()),
|
432 |
-
label=f"스타일 선택",
|
433 |
-
visible=(i < 3)
|
434 |
-
)
|
435 |
-
with gr.Column(scale=3):
|
436 |
-
hint = gr.Textbox(
|
437 |
-
label=f"프롬프트 힌트",
|
438 |
-
placeholder="이 슬라이드에서 표현하고 싶은 내용",
|
439 |
-
visible=(i < 3)
|
440 |
-
)
|
441 |
-
slides.append({"title": title, "style": style, "hint": hint})
|
442 |
-
return slides
|
443 |
-
|
444 |
-
# Gradio 인터페이스 생성
|
445 |
-
with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
|
446 |
-
gr.Markdown("""
|
447 |
-
# 🎯 AI 기반 PPT 이미지 생성기
|
448 |
-
|
449 |
-
### 전문적인 프레젠테이션을 위한 맞춤형 이미지를 한 번에 생성하세요!
|
450 |
-
""")
|
451 |
-
|
452 |
-
# API 토큰 상태 확인
|
453 |
-
if not REPLICATE_API_TOKEN:
|
454 |
-
gr.Markdown("⚠️ **경고**: RAPI_TOKEN 환경 변수가 설정되지 않았습니다.")
|
455 |
-
if not FRIENDLI_TOKEN:
|
456 |
-
gr.Markdown("⚠️ **경고**: FRIENDLI_TOKEN 환경 변수가 설정되지 않았습니다.")
|
457 |
-
|
458 |
-
with gr.Row():
|
459 |
-
with gr.Column(scale=1):
|
460 |
-
# 기본 입력
|
461 |
-
topic_input = gr.Textbox(
|
462 |
-
label="프레젠테이션 주제",
|
463 |
-
placeholder="예: AI 스타트업 투자 유치, 신제품 런칭, 디지털 전환 전략",
|
464 |
-
lines=2
|
465 |
-
)
|
466 |
-
|
467 |
-
# PPT 템플릿 선택
|
468 |
-
template_select = gr.Dropdown(
|
469 |
-
choices=list(PPT_TEMPLATES.keys()),
|
470 |
-
label="PPT 템플릿 선택",
|
471 |
-
value="비즈니스 제안서",
|
472 |
-
info="목적에 맞는 템플릿을 선택하세요"
|
473 |
-
)
|
474 |
-
|
475 |
-
# 템플릿 설명
|
476 |
-
template_info = gr.Markdown()
|
477 |
-
|
478 |
-
# 시드 값
|
479 |
-
seed_input = gr.Slider(
|
480 |
-
minimum=1,
|
481 |
-
maximum=100,
|
482 |
-
value=10,
|
483 |
-
step=1,
|
484 |
-
label="시드 값"
|
485 |
-
)
|
486 |
-
|
487 |
-
generate_btn = gr.Button("🚀 PPT 이미지 세트 생성", variant="primary", size="lg")
|
488 |
-
|
489 |
-
# 사용자 정의 섹션
|
490 |
-
with gr.Accordion("📝 사용자 정의 슬라이드 구성", open=False) as custom_accordion:
|
491 |
-
gr.Markdown("템플릿을 사용하지 않고 직접 슬라이드를 구성하세요.")
|
492 |
-
custom_slides_components = create_custom_slides_ui()
|
493 |
-
|
494 |
-
with gr.Column(scale=2):
|
495 |
-
# 진행 상황 표시
|
496 |
-
status_output = gr.Markdown(
|
497 |
-
value="### 👆 템플릿을 선택하고 생성 버튼을 클릭하세요!\n\n생성 진행 상황이 실시간으로 표시됩니다."
|
498 |
-
)
|
499 |
-
|
500 |
-
# 결과 갤러리
|
501 |
-
output_gallery = gr.Gallery(
|
502 |
-
label="생성된 PPT 이미지",
|
503 |
-
show_label=True,
|
504 |
-
elem_id="gallery",
|
505 |
-
columns=3,
|
506 |
-
rows=3,
|
507 |
-
object_fit="contain",
|
508 |
-
height="auto"
|
509 |
-
)
|
510 |
-
|
511 |
-
# 스타일 가이드
|
512 |
-
with gr.Accordion("📚 스타일별 활용 가이드", open=False):
|
513 |
-
style_guide = "### PPT 제작을 위한 스타일 가이드\n\n"
|
514 |
-
for style_name, style_info in STYLE_TEMPLATES.items():
|
515 |
-
style_guide += f"**{style_name}**\n"
|
516 |
-
style_guide += f"- 용도: {style_info['use_case']}\n"
|
517 |
-
style_guide += f"- 특징: {style_info['description']}\n\n"
|
518 |
-
gr.Markdown(style_guide)
|
519 |
-
|
520 |
-
# 활용 팁
|
521 |
-
gr.Markdown("""
|
522 |
-
---
|
523 |
-
### 💡 PPT 제작 팁:
|
524 |
-
|
525 |
-
1. **템플릿 활용**: 목적에 맞는 템플릿을 선택하면 최적화된 슬라이드 구성을 제공합니다
|
526 |
-
2. **일관성**: 하나의 주제로 전체 슬라이드를 생성하여 시각적 일관성을 유지합니다
|
527 |
-
3. **실시간 확인**: 각 슬라이드가 생성될 때마다 진행 상황을 확인할 수 있습니다
|
528 |
-
4. **고화질**: 모든 이미지는 프레젠테이션에 적합한 고해상도로 생성됩니다
|
529 |
-
""")
|
530 |
-
|
531 |
-
# 이벤트 핸들러
|
532 |
-
def update_template_info(template_name):
|
533 |
-
if template_name in PPT_TEMPLATES:
|
534 |
-
template = PPT_TEMPLATES[template_name]
|
535 |
-
info = f"**{template['description']}**\n\n포함된 슬라이드:\n"
|
536 |
-
for i, slide in enumerate(template['slides']):
|
537 |
-
info += f"{i+1}. {slide['title']} - {STYLE_TEMPLATES[slide['style']]['use_case']}\n"
|
538 |
-
return info
|
539 |
-
return ""
|
540 |
-
|
541 |
-
def generate_ppt_images_handler(topic, template_name, seed, progress=gr.Progress(), *custom_inputs):
|
542 |
-
if not topic.strip():
|
543 |
-
yield [], "❌ 주제를 입력해주세요."
|
544 |
-
return
|
545 |
-
|
546 |
-
# 사용자 정의 슬라이드 처리
|
547 |
-
custom_slides = []
|
548 |
-
if template_name == "사용자 정의":
|
549 |
-
for i in range(0, len(custom_inputs), 3):
|
550 |
-
title = custom_inputs[i]
|
551 |
-
style = custom_inputs[i+1] if i+1 < len(custom_inputs) else None
|
552 |
-
hint = custom_inputs[i+2] if i+2 < len(custom_inputs) else ""
|
553 |
-
|
554 |
-
if title and style:
|
555 |
-
custom_slides.append({
|
556 |
-
"title": title,
|
557 |
-
"style": style,
|
558 |
-
"prompt_hint": hint
|
559 |
-
})
|
560 |
-
|
561 |
-
# 순차적으로 이미지 생성하며 진행 상황 표시
|
562 |
-
for gallery, status in generate_ppt_images_sequential(topic, template_name, custom_slides, seed, progress):
|
563 |
-
yield gallery, status
|
564 |
-
|
565 |
-
# 이벤트 연결
|
566 |
-
template_select.change(
|
567 |
-
fn=update_template_info,
|
568 |
-
inputs=[template_select],
|
569 |
-
outputs=[template_info]
|
570 |
-
)
|
571 |
-
|
572 |
-
# 사용자 정의 입력 수집
|
573 |
-
all_custom_inputs = []
|
574 |
-
for slide_components in custom_slides_components:
|
575 |
-
all_custom_inputs.extend([
|
576 |
-
slide_components["title"],
|
577 |
-
slide_components["style"],
|
578 |
-
slide_components["hint"]
|
579 |
-
])
|
580 |
-
|
581 |
-
generate_btn.click(
|
582 |
-
fn=generate_ppt_images_handler,
|
583 |
-
inputs=[topic_input, template_select, seed_input] + all_custom_inputs,
|
584 |
-
outputs=[output_gallery, status_output]
|
585 |
-
)
|
586 |
-
|
587 |
-
# 초기 템플릿 정보 표시
|
588 |
-
demo.load(
|
589 |
-
fn=update_template_info,
|
590 |
-
inputs=[template_select],
|
591 |
-
outputs=[template_info]
|
592 |
-
)
|
593 |
-
|
594 |
-
# 앱 실행
|
595 |
-
if __name__ == "__main__":
|
596 |
-
print("\n" + "="*50)
|
597 |
-
print("🚀 PPT 이미지 생성기 시작!")
|
598 |
-
print("="*50)
|
599 |
-
|
600 |
-
# 환경 변수 확인
|
601 |
-
if not REPLICATE_API_TOKEN:
|
602 |
-
print("⚠️ 경고: RAPI_TOKEN 환경 변수가 설정되지 않았습니다.")
|
603 |
-
else:
|
604 |
-
print("✅ RAPI_TOKEN 확인됨")
|
605 |
-
|
606 |
-
if not FRIENDLI_TOKEN:
|
607 |
-
print("⚠️ 경고: FRIENDLI_TOKEN 환경 변수가 설정되지 않았습니다.")
|
608 |
-
else:
|
609 |
-
print("✅ FRIENDLI_TOKEN 확인됨")
|
610 |
-
|
611 |
-
print("="*50 + "\n")
|
612 |
-
|
613 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|