ginipick commited on
Commit
bfac245
·
verified ·
1 Parent(s): 79ac5ed

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -113
app.py CHANGED
@@ -10,6 +10,8 @@ from PIL import Image
10
  from typing import List, Tuple, Dict
11
  import zipfile
12
  from datetime import datetime
 
 
13
 
14
  # 환경 변수에서 토큰 가져오기
15
  REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
@@ -159,13 +161,14 @@ PPT_TEMPLATES = {
159
 
160
  def generate_prompt_with_llm(topic: str, style_example: str = None, slide_context: str = None) -> str:
161
  """주제와 스타일 예제를 받아서 LLM을 사용해 이미지 프롬프트를 생성"""
 
 
162
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
163
  headers = {
164
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
165
  "Content-Type": "application/json"
166
  }
167
 
168
- # PPT 제작을 위한 강화된 시스템 프롬프트
169
  system_prompt = """You are an expert image prompt engineer specializing in creating prompts for professional presentation slides.
170
 
171
  Your task is to create prompts that:
@@ -208,20 +211,28 @@ Important guidelines:
208
  }
209
 
210
  try:
211
- response = requests.post(url, json=payload, headers=headers)
212
  if response.status_code == 200:
213
  result = response.json()
214
- return result['choices'][0]['message']['content'].strip()
 
 
215
  else:
216
- return f"프롬프트 생성 실패: {response.status_code}"
 
 
217
  except Exception as e:
218
- return f"프롬프트 생성 중 오류 발생: {str(e)}"
 
 
219
 
220
  def translate_to_english(text: str) -> str:
221
  """한글 텍스트를 영어로 번역 (LLM 사용)"""
222
  if not any(ord('가') <= ord(char) <= ord('힣') for char in text):
223
  return text
224
 
 
 
225
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
226
  headers = {
227
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
@@ -246,23 +257,33 @@ def translate_to_english(text: str) -> str:
246
  }
247
 
248
  try:
249
- response = requests.post(url, json=payload, headers=headers)
250
  if response.status_code == 200:
251
  result = response.json()
252
- return result['choices'][0]['message']['content'].strip()
 
 
253
  else:
 
254
  return text
255
  except Exception as e:
 
256
  return text
257
 
258
- def generate_image(prompt: str, seed: int = 10) -> Tuple[Image.Image, str]:
259
  """Replicate API를 사용해 이미지 생성"""
 
 
 
260
  try:
261
  english_prompt = translate_to_english(prompt)
262
 
263
  if not REPLICATE_API_TOKEN:
264
- return None, "RAPI_TOKEN 환경변수가 설정되지 않았습니다."
 
 
265
 
 
266
  client = replicate.Client(api_token=REPLICATE_API_TOKEN)
267
 
268
  input_params = {
@@ -272,28 +293,42 @@ def generate_image(prompt: str, seed: int = 10) -> Tuple[Image.Image, str]:
272
  "output_quality": 80
273
  }
274
 
 
275
  output = client.run(
276
  "prunaai/hidream-l1-fast:17c237d753218fed0ed477cb553902b6b75735f48c128537ab829096ef3d3645",
277
  input=input_params
278
  )
279
 
 
 
 
280
  if output:
281
  if isinstance(output, str) and output.startswith('http'):
282
- response = requests.get(output)
 
283
  img = Image.open(BytesIO(response.content))
 
284
  return img, english_prompt
285
  else:
 
286
  img = Image.open(BytesIO(output.read()))
 
287
  return img, english_prompt
288
  else:
289
- return None, "이미지 생성 실패"
 
 
290
 
291
  except Exception as e:
292
- return None, f"오류: {str(e)}"
 
 
 
293
 
294
- def generate_ppt_images(topic: str, template_name: str, custom_slides: List[Dict], seed: int) -> Tuple[List[Dict], str]:
295
- """PPT 템플릿에 따라 이미지 생성"""
296
  results = []
 
297
 
298
  # 템플릿 선택
299
  if template_name == "사용자 정의" and custom_slides:
@@ -302,69 +337,94 @@ def generate_ppt_images(topic: str, template_name: str, custom_slides: List[Dict
302
  slides = PPT_TEMPLATES[template_name]["slides"]
303
 
304
  if not slides:
305
- return results, "슬라이드가 정의되지 않았습니다."
 
306
 
307
- # 슬라이드에 대한 프롬프트 생성
308
- prompts = []
309
- for slide in slides:
 
 
 
 
 
 
 
 
 
 
 
 
310
  style_key = slide["style"]
311
  if style_key in STYLE_TEMPLATES:
312
  style_info = STYLE_TEMPLATES[style_key]
313
  slide_context = f"{slide['title']} - {slide.get('prompt_hint', '')}"
 
 
314
  prompt = generate_prompt_with_llm(topic, style_info["example"], slide_context)
315
- prompts.append({
316
- "slide": slide,
317
- "prompt": prompt,
318
- "style_info": style_info
319
- })
320
-
321
- # 병렬로 이미지 생성
322
- with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
323
- future_to_slide = {}
324
- for i, prompt_data in enumerate(prompts):
325
- # 각 슬라이드마다 다른 시드 사용하여 다양성 확보
326
  slide_seed = seed + i
327
- future = executor.submit(generate_image, prompt_data["prompt"], slide_seed)
328
- future_to_slide[future] = prompt_data
329
-
330
- for future in concurrent.futures.as_completed(future_to_slide):
331
- prompt_data = future_to_slide[future]
332
- try:
333
- img, used_prompt = future.result()
334
- results.append({
335
- "slide_title": prompt_data["slide"]["title"],
336
- "style": prompt_data["slide"]["style"],
337
- "image": img,
338
- "prompt": prompt_data["prompt"],
339
- "used_prompt": used_prompt,
340
- "success": img is not None
341
- })
342
- except Exception as e:
343
- results.append({
344
- "slide_title": prompt_data["slide"]["title"],
345
- "style": prompt_data["slide"]["style"],
346
- "image": None,
347
- "prompt": prompt_data["prompt"],
348
- "used_prompt": str(e),
349
- "success": False
350
- })
351
-
352
- # 결과 정리
 
353
  successful = sum(1 for r in results if r["success"])
354
- status = f"총 {len(results)}개 슬라이드 중 {successful}개 이미지 생성 완료"
 
355
 
356
- return results, status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
  def create_custom_slides_ui():
359
  """사용자 정의 슬라이드 구성 UI"""
360
  slides = []
361
- for i in range(10): # 최대 10개 슬라이드
362
  with gr.Row():
363
  with gr.Column(scale=2):
364
  title = gr.Textbox(
365
  label=f"슬라이드 {i+1} 제목",
366
  placeholder="예: 표지, 목차, 현황 분석...",
367
- visible=(i < 3) # 처음 3개만 보이기
368
  )
369
  with gr.Column(scale=3):
370
  style = gr.Dropdown(
@@ -381,24 +441,6 @@ def create_custom_slides_ui():
381
  slides.append({"title": title, "style": style, "hint": hint})
382
  return slides
383
 
384
- def format_results_for_display(results: List[Dict]) -> Tuple[List[Tuple[Image.Image, str]], str]:
385
- """결과를 갤러리 표시용으로 포맷"""
386
- images = []
387
- details = []
388
-
389
- for i, result in enumerate(results):
390
- if result["success"] and result["image"]:
391
- caption = f"슬라이드 {i+1}: {result['slide_title']} ({result['style'].split('(')[0].strip()})"
392
- images.append((result["image"], caption))
393
- details.append(f"**슬라이드 {i+1}: {result['slide_title']}**\n"
394
- f"스타일: {result['style']}\n"
395
- f"프롬프트: {result['prompt']}\n")
396
- else:
397
- details.append(f"**슬라이드 {i+1}: {result['slide_title']}** - 생성 실패\n")
398
-
399
- details_text = "\n---\n".join(details)
400
- return images, details_text
401
-
402
  # Gradio 인터페이스 생성
403
  with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
404
  gr.Markdown("""
@@ -407,6 +449,12 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
407
  ### 전문적인 프레젠테이션을 위한 맞춤형 이미지를 한 번에 생성하세요!
408
  """)
409
 
 
 
 
 
 
 
410
  with gr.Row():
411
  with gr.Column(scale=1):
412
  # 기본 입력
@@ -442,10 +490,14 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
442
  with gr.Accordion("📝 사용자 정의 슬라이드 구성", open=False) as custom_accordion:
443
  gr.Markdown("템플릿을 사용하지 않고 직접 슬라이드를 구성하세요.")
444
  custom_slides_components = create_custom_slides_ui()
445
- add_slide_btn = gr.Button("슬라이드 추가", size="sm")
446
 
447
  with gr.Column(scale=2):
448
- # 결과 표시
 
 
 
 
 
449
  output_gallery = gr.Gallery(
450
  label="생성된 PPT 이미지",
451
  show_label=True,
@@ -453,11 +505,7 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
453
  columns=3,
454
  rows=3,
455
  object_fit="contain",
456
- height="600"
457
- )
458
-
459
- details_output = gr.Markdown(
460
- value="생성된 이미지의 상세 정보가 여기에 표시됩니다."
461
  )
462
 
463
  # 스타일 가이드
@@ -476,10 +524,8 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
476
 
477
  1. **템플릿 활용**: 목적에 맞는 템플릿을 선택하면 최적화된 슬라이드 구성을 제공합니다
478
  2. **일관성**: 하나의 주제로 전체 슬라이드를 생성하여 시각적 일관성을 유지합니다
479
- 3. **커스터마이징**: 생성된 이미지 위에 텍스트와 로고를 추가하여 완성하세요
480
  4. **고화질**: 모든 이미지는 프레젠테이션에 적합한 고해상도로 생성됩니다
481
-
482
- **추천 워크플로우**: 템플릿 선택 → 주제 입력 → 이미지 생성 → PPT에 삽입 → 텍스트 추가
483
  """)
