ginipick commited on
Commit
9e1e42e
·
verified ·
1 Parent(s): 4957e25

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +341 -91
app.py CHANGED
@@ -12,6 +12,7 @@ import zipfile
12
  from datetime import datetime
13
  import time
14
  import traceback
 
15
 
16
  # 환경 변수에서 토큰 가져오기
17
  REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
@@ -159,6 +160,136 @@ PPT_TEMPLATES = {
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}")
@@ -290,7 +421,7 @@ def generate_image(prompt: str, seed: int = 10, slide_info: str = "") -> Tuple[I
290
  "seed": seed,
291
  "prompt": english_prompt,
292
  "speed_mode": "Extra Juiced 🚀 (even more speed)",
293
- "output_quality": 80
294
  }
295
 
296
  start_time = time.time()
@@ -325,10 +456,146 @@ def generate_image(prompt: str, seed: int = 10, slide_info: str = "") -> Tuple[I
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,7 +604,7 @@ def generate_ppt_images_sequential(topic: str, template_name: str, custom_slides
337
  slides = PPT_TEMPLATES[template_name]["slides"]
338
 
339
  if not slides:
340
- yield [], "슬라이드가 정의되지 않았습니다."
341
  return
342
 
343
  total_slides = len(slides)
@@ -345,75 +612,66 @@ def generate_ppt_images_sequential(topic: str, template_name: str, custom_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"""
@@ -442,11 +700,13 @@ def create_custom_slides_ui():
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 토큰 상태 확인
@@ -484,48 +744,38 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
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
  # 이벤트 핸들러
@@ -538,9 +788,9 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
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
  # 사용자 정의 슬라이드 처리
@@ -558,9 +808,9 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
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(
@@ -579,9 +829,9 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
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
  # 초기 템플릿 정보 표시
@@ -594,7 +844,7 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft()) as demo:
594
  # 앱 실행
595
  if __name__ == "__main__":
596
  print("\n" + "="*50)
597
- print("🚀 PPT 이미지 생성기 시작!")
598
  print("="*50)
599
 
600
  # 환경 변수 확인
 
12
  from datetime import datetime
13
  import time
14
  import traceback
15
+ import base64
16
 
17
  # 환경 변수에서 토큰 가져오기
18
  REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
 
160
  }
161
  }
162
 
163
+ def generate_slide_content(topic: str, slide_title: str, slide_context: str) -> Dict[str, str]:
164
+ """각 슬라이드의 텍스트 내용 생성"""
165
+ print(f"[슬라이드 내용] {slide_title} 텍스트 생성 중...")
166
+
167
+ url = "https://api.friendli.ai/dedicated/v1/chat/completions"
168
+ headers = {
169
+ "Authorization": f"Bearer {FRIENDLI_TOKEN}",
170
+ "Content-Type": "application/json"
171
+ }
172
+
173
+ system_prompt = """You are a professional presentation content writer specializing in creating concise, impactful slide content.
174
+
175
+ Your task is to create:
176
+ 1. A compelling subtitle (max 10 words)
177
+ 2. Exactly 5 bullet points, each being a complete, concise sentence
178
+ 3. Each bullet point should be 10-15 words
179
+
180
+ Guidelines:
181
+ - Be specific and actionable
182
+ - Use professional business language
183
+ - Include relevant data points or metrics when appropriate
184
+ - Ensure content aligns with the slide's purpose
185
+ - Make each point distinct and valuable
186
+ - Use active voice and strong verbs
187
+
188
+ Output format:
189
+ Subtitle: [subtitle here]
190
+ • [Point 1]
191
+ • [Point 2]
192
+ • [Point 3]
193
+ • [Point 4]
194
+ • [Point 5]"""
195
+
196
+ user_message = f"""Topic: {topic}
197
+ Slide Title: {slide_title}
198
+ Context: {slide_context}
199
+
200
+ Create compelling content for this presentation slide."""
201
+
202
+ payload = {
203
+ "model": "dep89a2fld32mcm",
204
+ "messages": [
205
+ {
206
+ "role": "system",
207
+ "content": system_prompt
208
+ },
209
+ {
210
+ "role": "user",
211
+ "content": user_message
212
+ }
213
+ ],
214
+ "max_tokens": 300,
215
+ "top_p": 0.8,
216
+ "temperature": 0.7,
217
+ "stream": False
218
+ }
219
+
220
+ try:
221
+ response = requests.post(url, json=payload, headers=headers, timeout=30)
222
+ if response.status_code == 200:
223
+ result = response.json()
224
+ content = result['choices'][0]['message']['content'].strip()
225
+
226
+ # Parse content
227
+ lines = content.split('\n')
228
+ subtitle = ""
229
+ bullet_points = []
230
+
231
+ for line in lines:
232
+ if line.startswith("Subtitle:"):
233
+ subtitle = line.replace("Subtitle:", "").strip()
234
+ elif line.strip().startswith("•"):
235
+ bullet_points.append(line.strip())
236
+
237
+ # 한글로 번역이 필요한 경우
238
+ if any(ord('가') <= ord(char) <= ord('힣') for char in topic):
239
+ subtitle = translate_content_to_korean(subtitle)
240
+ bullet_points = [translate_content_to_korean(point) for point in bullet_points]
241
+
242
+ return {
243
+ "subtitle": subtitle,
244
+ "bullet_points": bullet_points[:5] # 최대 5개
245
+ }
246
+ else:
247
+ return {
248
+ "subtitle": slide_title,
249
+ "bullet_points": ["내용을 생성할 수 없습니다."] * 5
250
+ }
251
+ except Exception as e:
252
+ print(f"[슬라이드 내용] 오류: {str(e)}")
253
+ return {
254
+ "subtitle": slide_title,
255
+ "bullet_points": ["내용을 생성할 수 없습니다."] * 5
256
+ }
257
+
258
+ def translate_content_to_korean(text: str) -> str:
259
+ """영어 텍스트를 한글로 번역"""
260
+ url = "https://api.friendli.ai/dedicated/v1/chat/completions"
261
+ headers = {
262
+ "Authorization": f"Bearer {FRIENDLI_TOKEN}",
263
+ "Content-Type": "application/json"
264
+ }
265
+
266
+ payload = {
267
+ "model": "dep89a2fld32mcm",
268
+ "messages": [
269
+ {
270
+ "role": "system",
271
+ "content": "You are a translator. Translate the given English text to Korean. Maintain professional business tone. Only return the translation without any explanation."
272
+ },
273
+ {
274
+ "role": "user",
275
+ "content": text
276
+ }
277
+ ],
278
+ "max_tokens": 200,
279
+ "top_p": 0.8,
280
+ "stream": False
281
+ }
282
+
283
+ try:
284
+ response = requests.post(url, json=payload, headers=headers, timeout=30)
285
+ if response.status_code == 200:
286
+ result = response.json()
287
+ return result['choices'][0]['message']['content'].strip()
288
+ else:
289
+ return text
290
+ except Exception as e:
291
+ return text
292
+
293
  def generate_prompt_with_llm(topic: str, style_example: str = None, slide_context: str = None) -> str:
294
  """주제와 스타일 예제를 받아서 LLM을 사용해 이미지 프롬프트를 생성"""
295
  print(f"[LLM] 프롬프트 생성 시작: {slide_context}")
 
421
  "seed": seed,
422
  "prompt": english_prompt,
423
  "speed_mode": "Extra Juiced 🚀 (even more speed)",
424
+ "output_quality": 100
425
  }
426
 
427
  start_time = time.time()
 
456
  print(f"[이미지 생성] 상세 오류:\n{traceback.format_exc()}")
457
  return None, error_msg
458
 
459
+ def create_slide_preview_html(slide_data: Dict) -> str:
460
+ """16:9 비율의 슬라이드 프리뷰 HTML 생성"""
461
+
462
+ # 이미지를 base64로 인코딩
463
+ img_base64 = ""
464
+ if slide_data.get("image"):
465
+ buffered = BytesIO()
466
+ slide_data["image"].save(buffered, format="PNG")
467
+ img_base64 = base64.b64encode(buffered.getvalue()).decode()
468
+
469
+ # 텍스트 내용 가져오기
470
+ subtitle = slide_data.get("subtitle", "")
471
+ bullet_points = slide_data.get("bullet_points", [])
472
+
473
+ # HTML 생성
474
+ html = f"""
475
+ <div class="slide-container" style="
476
+ width: 100%;
477
+ max-width: 1200px;
478
+ margin: 20px auto;
479
+ background: white;
480
+ border-radius: 8px;
481
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
482
+ overflow: hidden;
483
+ ">
484
+ <div class="slide-header" style="
485
+ background: #2c3e50;
486
+ color: white;
487
+ padding: 15px 30px;
488
+ font-size: 18px;
489
+ font-weight: bold;
490
+ ">
491
+ 슬라이드 {slide_data.get('slide_number', '')}: {slide_data.get('title', '')}
492
+ </div>
493
+
494
+ <div class="slide-content" style="
495
+ display: flex;
496
+ height: 0;
497
+ padding-bottom: 56.25%; /* 16:9 비율 */
498
+ position: relative;
499
+ ">
500
+ <div class="slide-inner" style="
501
+ position: absolute;
502
+ top: 0;
503
+ left: 0;
504
+ width: 100%;
505
+ height: 100%;
506
+ display: flex;
507
+ ">
508
+ <!-- 텍스트 영역 (좌측) -->
509
+ <div class="text-area" style="
510
+ flex: 1;
511
+ padding: 40px;
512
+ display: flex;
513
+ flex-direction: column;
514
+ justify-content: center;
515
+ background: #f8f9fa;
516
+ ">
517
+ <h2 style="
518
+ color: #2c3e50;
519
+ font-size: 28px;
520
+ margin-bottom: 30px;
521
+ font-weight: 600;
522
+ ">{subtitle}</h2>
523
+
524
+ <ul style="
525
+ list-style: none;
526
+ padding: 0;
527
+ margin: 0;
528
+ ">
529
+ """
530
+
531
+ for point in bullet_points:
532
+ html += f"""
533
+ <li style="
534
+ margin-bottom: 15px;
535
+ padding-left: 25px;
536
+ position: relative;
537
+ color: #34495e;
538
+ font-size: 16px;
539
+ line-height: 1.6;
540
+ ">
541
+ <span style="
542
+ position: absolute;
543
+ left: 0;
544
+ color: #3498db;
545
+ ">▶</span>
546
+ {point.replace('•', '').strip()}
547
+ </li>
548
+ """
549
+
550
+ html += f"""
551
+ </ul>
552
+ </div>
553
+
554
+ <!-- 이미지 영역 (우측) -->
555
+ <div class="image-area" style="
556
+ flex: 1;
557
+ background: #e9ecef;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ padding: 20px;
562
+ ">
563
+ """
564
+
565
+ if img_base64:
566
+ html += f"""
567
+ <img src="data:image/png;base64,{img_base64}" style="
568
+ max-width: 100%;
569
+ max-height: 100%;
570
+ object-fit: contain;
571
+ border-radius: 4px;
572
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
573
+ " alt="Slide Image">
574
+ """
575
+ else:
576
+ html += """
577
+ <div style="
578
+ color: #6c757d;
579
+ text-align: center;
580
+ ">
581
+ <div style="font-size: 48px;">🖼️</div>
582
+ <p>이미지 생성 중...</p>
583
+ </div>
584
+ """
585
+
586
+ html += """
587
+ </div>
588
+ </div>
589
+ </div>
590
+ </div>
591
+ """
592
+
593
+ return html
594
+
595
+ def generate_ppt_with_content(topic: str, template_name: str, custom_slides: List[Dict], seed: int, progress=gr.Progress()):
596
+ """PPT 이미지와 텍스트 내용을 함께 생성"""
597
  results = []
598
+ preview_html = ""
599
 
600
  # 템플릿 선택
601
  if template_name == "사용자 정의" and custom_slides:
 
604
  slides = PPT_TEMPLATES[template_name]["slides"]
605
 
606
  if not slides:
607
+ yield "", "슬라이드가 정의되지 않았습니다."
608
  return
609
 
610
  total_slides = len(slides)
 
612
  print(f"[PPT 생성] 주제: {topic}")
613
  print(f"[PPT 생성] 템플릿: {template_name}")
614
 
615
+ # CSS 스타일 추가
616
+ preview_html = """
617
+ <style>
618
+ .slides-container {
619
+ width: 100%;
620
+ max-width: 1400px;
621
+ margin: 0 auto;
622
+ }
623
+ </style>
624
+ <div class="slides-container">
625
+ """
626
+
627
  # 각 슬라이드 순차 처리
628
  for i, slide in enumerate(slides):
629
  progress((i + 1) / (total_slides + 1), f"슬라이드 {i+1}/{total_slides} 처리 중...")
630
 
631
  slide_info = f"슬라이드 {i+1}: {slide['title']}"
 
632
 
633
+ # 텍스트 내용 생성
634
+ slide_context = f"{slide['title']} - {slide.get('prompt_hint', '')}"
635
+ content = generate_slide_content(topic, slide['title'], slide_context)
636
 
637
+ # 프롬프트 생성 및 이미지 생성
638
  style_key = slide["style"]
639
  if style_key in STYLE_TEMPLATES:
640
  style_info = STYLE_TEMPLATES[style_key]
 
 
 
641
  prompt = generate_prompt_with_llm(topic, style_info["example"], slide_context)
642
 
643
  # 이미지 생성
644
  slide_seed = seed + i
645
  img, used_prompt = generate_image(prompt, slide_seed, slide_info)
646
 
647
+ # 슬라이드 데이터 구성
648
+ slide_data = {
649
+ "slide_number": i + 1,
650
+ "title": slide["title"],
651
+ "subtitle": content["subtitle"],
652
+ "bullet_points": content["bullet_points"],
653
  "image": img,
654
+ "style": style_info["name"]
 
 
655
  }
 
656
 
657
+ # 프리뷰 HTML 생성
658
+ preview_html += create_slide_preview_html(slide_data)
 
 
 
 
 
 
659
 
660
+ # 현재까지의 상태 업데이트
661
+ yield preview_html + "</div>", f"### 🔄 {slide_info} 생성 중..."
662
+
663
+ results.append({
664
+ "slide_data": slide_data,
665
+ "success": img is not None
666
+ })
667
 
668
  # 최종 결과
669
+ preview_html += "</div>"
670
  progress(1.0, "완료!")
671
  successful = sum(1 for r in results if r["success"])
672
+ final_status = f"### 🎉 생성 완료! 총 {total_slides}개 슬라이드 중 {successful}개 성공"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
673
 
674
+ yield preview_html, final_status
675
 
676
  def create_custom_slides_ui():
677
  """사용자 정의 슬라이드 구성 UI"""
 
700
  return slides
701
 
702
  # Gradio 인터페이스 생성
703
+ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
704
+ .preview-container { max-width: 1400px; margin: 0 auto; }
705
+ """) as demo:
706
  gr.Markdown("""
707
+ # 🎯 AI 기반 PPT 통합 생성기
708
 
709
+ ### 텍스트와 이미지가 완벽하게 조화된 프레젠테이션을 자동으로 생성합니다!
710
  """)
711
 
712
  # API 토큰 상태 확인
 
744
  label="시드 값"
745
  )
746
 
747
+ generate_btn = gr.Button("🚀 PPT 전체 생성 (텍스트 + 이미지)", variant="primary", size="lg")
748
 
749
  # 사용자 정의 섹션
750
  with gr.Accordion("📝 사용자 정의 슬라이드 구성", open=False) as custom_accordion:
751
  gr.Markdown("템플릿을 사용하지 않고 직접 슬라이드를 구성하세요.")
752
  custom_slides_components = create_custom_slides_ui()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
 
754
+ # 상태 표시
755
+ status_output = gr.Markdown(
756
+ value="### 👆 템플릿을 선택하고 생성 버튼을 클릭하세요!"
757
+ )
758
+
759
+ # 프리뷰 영역
760
+ preview_output = gr.HTML(
761
+ label="PPT 프리뷰 (16:9)",
762
+ elem_classes="preview-container"
763
+ )
764
 
765
  # 활용 팁
766
  gr.Markdown("""
767
  ---
768
+ ### 💡 새로운 기능:
769
+
770
+ 1. **자동 텍스트 생성**: 각 슬라이드마다 적절한 제목과 5개의 핵심 포인트 자동 생성
771
+ 2. **16:9 프리뷰**: 실제 PPT와 동일한 비율로 미리보기
772
+ 3. **좌우 레이아웃**: 좌측 텍스트, 우측 이미지로 깔끔한 구성
773
+ 4. **실시간 업데이트**: 각 슬라이드가 생성될 때마다 즉시 확인
774
 
775
+ ### 📌 활용 팁:
776
+ - 생성된 내용을 복사하여 실제 PPT에 바로 사용 가능
777
+ -슬라이드의 텍스트와 이미지가 주제에 맞게 자동 조율
778
+ - 한글 주제 입력 한글로 텍스트 생성
779
  """)
780
 
781
  # 이벤트 핸들러
 
788
  return info
789
  return ""
790
 
791
+ def generate_ppt_handler(topic, template_name, seed, progress=gr.Progress(), *custom_inputs):
792
  if not topic.strip():
793
+ yield "", "❌ 주제를 입력해주세요."
794
  return
795
 
796
  # 사용자 정의 슬라이드 처리
 
808
  "prompt_hint": hint
809
  })
810
 
811
+ # PPT 생성
812
+ for preview, status in generate_ppt_with_content(topic, template_name, custom_slides, seed, progress):
813
+ yield preview, status
814
 
815
  # 이벤트 연결
816
  template_select.change(
 
829
  ])
830
 
831
  generate_btn.click(
832
+ fn=generate_ppt_handler,
833
  inputs=[topic_input, template_select, seed_input] + all_custom_inputs,
834
+ outputs=[preview_output, status_output]
835
  )
836
 
837
  # 초기 템플릿 정보 표시
 
844
  # 앱 실행
845
  if __name__ == "__main__":
846
  print("\n" + "="*50)
847
+ print("🚀 PPT 통합 생성기 시작!")
848
  print("="*50)
849
 
850
  # 환경 변수 확인