484
 
485
  # 이벤트 핸들러
@@ -492,9 +538,10 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
492
  return info
493
  return ""
494
 
495
- def generate_ppt_images_handler(topic, template_name, seed, *custom_inputs):
496
  if not topic.strip():
497
- return [], "주제를 입력해주세요."
 
498
 
499
  # 사용자 정의 슬라이드 처리
500
  custom_slides = []
@@ -511,13 +558,9 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
511
  "prompt_hint": hint
512
  })
513
 
514
- # 이미지 생성
515
- results, status = generate_ppt_images(topic, template_name, custom_slides, seed)
516
-
517
- # 결과 포맷팅
518
- images, details = format_results_for_display(results)
519
-
520
- return images, f"{status}\n\n{details}"
521
 
522
  # 이벤트 연결
523
  template_select.change(
@@ -538,23 +581,9 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
538
  generate_btn.click(
539
  fn=generate_ppt_images_handler,
540
  inputs=[topic_input, template_select, seed_input] + all_custom_inputs,
541
- outputs=[output_gallery, details_output]
542
  )
543
 
544
- # 슬라이드 추가 버튼 (간단한 가시성 토글)
545
- def show_more_slides(*current_values):
546
- # 더 많은 슬라이드 입력 필드 표시 로직
547
- updates = []
548
- for i, val in enumerate(current_values):
549
- if i % 3 == 0: # title fields
550
- if not val and i > 6: # 처음 3개 이후
551
- updates.append(gr.update(visible=False))
552
- else:
553
- updates.append(gr.update(visible=True))
554
- else:
555
- updates.append(gr.update(visible=updates[-3].visible if i % 3 == 1 else updates[-2].visible))
556
- return updates
557
-
558
  # 초기 템플릿 정보 표시
559
  demo.load(
560
  fn=update_template_info,
@@ -564,10 +593,21 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
564
 
565
  # 앱 실행
566
  if __name__ == "__main__":
 
 
 
 
567
  # 환경 변수 확인
568
  if not REPLICATE_API_TOKEN:
569
- print("경고: RAPI_TOKEN 환경 변수가 설정되지 않았습니다.")
 
 
 
570
  if not FRIENDLI_TOKEN:
571
- print("경고: FRIENDLI_TOKEN 환경 변수가 설정되지 않았습니다.")
 
 
 
 
572
 
573
  demo.launch()
 
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")
 
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:
 
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}",
 
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 = {
 
293
  "output_quality": 80
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:
 
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(
 
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("""
 
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
  # 기본 입력
 
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,
 
505
  columns=3,
506
  rows=3,
507
  object_fit="contain",
508
+ height="auto"
 
 
 
 
509
  )
510
 
511
  # 스타일 가이드
 
524
 
525
  1. **템플릿 활용**: 목적에 맞는 템플릿을 선택하면 최적화된 슬라이드 구성을 제공합니다
526
  2. **일관성**: 하나의 주제로 전체 슬라이드를 생성하여 시각적 일관성을 유지합니다
527
+ 3. **실시간 확인**: 슬라이드가 생성될 때마다 진행 상황을 확인할 수 있습니다
528
  4. **고화질**: 모든 이미지는 프레젠테이션에 적합한 고해상도로 생성됩니다
 
 
529
  """)
530
 
531
  # 이벤트 핸들러
 
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 = []
 
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(
 
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,
 
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()