ginipick commited on
Commit
40eb342
Β·
verified Β·
1 Parent(s): 5529a6a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -678
app.py CHANGED
@@ -35,6 +35,40 @@ EXAMPLE_TOPICS = {
35
  "μ „λž΅ 기획": "2025λ…„ κΈ€λ‘œλ²Œ μ‹œμž₯ μ§„μΆœ μ „λž΅ 수립"
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  # ν™˜κ²½ λ³€μˆ˜μ—μ„œ 토큰 κ°€μ Έμ˜€κΈ°
39
  REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
40
  FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN")
@@ -396,34 +430,41 @@ def read_uploaded_file(file_path: str) -> str:
396
  except Exception as e:
397
  return f"파일 읽기 였λ₯˜: {str(e)}"
398
 
399
- def generate_presentation_notes(topic: str, slide_title: str, content: Dict) -> str:
400
  """각 μŠ¬λΌμ΄λ“œμ˜ λ°œν‘œμž λ…ΈνŠΈ 생성 (ꡬ어체)"""
401
  print(f"[λ°œν‘œμž λ…ΈνŠΈ] {slide_title} 생성 쀑...")
402
 
 
 
 
403
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
404
  headers = {
405
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
406
  "Content-Type": "application/json"
407
  }
408
 
409
- system_prompt = """You are a professional presentation coach who creates natural, conversational speaker notes.
 
 
 
 
410
 
411
  The slide content uses concise noun-ending style, but your speaker notes should be natural and conversational.
412
 
413
  Create speaker notes that:
414
- 1. Sound natural and conversational, as if speaking to an audience
415
- 2. Expand on the concise bullet points with additional context
416
- 3. Include transitions and engagement phrases
417
- 4. Use a warm, professional tone
418
  5. Be 100-150 words long
419
  6. Include pauses and emphasis markers where appropriate
420
 
421
  Note: The bullet points may include emojis - incorporate their meaning into your speech.
422
 
423
  Format:
424
- - Use conversational language
425
  - Include transition phrases
426
- - Add engagement questions or comments
427
  - Keep it professional but friendly"""
428
 
429
  bullet_text = "\n".join(content.get("bullet_points", []))
@@ -433,7 +474,7 @@ Subtitle: {content.get('subtitle', '')}
433
  Key Points (concise style with emojis):
434
  {bullet_text}
435
 
436
- Create natural speaker notes that expand on these concise points for presenting this slide."""
437
 
438
  payload = {
439
  "model": "dep89a2fld32mcm",
@@ -469,161 +510,29 @@ Create natural speaker notes that expand on these concise points for presenting
469
  print(f"[λ°œν‘œμž λ…ΈνŠΈ] 였λ₯˜: {str(e)}")
470
  return "이 μŠ¬λΌμ΄λ“œμ—μ„œλŠ” 핡심 λ‚΄μš©μ„ μ„€λͺ…ν•΄ μ£Όμ„Έμš”."
471
 
472
- def regenerate_slide_text(topic: str, slide_title: str, slide_context: str,
473
- user_instruction: str, uploaded_content: str = None) -> Dict[str, str]:
474
- """μ‚¬μš©μž μ§€μ‹œμ‚¬ν•­μ— 따라 μŠ¬λΌμ΄λ“œ ν…μŠ€νŠΈ μž¬μƒμ„±"""
475
- print(f"[AI μž¬μž‘μ„±] {slide_title} - μ§€μ‹œμ‚¬ν•­: {user_instruction}")
476
-
477
- url = "https://api.friendli.ai/dedicated/v1/chat/completions"
478
- headers = {
479
- "Authorization": f"Bearer {FRIENDLI_TOKEN}",
480
- "Content-Type": "application/json"
481
- }
482
-
483
- system_prompt = """You are a professional presentation content writer.
484
- Follow the user's specific instructions to rewrite slide content.
485
-
486
- Your task is to create:
487
- 1. A compelling subtitle (max 10 words)
488
- 2. Exactly 5 bullet points with emojis
489
- 3. Each bullet point should be 8-12 words
490
- 4. Use noun-ending style (λͺ…μ‚¬ν˜• μ’…κ²°) for Korean or concise fragments for English
491
-
492
- IMPORTANT Style Guidelines:
493
- - End sentences with nouns or concise phrases
494
- - Avoid long verb endings like "μž…λ‹ˆλ‹€", "μŠ΅λ‹ˆλ‹€"
495
- - Use short endings like "μž„", "함" or noun forms
496
- - Start each bullet with a relevant emoji
497
- - Follow the user's instructions while maintaining this style
498
-
499
- Emoji Guidelines:
500
- - Professional emojis: πŸ“Š 🎯 πŸ’‘ πŸš€ ⚑ πŸ” πŸ“ˆ πŸ’° πŸ† πŸ”§ 🌐 πŸ” ⭐ 🎨 πŸ“± 🀝 πŸ“ πŸŽ–οΈ πŸ—οΈ 🌱
501
- - Match emoji to content meaning
502
- - Use different emojis for variety
503
-
504
- Output format (EXACTLY FOLLOW THIS FORMAT):
505
- Subtitle: [subtitle here]
506
- - 🎯 [Point 1]
507
- - πŸ“Š [Point 2]
508
- - πŸ’‘ [Point 3]
509
- - πŸš€ [Point 4]
510
- - ⚑ [Point 5]"""
511
-
512
- user_message = f"""Topic: {topic}
513
- Slide Title: {slide_title}
514
- Context: {slide_context}
515
-
516
- User's specific instruction: {user_instruction}
517
-
518
- Rewrite the content following the instruction above while maintaining concise, noun-ending style with emojis."""
519
-
520
- if uploaded_content:
521
- user_message += f"\n\nReference Material:\n{uploaded_content[:1000]}"
522
-
523
- payload = {
524
- "model": "dep89a2fld32mcm",
525
- "messages": [
526
- {
527
- "role": "system",
528
- "content": system_prompt
529
- },
530
- {
531
- "role": "user",
532
- "content": user_message
533
- }
534
- ],
535
- "max_tokens": 300,
536
- "temperature": 0.8,
537
- "stream": False
538
- }
539
-
540
- try:
541
- response = requests.post(url, json=payload, headers=headers, timeout=30)
542
- if response.status_code == 200:
543
- result = response.json()
544
- content = result['choices'][0]['message']['content'].strip()
545
-
546
- print(f"[AI μž¬μž‘μ„±] LLM 응닡:\n{content}")
547
-
548
- # Parse content - 더 κ°•κ±΄ν•œ νŒŒμ‹±
549
- lines = content.split('\n')
550
- subtitle = ""
551
- bullet_points = []
552
-
553
- for line in lines:
554
- line = line.strip()
555
- if not line:
556
- continue
557
-
558
- # Subtitle νŒŒμ‹±
559
- if line.lower().startswith("subtitle:") or line.startswith("Subtitle:"):
560
- subtitle = line.split(':', 1)[1].strip()
561
- # Bullet point νŒŒμ‹±
562
- elif line.startswith("β€’") or line.startswith("-") or (len(line) > 2 and line[1] == ' ' and ord(line[0]) >= 128):
563
- if not line.startswith("β€’"):
564
- line = "β€’ " + line.lstrip("- ")
565
- bullet_points.append(line)
566
-
567
- # κΈ°λ³Έκ°’ 처리
568
- if not subtitle:
569
- subtitle = f"{slide_title} κ°œμš”"
570
-
571
- while len(bullet_points) < 5:
572
- bullet_points.append(f"β€’ πŸ“Œ 포인트 {len(bullet_points) + 1}")
573
-
574
- bullet_points = bullet_points[:5]
575
-
576
- # ν•œκΈ€λ‘œ λ²ˆμ—­μ΄ ν•„μš”ν•œ 경우
577
- if any(ord('κ°€') <= ord(char) <= ord('힣') for char in topic):
578
- subtitle = translate_content_to_korean(subtitle)
579
- # 뢈릿 포인트 λ²ˆμ—­
580
- translated_bullets = []
581
- for point in bullet_points:
582
- clean_point = point.replace('β€’', '').strip()
583
-
584
- if len(clean_point) > 0 and clean_point[0] in 'πŸŽ―πŸ“ŠπŸ’‘πŸš€βš‘πŸ”πŸ“ˆπŸ’°πŸ†πŸ”§πŸŒπŸ”β­πŸŽ¨πŸ“±πŸ€πŸ“πŸŽ–οΈπŸ—οΈπŸŒ±':
585
- emoji = clean_point[0]
586
- text = clean_point[1:].strip()
587
- translated_text = translate_content_to_korean_concise(text)
588
- translated_bullets.append(f"β€’ {emoji} {translated_text}")
589
- else:
590
- translated_bullets.append(f"β€’ {translate_content_to_korean_concise(clean_point)}")
591
-
592
- bullet_points = translated_bullets
593
-
594
- return {
595
- "subtitle": subtitle,
596
- "bullet_points": bullet_points
597
- }
598
- else:
599
- return {
600
- "subtitle": slide_title,
601
- "bullet_points": ["β€’ ❌ μž¬μƒμ„± μ‹€νŒ¨"] * 5
602
- }
603
- except Exception as e:
604
- print(f"[AI μž¬μž‘μ„±] 였λ₯˜: {str(e)}")
605
- return {
606
- "subtitle": slide_title,
607
- "bullet_points": ["β€’ ❌ μž¬μƒμ„± 였λ₯˜"] * 5
608
- }
609
-
610
- def generate_closing_notes(topic: str, conclusion_phrase: str) -> str:
611
  """λ§ˆμ§€λ§‰ μŠ¬λΌμ΄λ“œμ˜ λ°œν‘œμž λ…ΈνŠΈ 생성"""
612
  print(f"[마무리 λ…ΈνŠΈ] 생성 쀑...")
613
 
 
 
 
614
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
615
  headers = {
616
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
617
  "Content-Type": "application/json"
618
  }
619
 
620
- system_prompt = """You are a professional presentation coach creating closing speaker notes.
 
 
 
621
 
622
  Create natural closing remarks that:
623
- 1. Thank the audience warmly
624
- 2. Briefly summarize the key message of the presentation
625
  3. Reference the conclusion phrase naturally
626
- 4. End with an invitation for questions or next steps
627
  5. Be 80-100 words long
628
  6. Sound conversational and warm
629
  7. NO stage directions or parentheses - only spoken words
@@ -631,9 +540,10 @@ Create natural closing remarks that:
631
  Write only what the speaker would say out loud."""
632
 
633
  user_message = f"""Presentation topic: {topic}
 
634
  Conclusion phrase on screen: {conclusion_phrase}
635
 
636
- Create natural closing speaker notes that wrap up the presentation effectively."""
637
 
638
  payload = {
639
  "model": "dep89a2fld32mcm",
@@ -669,36 +579,44 @@ Create natural closing speaker notes that wrap up the presentation effectively."
669
  print(f"[마무리 λ…ΈνŠΈ] 였λ₯˜: {str(e)}")
670
  return "였늘 λ°œν‘œλ₯Ό λ§ˆλ¬΄λ¦¬ν•˜λ©° κ°μ‚¬μ˜ 말씀을 λ“œλ¦½λ‹ˆλ‹€. 질문이 μžˆμœΌμ‹œλ©΄ νŽΈν•˜κ²Œ 말씀해 μ£Όμ„Έμš”."
671
 
672
- def generate_conclusion_phrase(topic: str) -> str:
673
  """ν”„λ ˆμ  ν…Œμ΄μ…˜μ˜ 핡심을 담은 κ²°λ‘  문ꡬ 생성"""
674
  print(f"[κ²°λ‘  문ꡬ] 생성 쀑...")
675
 
 
 
 
676
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
677
  headers = {
678
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
679
  "Content-Type": "application/json"
680
  }
681
 
682
- system_prompt = """You are a professional copywriter creating powerful closing statements for presentations.
 
 
 
683
 
684
  Create a concise, impactful closing phrase that:
685
  1. Captures the essence of the presentation topic
686
- 2. Is memorable and inspirational
687
- 3. Maximum 5-7 words
688
- 4. Uses powerful, action-oriented language
689
- 5. Leaves a lasting impression
 
690
 
691
- Examples:
692
- - "Innovation Starts Today"
693
- - "Together We Transform"
694
- - "Future Begins Now"
695
- - "Excellence Through Innovation"
696
 
697
  Output only the phrase, no explanation."""
698
 
699
  user_message = f"""Presentation topic: {topic}
 
700
 
701
- Create a powerful closing phrase that encapsulates the main message."""
702
 
703
  payload = {
704
  "model": "dep89a2fld32mcm",
@@ -734,9 +652,13 @@ Create a powerful closing phrase that encapsulates the main message."""
734
  print(f"[κ²°λ‘  문ꡬ] 였λ₯˜: {str(e)}")
735
  return "ν•¨κ»˜ λ§Œλ“œλŠ” 미래"
736
 
737
- def generate_slide_content(topic: str, slide_title: str, slide_context: str, uploaded_content: str = None, web_search_results: List[Dict] = None) -> Dict[str, str]:
 
738
  """각 μŠ¬λΌμ΄λ“œμ˜ ν…μŠ€νŠΈ λ‚΄μš© 생성"""
739
- print(f"[μŠ¬λΌμ΄λ“œ λ‚΄μš©] {slide_title} ν…μŠ€νŠΈ 생성 쀑...")
 
 
 
740
 
741
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
742
  headers = {
@@ -744,44 +666,46 @@ def generate_slide_content(topic: str, slide_title: str, slide_context: str, upl
744
  "Content-Type": "application/json"
745
  }
746
 
747
- system_prompt = """You are a professional presentation content writer specializing in creating concise, impactful slide content.
 
 
 
 
748
 
749
- Your task is to create:
750
- 1. A compelling subtitle (max 10 words)
751
- 2. Exactly 5 bullet points with emojis
752
  3. Each bullet point should be 8-12 words
753
  4. Use noun-ending style (λͺ…μ‚¬ν˜• μ’…κ²°) for Korean or concise fragments for English
 
754
 
755
  IMPORTANT Style Guidelines:
756
- - End sentences with nouns or concise phrases (예: "μ „λž΅μ  ν™•λŒ€", "핡심 과제", "μ£Όμš” λͺ©ν‘œ")
757
  - Avoid long verb endings like "μž…λ‹ˆλ‹€", "μŠ΅λ‹ˆλ‹€"
758
  - Use short endings like "μž„", "함" or noun forms
759
- - Start each bullet with a relevant emoji that represents the content
760
  - Be extremely concise and impactful
761
 
762
- Emoji Guidelines:
763
- - Use professional emojis that match the content
764
- - Common emojis:
765
- πŸ“Š (data/analysis), 🎯 (goals/targets), πŸ’‘ (innovation/ideas),
766
- πŸš€ (growth/launch), ⚑ (speed/efficiency), πŸ” (research/analysis),
767
- πŸ“ˆ (increase/growth), πŸ’° (finance/revenue), πŸ† (achievement/success),
768
- πŸ”§ (tools/solutions), 🌐 (global/network), πŸ” (security/safety),
769
- ⭐ (excellence/quality), 🎨 (creative/design), πŸ“± (mobile/tech),
770
- 🀝 (partnership/collaboration), πŸ“ (location/focus), πŸŽ–οΈ (strategy),
771
- πŸ—οΈ (building/development), 🌱 (sustainability/growth)
772
- - Each bullet should have a different, relevant emoji
773
 
774
  Output format (EXACTLY FOLLOW THIS FORMAT):
775
  Subtitle: [subtitle here]
776
- - 🎯 [Point 1 - noun ending or fragment]
777
- - πŸ“Š [Point 2 - noun ending or fragment]
778
- - πŸ’‘ [Point 3 - noun ending or fragment]
779
- - πŸš€ [Point 4 - noun ending or fragment]
780
- - ⚑ [Point 5 - noun ending or fragment]"""
781
 
782
  user_message = f"""Topic: {topic}
783
  Slide Title: {slide_title}
784
- Context: {slide_context}"""
 
785
 
786
  # μ—…λ‘œλ“œλœ μ½˜ν…μΈ κ°€ 있으면 μΆ”κ°€
787
  if uploaded_content:
@@ -794,7 +718,7 @@ Context: {slide_context}"""
794
  search_context += f"- {result['title']}: {result['description']}\n"
795
  user_message += search_context
796
 
797
- user_message += "\n\nCreate compelling content for this presentation slide. Remember to use emojis and concise noun-ending style."
798
 
799
  payload = {
800
  "model": "dep89a2fld32mcm",
@@ -866,7 +790,7 @@ Context: {slide_context}"""
866
  clean_point = point.replace('β€’', '').strip()
867
 
868
  # 이λͺ¨μ§€μ™€ ν…μŠ€νŠΈ 뢄리 (첫 번째 곡백을 κΈ°μ€€μœΌλ‘œ)
869
- if len(clean_point) > 0 and clean_point[0] in 'πŸŽ―πŸ“ŠπŸ’‘πŸš€βš‘πŸ”πŸ“ˆπŸ’°πŸ†πŸ”§πŸŒπŸ”β­πŸŽ¨πŸ“±πŸ€πŸ“πŸŽ–οΈπŸ—οΈπŸŒ±':
870
  # 이λͺ¨μ§€κ°€ μžˆλŠ” 경우
871
  emoji = clean_point[0]
872
  text = clean_point[1:].strip()
@@ -1250,7 +1174,7 @@ def generate_image(prompt: str, seed: int = 10, slide_info: str = "") -> Tuple[I
1250
  return None, error_msg
1251
 
1252
  def create_slide_preview_html(slide_data: Dict) -> str:
1253
- """16:9 λΉ„μœ¨μ˜ μŠ¬λΌμ΄λ“œ 프리뷰 HTML 생성 (λ‘₯κ·Ό λͺ¨μ„œλ¦¬μ™€ μž…μ²΄κ°)"""
1254
 
1255
  # 이미지λ₯Ό base64둜 인코딩
1256
  img_base64 = ""
@@ -1411,13 +1335,10 @@ def create_slide_preview_html(slide_data: Dict) -> str:
1411
  <!-- 이미지 μ˜μ—­ (우츑) -->
1412
  <div class="image-area" style="
1413
  flex: 1;
1414
- background: rgba(248, 249, 250, 0.9);
1415
- border-radius: 12px;
1416
  display: flex;
1417
  align-items: center;
1418
  justify-content: center;
1419
  padding: 20px;
1420
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
1421
  ">
1422
  """
1423
 
@@ -1454,20 +1375,6 @@ def create_slide_preview_html(slide_data: Dict) -> str:
1454
 
1455
  return html
1456
 
1457
- def create_final_pptx_with_edits(results: List[Dict], topic: str, template_name: str,
1458
- theme_name: str, edited_data: Dict = None) -> str:
1459
- """νŽΈμ§‘λœ λ‚΄μš©μ„ λ°˜μ˜ν•œ μ΅œμ’… PPTX 파일 생성"""
1460
- print("[PPTX] νŽΈμ§‘λœ λ‚΄μš©μœΌλ‘œ μ΅œμ’… 파일 생성 쀑...")
1461
-
1462
- # νŽΈμ§‘λœ 데이터가 있으면 결과에 반영
1463
- if edited_data:
1464
- for slide_index, edits in edited_data.items():
1465
- if slide_index < len(results):
1466
- results[slide_index]["slide_data"].update(edits)
1467
-
1468
- # κΈ°μ‘΄ create_pptx_file ν•¨μˆ˜ 호좜
1469
- return create_pptx_file(results, topic, template_name, theme_name)
1470
-
1471
  def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_name: str = "λ―Έλ‹ˆλ©€ 라이트") -> str:
1472
  """μƒμ„±λœ κ²°κ³Όλ₯Ό PPTX 파일둜 λ³€ν™˜ (λ°œν‘œμž λ…ΈνŠΈ 포함)"""
1473
  print(f"[PPTX] 파일 생성 μ‹œμž‘... ν…Œλ§ˆ: {theme_name}")
@@ -1513,7 +1420,7 @@ def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_
1513
  except Exception as e:
1514
  print(f"[PPTX] ν‘œμ§€ 이미지 μΆ”κ°€ μ‹€νŒ¨: {str(e)}")
1515
 
1516
- # 제λͺ© λ°°κ²½ λ°•μŠ€ (반투λͺ… - 더 밝고 투λͺ…ν•˜κ²Œ)
1517
  title_bg = slide.shapes.add_shape(
1518
  MSO_SHAPE.ROUNDED_RECTANGLE,
1519
  Inches(2), Inches(2.8),
@@ -1521,16 +1428,16 @@ def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_
1521
  )
1522
  title_bg.fill.solid()
1523
  title_bg.fill.fore_color.rgb = RGBColor(255, 255, 255) # 흰색 λ°°κ²½
1524
- title_bg.fill.transparency = 0.2 # 20% 투λͺ…도 (80% 뢈투λͺ…)
1525
  title_bg.line.fill.background()
1526
 
1527
- # 그림자 효과 μΆ”κ°€ (더 κ°•ν•˜κ²Œ)
1528
  shadow = title_bg.shadow
1529
  shadow.visible = True
1530
- shadow.distance = Pt(8)
1531
- shadow.size = 120
1532
- shadow.blur_radius = Pt(15)
1533
- shadow.transparency = 0.3
1534
  shadow.angle = 45
1535
 
1536
  # 제λͺ© ν…μŠ€νŠΈ μΆ”κ°€
@@ -1580,7 +1487,7 @@ def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_
1580
  except Exception as e:
1581
  print(f"[PPTX] Thank You 이미지 μΆ”κ°€ μ‹€νŒ¨: {str(e)}")
1582
 
1583
- # Thank You λ°°κ²½ λ°•μŠ€ (반투λͺ… - 더 밝고 투λͺ…ν•˜κ²Œ)
1584
  thanks_bg = slide.shapes.add_shape(
1585
  MSO_SHAPE.ROUNDED_RECTANGLE,
1586
  Inches(2), Inches(3.5),
@@ -1588,16 +1495,16 @@ def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_
1588
  )
1589
  thanks_bg.fill.solid()
1590
  thanks_bg.fill.fore_color.rgb = RGBColor(255, 255, 255) # 흰색 λ°°κ²½
1591
- thanks_bg.fill.transparency = 0.2 # 20% 투λͺ…도
1592
  thanks_bg.line.fill.background()
1593
 
1594
- # 그림자 효과 μΆ”κ°€ (더 κ°•ν•˜κ²Œ)
1595
  shadow = thanks_bg.shadow
1596
  shadow.visible = True
1597
- shadow.distance = Pt(8)
1598
- shadow.size = 120
1599
- shadow.blur_radius = Pt(15)
1600
- shadow.transparency = 0.3
1601
  shadow.angle = 45
1602
 
1603
  # Thank You ν…μŠ€νŠΈ (κ²°λ‘  문ꡬ)
@@ -1711,28 +1618,7 @@ def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_
1711
  # 뢈릿 색상
1712
  p.font.color.rgb = theme["text_color"]
1713
 
1714
- # 우츑 이미지 μ˜μ—­ λ°°κ²½ λ°•μŠ€
1715
- img_box_bg = slide.shapes.add_shape(
1716
- MSO_SHAPE.ROUNDED_RECTANGLE,
1717
- Inches(8.3), Inches(1.4),
1718
- Inches(7.4), Inches(6.8)
1719
- )
1720
- img_box_bg.fill.solid()
1721
- img_box_bg.fill.fore_color.rgb = RGBColor(248, 249, 250)
1722
- img_box_bg.fill.transparency = 0.1
1723
-
1724
- if theme["shadow"]:
1725
- shadow = img_box_bg.shadow
1726
- shadow.visible = True
1727
- shadow.distance = Pt(5)
1728
- shadow.size = 100
1729
- shadow.blur_radius = Pt(10)
1730
- shadow.transparency = 0.7
1731
- shadow.angle = 45
1732
-
1733
- img_box_bg.line.fill.background()
1734
-
1735
- # 우츑 이미지 μΆ”κ°€
1736
  if slide_data.get('image'):
1737
  try:
1738
  img_buffer = BytesIO()
@@ -1808,331 +1694,31 @@ def generate_dynamic_slides(topic: str, template: Dict, slide_count: int) -> Lis
1808
 
1809
  return slides
1810
 
1811
- def create_editable_slide_interface(slide_data: Dict, slide_index: int, topic: str) -> str:
1812
- """νŽΈμ§‘ κ°€λŠ₯ν•œ μŠ¬λΌμ΄λ“œ μΈν„°νŽ˜μ΄μŠ€ 생성"""
1813
-
1814
- # 이미지λ₯Ό base64둜 인코딩
1815
- img_base64 = ""
1816
- if slide_data.get("image"):
1817
- buffered = BytesIO()
1818
- slide_data["image"].save(buffered, format="PNG")
1819
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
1820
-
1821
- # ν…μŠ€νŠΈ λ‚΄μš© κ°€μ Έμ˜€κΈ°
1822
- subtitle = slide_data.get("subtitle", "")
1823
- bullet_points = slide_data.get("bullet_points", [])
1824
-
1825
- # ν‘œμ§€μ™€ Thank YouλŠ” νŽΈμ§‘ λΆˆκ°€
1826
- if slide_data.get('title') in ['ν‘œμ§€', 'Thank You']:
1827
- return create_slide_preview_html(slide_data)
1828
-
1829
- # HTML 생성
1830
- html = f"""
1831
- <div class="slide-container" id="slide_container_{slide_index}" style="
1832
- width: 100%;
1833
- max-width: 1200px;
1834
- margin: 20px auto;
1835
- background: white;
1836
- border-radius: 12px;
1837
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
1838
- overflow: hidden;
1839
- ">
1840
- <div class="slide-header" style="
1841
- background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
1842
- color: white;
1843
- padding: 15px 30px;
1844
- font-size: 18px;
1845
- font-weight: bold;
1846
- display: flex;
1847
- justify-content: space-between;
1848
- align-items: center;
1849
- ">
1850
- <span>μŠ¬λΌμ΄λ“œ {slide_data.get('slide_number', '')}: {slide_data.get('title', '')}</span>
1851
- <button id="edit_btn_{slide_index}" style="
1852
- background: #3498db;
1853
- color: white;
1854
- border: none;
1855
- padding: 5px 15px;
1856
- border-radius: 5px;
1857
- cursor: pointer;
1858
- font-size: 14px;
1859
- ">✏️ νŽΈμ§‘</button>
1860
- </div>
1861
-
1862
- <div class="slide-content" style="
1863
- display: flex;
1864
- min-height: 400px;
1865
- background: #fafbfc;
1866
- position: relative;
1867
- ">
1868
- <div class="slide-inner" style="
1869
- width: 100%;
1870
- display: flex;
1871
- padding: 20px;
1872
- gap: 20px;
1873
- ">
1874
- <!-- ν…μŠ€νŠΈ μ˜μ—­ (쒌츑) -->
1875
- <div class="text-area" style="
1876
- flex: 1;
1877
- padding: 30px;
1878
- background: rgba(255, 255, 255, 0.95);
1879
- border-radius: 12px;
1880
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);
1881
- ">
1882
- <div id="view_mode_{slide_index}" style="display: block;">
1883
- <h2 style="
1884
- color: #212529;
1885
- font-size: 28px;
1886
- margin-bottom: 30px;
1887
- font-weight: 600;
1888
- ">{subtitle}</h2>
1889
-
1890
- <ul style="
1891
- list-style: none;
1892
- padding: 0;
1893
- margin: 0;
1894
- ">
1895
- """
1896
-
1897
- for point in bullet_points:
1898
- clean_point = point.replace('β€’', '').strip()
1899
- html += f"""
1900
- <li style="
1901
- margin-bottom: 16px;
1902
- padding-left: 28px;
1903
- position: relative;
1904
- color: #495057;
1905
- font-size: 16px;
1906
- line-height: 1.6;
1907
- ">
1908
- <span style="
1909
- position: absolute;
1910
- left: 0;
1911
- color: #007bff;
1912
- font-size: 18px;
1913
- ">β€’</span>
1914
- {clean_point}
1915
- </li>
1916
- """
1917
-
1918
- html += f"""
1919
- </ul>
1920
- </div>
1921
-
1922
- <!-- νŽΈμ§‘ λͺ¨λ“œ -->
1923
- <div id="edit_mode_{slide_index}" style="display: none;">
1924
- <input type="text" id="subtitle_{slide_index}" value="{subtitle}" style="
1925
- width: 100%;
1926
- font-size: 24px;
1927
- font-weight: 600;
1928
- margin-bottom: 20px;
1929
- padding: 10px;
1930
- border: 2px solid #e9ecef;
1931
- border-radius: 5px;
1932
- " placeholder="μ†Œμ œλͺ© μž…λ ₯">
1933
-
1934
- <div style="margin-bottom: 20px;">
1935
- """
1936
-
1937
- for i, point in enumerate(bullet_points):
1938
- # 이λͺ¨μ§€λ₯Ό ν¬ν•¨ν•œ 전체 ν…μŠ€νŠΈ (β€’ 제거)
1939
- clean_point = point.replace('β€’', '').strip()
1940
- html += f"""
1941
- <input type="text" id="bullet_{slide_index}_{i}" value="{clean_point}" style="
1942
- width: 100%;
1943
- margin-bottom: 10px;
1944
- padding: 8px 8px 8px 25px;
1945
- border: 1px solid #e9ecef;
1946
- border-radius: 5px;
1947
- font-size: 16px;
1948
- " placeholder="예: 🎯 포인트 {i+1}">
1949
- """
1950
-
1951
- html += f"""
1952
- </div>
1953
-
1954
- <div style="
1955
- background: #f3e5f5;
1956
- border-radius: 8px;
1957
- padding: 15px;
1958
- margin-top: 20px;
1959
- ">
1960
- <h4 style="margin-bottom: 10px; color: #7b1fa2;">πŸ€– AI둜 λ‹€μ‹œ μž‘μ„±ν•˜κΈ°</h4>
1961
- <textarea id="instruction_{slide_index}" style="
1962
- width: 100%;
1963
- height: 60px;
1964
- padding: 10px;
1965
- border: 2px solid #e1bee7;
1966
- border-radius: 5px;
1967
- font-size: 14px;
1968
- resize: vertical;
1969
- background: white;
1970
- " placeholder="예: 더 μ „λ¬Έμ μœΌλ‘œ, μˆ«μžμ™€ 톡계 포함, 더 κ°„λ‹¨ν•˜κ²Œ, μž„νŒ©νŠΈ 있게"></textarea>
1971
- <button id="ai_btn_{slide_index}" style="
1972
- background: #9b59b6;
1973
- color: white;
1974
- border: none;
1975
- padding: 8px 20px;
1976
- border-radius: 5px;
1977
- cursor: pointer;
1978
- margin-top: 10px;
1979
- font-size: 14px;
1980
- ">πŸ”„ AI μƒˆλ‘œ μž‘μ„±</button>
1981
- </div>
1982
-
1983
- <div style="margin-top: 20px;">
1984
- <button id="save_btn_{slide_index}" style="
1985
- background: #27ae60;
1986
- color: white;
1987
- border: none;
1988
- padding: 8px 20px;
1989
- border-radius: 5px;
1990
- cursor: pointer;
1991
- margin-right: 10px;
1992
- ">μ €μž₯</button>
1993
- <button id="cancel_btn_{slide_index}" style="
1994
- background: #95a5a6;
1995
- color: white;
1996
- border: none;
1997
- padding: 8px 20px;
1998
- border-radius: 5px;
1999
- cursor: pointer;
2000
- ">μ·¨μ†Œ</button>
2001
- </div>
2002
- </div>
2003
- </div>
2004
-
2005
- <!-- 이미지 μ˜μ—­ (우츑) -->
2006
- <div class="image-area" style="
2007
- flex: 1;
2008
- background: rgba(248, 249, 250, 0.9);
2009
- border-radius: 12px;
2010
- display: flex;
2011
- align-items: center;
2012
- justify-content: center;
2013
- padding: 20px;
2014
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
2015
- ">
2016
- """
2017
-
2018
- if img_base64:
2019
- html += f"""
2020
- <img src="data:image/png;base64,{img_base64}" style="
2021
- max-width: 100%;
2022
- max-height: 100%;
2023
- object-fit: contain;
2024
- border-radius: 8px;
2025
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2026
- " alt="Slide Image">
2027
- """
2028
-
2029
- html += f"""
2030
- </div>
2031
- </div>
2032
- </div>
2033
- </div>
2034
-
2035
- <script>
2036
- // μ¦‰μ‹œ μ‹€ν–‰ ν•¨μˆ˜λ‘œ μŠ€μ½”ν”„ 뢄리
2037
- (function() {{
2038
- const slideIndex = {slide_index};
2039
-
2040
- // DOM이 μ€€λΉ„λ˜λ©΄ μ‹€ν–‰
2041
- setTimeout(function() {{
2042
- const viewMode = document.getElementById('view_mode_' + slideIndex);
2043
- const editMode = document.getElementById('edit_mode_' + slideIndex);
2044
- const editBtn = document.getElementById('edit_btn_' + slideIndex);
2045
- const saveBtn = document.getElementById('save_btn_' + slideIndex);
2046
- const cancelBtn = document.getElementById('cancel_btn_' + slideIndex);
2047
- const aiBtn = document.getElementById('ai_btn_' + slideIndex);
2048
-
2049
- if (editBtn) {{
2050
- editBtn.onclick = function(e) {{
2051
- e.preventDefault();
2052
- e.stopPropagation();
2053
- viewMode.style.display = 'none';
2054
- editMode.style.display = 'block';
2055
- return false;
2056
- }};
2057
- }}
2058
-
2059
- if (saveBtn) {{
2060
- saveBtn.onclick = function(e) {{
2061
- e.preventDefault();
2062
- e.stopPropagation();
2063
- const subtitle = document.getElementById('subtitle_' + slideIndex).value;
2064
- const bullets = [];
2065
- for (let i = 0; i < 5; i++) {{
2066
- const bullet = document.getElementById('bullet_' + slideIndex + '_' + i);
2067
- if (bullet && bullet.value) {{
2068
- let bulletText = bullet.value.trim();
2069
- if (!bulletText.startsWith('β€’')) {{
2070
- bulletText = 'β€’ ' + bulletText;
2071
- }}
2072
- bullets.push(bulletText);
2073
- }}
2074
- }}
2075
-
2076
- window.savedSlideData = window.savedSlideData || {{}};
2077
- window.savedSlideData[slideIndex] = {{
2078
- subtitle: subtitle,
2079
- bullet_points: bullets
2080
- }};
2081
-
2082
- // λ·° λͺ¨λ“œ μ—…λ°μ΄νŠΈ
2083
- const h2 = viewMode.querySelector('h2');
2084
- if (h2) h2.textContent = subtitle;
2085
-
2086
- const ul = viewMode.querySelector('ul');
2087
- if (ul) {{
2088
- ul.innerHTML = bullets.map(b => `
2089
- <li style="margin-bottom: 16px; padding-left: 28px; position: relative; color: #495057; font-size: 16px; line-height: 1.6;">
2090
- <span style="position: absolute; left: 0; color: #007bff; font-size: 18px;">β€’</span>
2091
- ${{b.replace('β€’', '').trim()}}
2092
- </li>
2093
- `).join('');
2094
- }}
2095
-
2096
- viewMode.style.display = 'block';
2097
- editMode.style.display = 'none';
2098
- return false;
2099
- }};
2100
- }}
2101
-
2102
- if (cancelBtn) {{
2103
- cancelBtn.onclick = function(e) {{
2104
- e.preventDefault();
2105
- e.stopPropagation();
2106
- viewMode.style.display = 'block';
2107
- editMode.style.display = 'none';
2108
- return false;
2109
- }};
2110
- }}
2111
-
2112
- if (aiBtn) {{
2113
- aiBtn.onclick = function(e) {{
2114
- e.preventDefault();
2115
- e.stopPropagation();
2116
- const instruction = document.getElementById('instruction_' + slideIndex).value;
2117
- if (instruction) {{
2118
- window.aiRegenerateRequest = {{
2119
- slideIndex: slideIndex,
2120
- instruction: instruction,
2121
- topic: "{topic}"
2122
- }};
2123
- alert('AI μž¬μƒμ„±μ΄ μš”μ²­λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ‹€μ œ κ΅¬ν˜„μ—μ„œλŠ” μ„œλ²„μ™€ ν†΅μ‹ ν•©λ‹ˆλ‹€.');
2124
- }}
2125
- return false;
2126
- }};
2127
- }}
2128
- }}, 100);
2129
- }})();
2130
- </script>
2131
- """
2132
-
2133
- return html
2134
 
2135
- def generate_ppt_with_content(topic: str, template_name: str, custom_slides: List[Dict],
2136
  slide_count: int, seed: int, uploaded_file,
2137
  use_web_search: bool, theme_name: str = "λ―Έλ‹ˆλ©€ 라이트",
2138
  progress=gr.Progress()):
@@ -2171,6 +1757,7 @@ def generate_ppt_with_content(topic: str, template_name: str, custom_slides: Lis
2171
  print(f"\n[PPT 생성] μ‹œμž‘ - 총 {total_slides}개 μŠ¬λΌμ΄λ“œ (ν‘œμ§€ + λ³Έλ¬Έ {slide_count} + Thank You)")
2172
  print(f"[PPT 생성] 주제: {topic}")
2173
  print(f"[PPT 생성] ν…œν”Œλ¦Ώ: {template_name}")
 
2174
  print(f"[PPT 생성] λ””μžμΈ ν…Œλ§ˆ: {theme_name}")
2175
  print(f"[PPT 생성] μ›Ή 검색: {'μ‚¬μš©' if use_web_search else 'λ―Έμ‚¬μš©'}")
2176
 
@@ -2203,20 +1790,20 @@ def generate_ppt_with_content(topic: str, template_name: str, custom_slides: Lis
2203
 
2204
  # Thank You μŠ¬λΌμ΄λ“œλŠ” κ²°λ‘  문ꡬ 생성
2205
  if slide['title'] == 'Thank You':
2206
- conclusion_phrase = generate_conclusion_phrase(topic)
2207
  content = {
2208
  "subtitle": conclusion_phrase,
2209
  "bullet_points": []
2210
  }
2211
  # Thank You μŠ¬λΌμ΄λ“œμš© νŠΉλ³„ λ…ΈνŠΈ
2212
- speaker_notes = generate_closing_notes(topic, conclusion_phrase)
2213
  else:
2214
  content = generate_slide_content(
2215
- topic, slide['title'], slide_context,
2216
  uploaded_content, web_search_results
2217
  )
2218
  # 일반 μŠ¬λΌμ΄λ“œ λ°œν‘œμž λ…ΈνŠΈ
2219
- speaker_notes = generate_presentation_notes(topic, slide['title'], content)
2220
 
2221
  # ν”„λ‘¬ν”„νŠΈ 생성 및 이미지 생성
2222
  style_key = slide["style"]
@@ -2243,11 +1830,8 @@ def generate_ppt_with_content(topic: str, template_name: str, custom_slides: Lis
2243
  "topic": topic # ν‘œμ§€μš©
2244
  }
2245
 
2246
- # 프리뷰 HTML 생성 (νŽΈμ§‘ κ°€λŠ₯ν•œ μΈν„°νŽ˜μ΄μŠ€)
2247
- if slide_data.get('title') not in ['ν‘œμ§€', 'Thank You']:
2248
- preview_html += create_editable_slide_interface(slide_data, i, topic)
2249
- else:
2250
- preview_html += create_slide_preview_html(slide_data)
2251
 
2252
  # ν˜„μž¬κΉŒμ§€μ˜ μƒνƒœ μ—…λ°μ΄νŠΈ
2253
  yield preview_html + "</div>", f"### πŸ”„ {slide_info} 생성 쀑...", None
@@ -2265,44 +1849,12 @@ def generate_ppt_with_content(topic: str, template_name: str, custom_slides: Lis
2265
  except Exception as e:
2266
  print(f"[PPTX] 파일 생성 였λ₯˜: {str(e)}")
2267
 
2268
- # μ΅œμ’… 결과에 νŽΈμ§‘ κ°€λŠ₯ν•œ μŠ¬λΌμ΄λ“œ 데이터 μ €μž₯
2269
- preview_html += """
2270
- <div style="margin: 40px auto; max-width: 1200px; text-align: center; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
2271
- <h3 style="color: white; margin-bottom: 20px;">✨ λͺ¨λ“  νŽΈμ§‘μ΄ μ™„λ£Œλ˜μ—ˆλ‚˜μš”?</h3>
2272
- <button id="finalDownloadBtn" onclick="prepareFinalDownload()" style="
2273
- background: white;
2274
- color: #667eea;
2275
- border: none;
2276
- padding: 18px 50px;
2277
- border-radius: 50px;
2278
- cursor: pointer;
2279
- font-size: 20px;
2280
- font-weight: bold;
2281
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
2282
- transition: all 0.3s ease;
2283
- " onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
2284
- πŸ’Ύ νŽΈμ§‘ λ‚΄μš© λ°˜μ˜ν•˜μ—¬ μ΅œμ’… λ‹€μš΄λ‘œλ“œ
2285
- </button>
2286
- <p style="color: white; margin-top: 15px; font-size: 14px;">
2287
- πŸ’‘ νŽΈμ§‘ν•œ λ‚΄μš©μ΄ μ΅œμ’… PPT에 λ°˜μ˜λ©λ‹ˆλ‹€
2288
- </p>
2289
- </div>
2290
-
2291
- <script>
2292
- function prepareFinalDownload() {
2293
- // νŽΈμ§‘λœ λͺ¨λ“  데이터 μˆ˜μ§‘
2294
- alert('νŽΈμ§‘λœ λ‚΄μš©μ„ λ°˜μ˜ν•œ μ΅œμ’… PPTλ₯Ό λ‹€μš΄λ‘œλ“œν•©λ‹ˆλ‹€.');
2295
- // μ‹€μ œ κ΅¬ν˜„μ—μ„œλŠ” Gradio 이벀트둜 처리
2296
- }
2297
- </script>
2298
- """
2299
-
2300
  preview_html += "</div>"
2301
  progress(1.0, "μ™„λ£Œ!")
2302
  successful = sum(1 for r in results if r["success"])
2303
  final_status = f"### πŸŽ‰ 생성 μ™„λ£Œ! 총 {total_slides}개 μŠ¬λΌμ΄λ“œ 쀑 {successful}개 성곡"
2304
 
2305
- # νŽΈμ§‘ κ°€λŠ₯ν•œ μŠ¬λΌμ΄λ“œ 데이터λ₯Ό μ „μ—­ λ³€μˆ˜λ‘œ μ €μž₯
2306
  global current_slides_data, current_template, current_theme
2307
  current_slides_data = results
2308
  current_template = template_name
@@ -2310,71 +1862,32 @@ def generate_ppt_with_content(topic: str, template_name: str, custom_slides: Lis
2310
 
2311
  if pptx_path:
2312
  final_status += f"\n\n### πŸ“₯ PPTX 파일이 μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!"
2313
- final_status += f"\n\nπŸ’‘ **각 μŠ¬λΌμ΄λ“œλ₯Ό νŽΈμ§‘ν•œ ν›„ 'νŽΈμ§‘ μ™„λ£Œ ν›„ μ΅œμ’… λ‹€μš΄λ‘œλ“œ' λ²„νŠΌμ„ ν΄λ¦­ν•˜μ„Έμš”**"
2314
- final_status += f"\n\n🎀 **λ°œν‘œμž λ…ΈνŠΈκ°€ 각 μŠ¬λΌμ΄λ“œμ— ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€!**"
2315
  if PROCESS_FLOW_AVAILABLE:
2316
  final_status += f"\n\nπŸ”§ **ν”„λ‘œμ„ΈμŠ€ ν”Œλ‘œμš° λ‹€μ΄μ–΄κ·Έλž¨μ΄ μžλ™μœΌλ‘œ μƒμ„±λ©λ‹ˆλ‹€ (ν•œκΈ€ 지원)**"
2317
 
2318
  yield preview_html, final_status, pptx_path
2319
 
2320
- def create_custom_slides_ui():
2321
- """μ‚¬μš©μž μ •μ˜ μŠ¬λΌμ΄λ“œ ꡬ성 UI (3-20μž₯)"""
2322
- slides = []
2323
- for i in range(20): # μ΅œλŒ€ 20μž₯
2324
- with gr.Row(visible=(i < 3)): # κΈ°λ³Έ 3μž₯만 ν‘œμ‹œ
2325
- with gr.Column(scale=2):
2326
- title = gr.Textbox(
2327
- label=f"μŠ¬λΌμ΄λ“œ {i+1} 제λͺ©",
2328
- placeholder="예: ν˜„ν™© 뢄석, μ†”λ£¨μ…˜, λ‘œλ“œλ§΅...",
2329
- )
2330
- with gr.Column(scale=3):
2331
- style = gr.Dropdown(
2332
- choices=list(STYLE_TEMPLATES.keys()),
2333
- label=f"μŠ€νƒ€μΌ 선택",
2334
- value="Colorful Mind Map"
2335
- )
2336
- with gr.Column(scale=3):
2337
- hint = gr.Textbox(
2338
- label=f"ν”„λ‘¬ν”„νŠΈ 힌트",
2339
- placeholder="이 μŠ¬λΌμ΄λ“œμ—μ„œ ν‘œν˜„ν•˜κ³  싢은 λ‚΄μš©"
2340
- )
2341
- slides.append({"title": title, "style": style, "hint": hint, "row": gr.Row})
2342
- return slides
2343
-
2344
  # Gradio μΈν„°νŽ˜μ΄μŠ€ 생성
2345
  with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2346
  .preview-container { max-width: 1400px; margin: 0 auto; }
2347
  .slide-container { transition: all 0.3s ease; }
2348
  .slide-container:hover { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15) !important; }
2349
- input[type="text"], textarea { transition: all 0.3s ease; }
2350
- input[type="text"]:focus, textarea:focus { border-color: #3498db !important; outline: none; box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); }
2351
- button {
2352
- transition: all 0.3s ease;
2353
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2354
- cursor: pointer !important;
2355
- user-select: none;
2356
- -webkit-user-select: none;
2357
- }
2358
- button:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); opacity: 0.9; }
2359
- button:active { transform: translateY(0); }
2360
- .edit-mode { background: #f8f9fa; padding: 20px; border-radius: 8px; }
2361
- .ai-instruction { background: #f3e5f5; padding: 15px; border-radius: 8px; margin-top: 15px; }
2362
  """) as demo:
2363
  gr.Markdown("""
2364
  # 🎯 AI 기반 PPT 톡합 생성기 (κ°„κ²°ν•œ μŠ€νƒ€μΌ 버전)
2365
 
2366
- ### ν…μŠ€νŠΈμ™€ 이미지가 μ™„λ²½ν•˜κ²Œ μ‘°ν™”λœ ν”„λ ˆμ  ν…Œμ΄μ…˜μ„ μžλ™μœΌλ‘œ μƒμ„±ν•˜κ³  νŽΈμ§‘ ν›„ λ‹€μš΄λ‘œλ“œν•˜μ„Έμš”!
2367
 
2368
  #### πŸ†• μƒˆλ‘œμš΄ κΈ°λŠ₯:
 
2369
  - πŸ“ **κ°„κ²°ν•œ λͺ…μ‚¬ν˜• ν…μŠ€νŠΈ**: "~μž„", "~함" μŠ€νƒ€μΌμ˜ ν”„λ‘œνŽ˜μ…”λ„ν•œ 문체
2370
  - 🎨 **이λͺ¨μ§€ μžλ™ μΆ”κ°€**: 각 ν¬μΈνŠΈλ³„ μ μ ˆν•œ 이λͺ¨μ§€λ‘œ μ‹œκ°μ  ꡬ뢄
2371
- - ✏️ **μŠ¬λΌμ΄λ“œ ν…μŠ€νŠΈ νŽΈμ§‘**: μƒμ„±λœ 각 μŠ¬λΌμ΄λ“œμ˜ ν…μŠ€νŠΈλ₯Ό 직접 μˆ˜μ •
2372
- - πŸ€– **AI μž¬μž‘μ„±**: μ§€μ‹œμ‚¬ν•­μ„ μž…λ ₯ν•˜λ©΄ AIκ°€ ν…μŠ€νŠΈλ₯Ό λ‹€μ‹œ μž‘μ„±
2373
  - πŸ“‹ **예제 ν…œν”Œλ¦Ώ**: λ²„νŠΌ 클릭으둜 예제 주제 뢈러였기
2374
  - 🎨 **λ””μžμΈ ν…Œλ§ˆ**: 5κ°€μ§€ ���문적인 λ””μžμΈ ν…Œλ§ˆ 선택 κ°€λŠ₯
2375
- - πŸ’Ž **κ°œμ„ λœ ν‘œμ§€ λ””μžμΈ**: 반투λͺ… λ°°κ²½κ³Ό μ„ λͺ…ν•œ ν…μŠ€νŠΈ
2376
  - πŸ“Š **ν‘œμ§€μ™€ Thank You μŠ¬λΌμ΄λ“œ** μžλ™ μΆ”κ°€
2377
- - πŸ“ **λ°œν‘œμž λ…ΈνŠΈ** μžλ™ 생성 (ꡬ어체)
2378
  - πŸ“ **파일 μ—…λ‘œλ“œ** 지원 (PDF/CSV/TXT)
2379
  - πŸ” **μ›Ή 검색** κΈ°λŠ₯ (Brave Search)
2380
  - 🎚️ **μŠ¬λΌμ΄λ“œ 수 쑰절** (6-20μž₯)
@@ -2408,6 +1921,17 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2408
  lines=2
2409
  )
2410
 
 
 
 
 
 
 
 
 
 
 
 
2411
  # PPT ν…œν”Œλ¦Ώ 선택
2412
  template_select = gr.Dropdown(
2413
  choices=list(PPT_TEMPLATES.keys()),
@@ -2471,28 +1995,23 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2471
  gr.Markdown("""
2472
  ### πŸ”„ μž‘μ—… μˆœμ„œ:
2473
  1. **예제 뢈러였기** λ²„νŠΌμœΌλ‘œ μƒ˜ν”Œ 주제λ₯Ό λ‘œλ“œν•˜κ±°λ‚˜ 직접 μž…λ ₯
2474
- 2. **ν…œν”Œλ¦Ώκ³Ό ν…Œλ§ˆ 선택** ν›„ 생성 λ²„νŠΌ 클릭
2475
- 3. **각 μŠ¬λΌμ΄λ“œ νŽΈμ§‘**: ✏️ νŽΈμ§‘ λ²„νŠΌμœΌλ‘œ ν…μŠ€νŠΈ μˆ˜μ •
2476
- 4. **AI μž¬μž‘μ„±**: μ§€μ‹œμ‚¬ν•­ μž…λ ₯ ν›„ AI둜 λ‹€μ‹œ μž‘μ„±
2477
- 5. **μ΅œμ’… λ‹€μš΄λ‘œλ“œ**: νŽΈμ§‘ μ™„λ£Œ ν›„ λ‹€μš΄λ‘œλ“œ
 
 
 
 
 
 
 
2478
 
2479
  ### πŸ’‘ ν…μŠ€νŠΈ μŠ€νƒ€μΌ νŠΉμ§•:
2480
  - **κ°„κ²°ν•œ λͺ…μ‚¬ν˜• μ’…κ²°**: "~μž„", "~함" λ˜λŠ” λͺ…μ‚¬λ‘œ 끝남
2481
  - **이λͺ¨μ§€ ν™œμš©**: 각 포인트 μ•žμ— λ‚΄μš© κ΄€λ ¨ 이λͺ¨μ§€ μžλ™ μΆ”κ°€
2482
  - **8-12단어**: 각 뢈릿 ν¬μΈνŠΈλŠ” 맀우 κ°„κ²°ν•˜κ²Œ ꡬ성
2483
 
2484
- **μŠ€νƒ€μΌ μ˜ˆμ‹œ:**
2485
- - ❌ "μ‹œμž₯ μ μœ μœ¨μ„ ν™•λŒ€ν•˜κΈ° μœ„ν•œ μ „λž΅μ„ μˆ˜λ¦½ν•©λ‹ˆλ‹€"
2486
- - βœ… "🎯 μ‹œμž₯ 점유율 ν™•λŒ€ μ „λž΅ 수립"
2487
- - ❌ "AI κΈ°μˆ μ„ ν™œμš©ν•˜μ—¬ νš¨μœ¨μ„±μ„ ν–₯μƒμ‹œν‚΅λ‹ˆλ‹€"
2488
- - βœ… "πŸš€ AI 기술 ν™œμš©ν•œ νš¨μœ¨μ„± κ·ΉλŒ€ν™”"
2489
-
2490
- ### πŸ€– AI μž¬μž‘μ„± 팁:
2491
- - "더 κ°„λ‹¨ν•˜κ²Œ": ν•΅μ‹¬λ§Œ 남기고 μΆ•μ•½
2492
- - "숫자 포함": ꡬ체적 μˆ˜μΉ˜λ‚˜ 톡계 μΆ”κ°€
2493
- - "μ „λ¬Έμ μœΌλ‘œ": 업계 μ „λ¬Έ μš©μ–΄ μ‚¬μš©
2494
- - "μž„νŒ©νŠΈ 있게": 강쑰점과 핡심 μ„±κ³Ό 뢀각
2495
-
2496
  ### πŸ”§ ν”„λ‘œμ„ΈμŠ€ ν”Œλ‘œμš° λ‹€μ΄μ–΄κ·Έλž¨:
2497
  - **Business Workflow** λ˜λŠ” **Flowchart** μŠ€νƒ€μΌ 선택 μ‹œ μžλ™μœΌλ‘œ ν”„λ‘œμ„ΈμŠ€ ν”Œλ‘œμš° λ‹€μ΄μ–΄κ·Έλž¨ 생성
2498
  - ν•œκΈ€ ν…μŠ€νŠΈλ₯Ό μ™„λ²½ν•˜κ²Œ μ§€μ›ν•˜λŠ” λ‹€μ΄μ–΄κ·Έλž¨
@@ -2539,6 +2058,13 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2539
  example_topic = EXAMPLE_TOPICS.get(template_name, "")
2540
  return example_topic
2541
 
 
 
 
 
 
 
 
2542
  def update_theme_preview(theme_name):
2543
  """ν…Œλ§ˆ 미리보기 HTML 생성"""
2544
  theme = DESIGN_THEMES.get(theme_name, DESIGN_THEMES["λ―Έλ‹ˆλ©€ 라이트"])
@@ -2613,7 +2139,7 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2613
  ])
2614
  return updates
2615
 
2616
- def generate_ppt_handler(topic, template_name, theme_name, slide_count, seed, file_upload,
2617
  use_web_search, custom_slide_count, progress=gr.Progress(),
2618
  *custom_inputs):
2619
  if not topic.strip():
@@ -2638,7 +2164,7 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2638
 
2639
  # PPT 생성
2640
  for preview, status, pptx_file in generate_ppt_with_content(
2641
- topic, template_name, custom_slides, slide_count, seed,
2642
  file_upload, use_web_search, theme_name, progress
2643
  ):
2644
  yield preview, status, pptx_file
@@ -2650,6 +2176,12 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2650
  outputs=[topic_input]
2651
  )
2652
 
 
 
 
 
 
 
2653
  theme_select.change(
2654
  fn=update_theme_preview,
2655
  inputs=[theme_select],
@@ -2683,7 +2215,7 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2683
  generate_btn.click(
2684
  fn=generate_ppt_handler,
2685
  inputs=[
2686
- topic_input, template_select, theme_select, slide_count,
2687
  seed_input, file_upload, use_web_search, custom_slide_count
2688
  ] + all_custom_inputs,
2689
  outputs=[preview_output, status_output, download_file]
@@ -2692,11 +2224,12 @@ with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
2692
  # 초기 λ‘œλ“œμ‹œ ν…œν”Œλ¦Ώ/ν…Œλ§ˆ 정보 ν‘œμ‹œ
2693
  demo.load(
2694
  fn=lambda: (update_template_info(template_select.value, slide_count.value),
2695
- update_theme_preview(theme_select.value)),
 
2696
  inputs=[],
2697
- outputs=[template_info, theme_preview]
2698
  )
2699
 
2700
  # μ•± μ‹€ν–‰
2701
  if __name__ == "__main__":
2702
- demo.launch(ssr_mode=False)
 
35
  "μ „λž΅ 기획": "2025λ…„ κΈ€λ‘œλ²Œ μ‹œμž₯ μ§„μΆœ μ „λž΅ 수립"
36
  }
37
 
38
+ # μ˜€λ””μ–ΈμŠ€ νƒ€μž… μ •μ˜
39
+ AUDIENCE_TYPES = {
40
+ "κ²½μ˜μ§„/μž„μ›": {
41
+ "description": "C-level, μ˜μ‚¬κ²°μ •κΆŒμž",
42
+ "tone": "μ „λž΅μ , κ²°κ³Ό 쀑심, ROI κ°•μ‘°",
43
+ "focus": "λΉ„μ¦ˆλ‹ˆμŠ€ κ°€μΉ˜, 투자 수읡λ₯ , μ „λž΅μ  영ν–₯"
44
+ },
45
+ "투자자": {
46
+ "description": "VC, μ—”μ €νˆ¬μžμž, κΈ°κ΄€νˆ¬μžμž",
47
+ "tone": "수치 기반, μ„±μž₯ κ°€λŠ₯μ„±, μ‹œμž₯ 기회",
48
+ "focus": "μ‹œμž₯ 규λͺ¨, μ„±μž₯λ₯ , κ²½μŸμš°μœ„, Exit μ „λž΅"
49
+ },
50
+ "κΈ°μˆ νŒ€": {
51
+ "description": "개발자, μ—”μ§€λ‹ˆμ–΄, IT μ „λ¬Έκ°€",
52
+ "tone": "기술적, ꡬ체적, μ‹€μš©μ ",
53
+ "focus": "기술 μŠ€νƒ, μ•„ν‚€ν…μ²˜, κ΅¬ν˜„ 방법, μ„±λŠ₯"
54
+ },
55
+ "일반 직원": {
56
+ "description": "μ‹€λ¬΄μž, νŒ€μ›",
57
+ "tone": "μΉœκ·Όν•œ, 싀무적, ν˜‘μ—… 쀑심",
58
+ "focus": "μ‹€ν–‰ κ³„νš, μ—­ν• , ν”„λ‘œμ„ΈμŠ€, ν˜‘μ—… λ°©μ•ˆ"
59
+ },
60
+ "고객/νŒŒνŠΈλ„ˆ": {
61
+ "description": "B2B 고객, λΉ„μ¦ˆλ‹ˆμŠ€ νŒŒνŠΈλ„ˆ",
62
+ "tone": "신뒰감, 전문적, ν˜œνƒ 쀑심",
63
+ "focus": "고객 κ°€μΉ˜, ν˜œνƒ, 사둀, 지원 체계"
64
+ },
65
+ "일반 λŒ€μ€‘": {
66
+ "description": "B2C 고객, 일반 μ‚¬μš©μž",
67
+ "tone": "쉽고 μΉœκ·Όν•œ, μ΄ν•΄ν•˜κΈ° μ‰¬μš΄",
68
+ "focus": "μ‚¬μš© νŽΈμ˜μ„±, ν˜œνƒ, 가격, 차별점"
69
+ }
70
+ }
71
+
72
  # ν™˜κ²½ λ³€μˆ˜μ—μ„œ 토큰 κ°€μ Έμ˜€κΈ°
73
  REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
74
  FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN")
 
430
  except Exception as e:
431
  return f"파일 읽기 였λ₯˜: {str(e)}"
432
 
433
+ def generate_presentation_notes(topic: str, slide_title: str, content: Dict, audience_type: str) -> str:
434
  """각 μŠ¬λΌμ΄λ“œμ˜ λ°œν‘œμž λ…ΈνŠΈ 생성 (ꡬ어체)"""
435
  print(f"[λ°œν‘œμž λ…ΈνŠΈ] {slide_title} 생성 쀑...")
436
 
437
+ # μ˜€λ””μ–ΈμŠ€ 정보 κ°€μ Έμ˜€κΈ°
438
+ audience_info = AUDIENCE_TYPES.get(audience_type, AUDIENCE_TYPES["일반 직원"])
439
+
440
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
441
  headers = {
442
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
443
  "Content-Type": "application/json"
444
  }
445
 
446
+ system_prompt = f"""You are a professional presentation coach who creates natural, conversational speaker notes.
447
+
448
+ The audience for this presentation is: {audience_type} - {audience_info['description']}
449
+ Tone: {audience_info['tone']}
450
+ Focus areas: {audience_info['focus']}
451
 
452
  The slide content uses concise noun-ending style, but your speaker notes should be natural and conversational.
453
 
454
  Create speaker notes that:
455
+ 1. Sound natural and conversational, as if speaking to {audience_type}
456
+ 2. Expand on the concise bullet points with additional context relevant to this audience
457
+ 3. Include transitions and engagement phrases appropriate for {audience_type}
458
+ 4. Use a warm, professional tone suitable for {audience_type}
459
  5. Be 100-150 words long
460
  6. Include pauses and emphasis markers where appropriate
461
 
462
  Note: The bullet points may include emojis - incorporate their meaning into your speech.
463
 
464
  Format:
465
+ - Use conversational language appropriate for {audience_type}
466
  - Include transition phrases
467
+ - Add engagement questions or comments that resonate with this audience
468
  - Keep it professional but friendly"""
469
 
470
  bullet_text = "\n".join(content.get("bullet_points", []))
 
474
  Key Points (concise style with emojis):
475
  {bullet_text}
476
 
477
+ Create natural speaker notes that expand on these concise points for presenting this slide to {audience_type}."""
478
 
479
  payload = {
480
  "model": "dep89a2fld32mcm",
 
510
  print(f"[λ°œν‘œμž λ…ΈνŠΈ] 였λ₯˜: {str(e)}")
511
  return "이 μŠ¬λΌμ΄λ“œμ—μ„œλŠ” 핡심 λ‚΄μš©μ„ μ„€λͺ…ν•΄ μ£Όμ„Έμš”."
512
 
513
+ def generate_closing_notes(topic: str, conclusion_phrase: str, audience_type: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  """λ§ˆμ§€λ§‰ μŠ¬λΌμ΄λ“œμ˜ λ°œν‘œμž λ…ΈνŠΈ 생성"""
515
  print(f"[마무리 λ…ΈνŠΈ] 생성 쀑...")
516
 
517
+ # μ˜€λ””μ–ΈμŠ€ 정보 κ°€μ Έμ˜€κΈ°
518
+ audience_info = AUDIENCE_TYPES.get(audience_type, AUDIENCE_TYPES["일반 직원"])
519
+
520
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
521
  headers = {
522
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
523
  "Content-Type": "application/json"
524
  }
525
 
526
+ system_prompt = f"""You are a professional presentation coach creating closing speaker notes.
527
+
528
+ The audience for this presentation is: {audience_type} - {audience_info['description']}
529
+ Tone: {audience_info['tone']}
530
 
531
  Create natural closing remarks that:
532
+ 1. Thank the {audience_type} audience warmly and appropriately
533
+ 2. Briefly summarize the key message relevant to {audience_type}
534
  3. Reference the conclusion phrase naturally
535
+ 4. End with an invitation for questions or next steps appropriate for {audience_type}
536
  5. Be 80-100 words long
537
  6. Sound conversational and warm
538
  7. NO stage directions or parentheses - only spoken words
 
540
  Write only what the speaker would say out loud."""
541
 
542
  user_message = f"""Presentation topic: {topic}
543
+ Audience: {audience_type}
544
  Conclusion phrase on screen: {conclusion_phrase}
545
 
546
+ Create natural closing speaker notes that wrap up the presentation effectively for {audience_type}."""
547
 
548
  payload = {
549
  "model": "dep89a2fld32mcm",
 
579
  print(f"[마무리 λ…ΈνŠΈ] 였λ₯˜: {str(e)}")
580
  return "였늘 λ°œν‘œλ₯Ό λ§ˆλ¬΄λ¦¬ν•˜λ©° κ°μ‚¬μ˜ 말씀을 λ“œλ¦½λ‹ˆλ‹€. 질문이 μžˆμœΌμ‹œλ©΄ νŽΈν•˜κ²Œ 말씀해 μ£Όμ„Έμš”."
581
 
582
+ def generate_conclusion_phrase(topic: str, audience_type: str) -> str:
583
  """ν”„λ ˆμ  ν…Œμ΄μ…˜μ˜ 핡심을 담은 κ²°λ‘  문ꡬ 생성"""
584
  print(f"[κ²°λ‘  문ꡬ] 생성 쀑...")
585
 
586
+ # μ˜€λ””μ–ΈμŠ€ 정보 κ°€μ Έμ˜€κΈ°
587
+ audience_info = AUDIENCE_TYPES.get(audience_type, AUDIENCE_TYPES["일반 직원"])
588
+
589
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
590
  headers = {
591
  "Authorization": f"Bearer {FRIENDLI_TOKEN}",
592
  "Content-Type": "application/json"
593
  }
594
 
595
+ system_prompt = f"""You are a professional copywriter creating powerful closing statements for presentations.
596
+
597
+ The audience is: {audience_type} - {audience_info['description']}
598
+ Focus on: {audience_info['focus']}
599
 
600
  Create a concise, impactful closing phrase that:
601
  1. Captures the essence of the presentation topic
602
+ 2. Resonates with {audience_type}
603
+ 3. Is memorable and inspirational
604
+ 4. Maximum 5-7 words
605
+ 5. Uses powerful, action-oriented language appropriate for {audience_type}
606
+ 6. Leaves a lasting impression
607
 
608
+ Examples for different audiences:
609
+ - For executives: "Excellence Through Strategic Innovation"
610
+ - For investors: "Maximum Returns, Minimal Risk"
611
+ - For technical teams: "Code Today, Transform Tomorrow"
612
+ - For customers: "Your Success, Our Mission"
613
 
614
  Output only the phrase, no explanation."""
615
 
616
  user_message = f"""Presentation topic: {topic}
617
+ Target audience: {audience_type}
618
 
619
+ Create a powerful closing phrase that encapsulates the main message for {audience_type}."""
620
 
621
  payload = {
622
  "model": "dep89a2fld32mcm",
 
652
  print(f"[κ²°λ‘  문ꡬ] 였λ₯˜: {str(e)}")
653
  return "ν•¨κ»˜ λ§Œλ“œλŠ” 미래"
654
 
655
+ def generate_slide_content(topic: str, slide_title: str, slide_context: str, audience_type: str,
656
+ uploaded_content: str = None, web_search_results: List[Dict] = None) -> Dict[str, str]:
657
  """각 μŠ¬λΌμ΄λ“œμ˜ ν…μŠ€νŠΈ λ‚΄μš© 생성"""
658
+ print(f"[μŠ¬λΌμ΄λ“œ λ‚΄μš©] {slide_title} ν…μŠ€νŠΈ 생성 쀑... (λŒ€μƒ: {audience_type})")
659
+
660
+ # μ˜€λ””μ–ΈμŠ€ 정보 κ°€μ Έμ˜€κΈ°
661
+ audience_info = AUDIENCE_TYPES.get(audience_type, AUDIENCE_TYPES["일반 직원"])
662
 
663
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
664
  headers = {
 
666
  "Content-Type": "application/json"
667
  }
668
 
669
+ system_prompt = f"""You are a professional presentation content writer specializing in creating concise, impactful slide content.
670
+
671
+ Target Audience: {audience_type} - {audience_info['description']}
672
+ Tone: {audience_info['tone']}
673
+ Focus: {audience_info['focus']}
674
 
675
+ Your task is to create content specifically tailored for {audience_type}:
676
+ 1. A compelling subtitle (max 10 words) that resonates with {audience_type}
677
+ 2. Exactly 5 bullet points with emojis relevant to {audience_type}'s interests
678
  3. Each bullet point should be 8-12 words
679
  4. Use noun-ending style (λͺ…μ‚¬ν˜• μ’…κ²°) for Korean or concise fragments for English
680
+ 5. Content should address {audience_type}'s specific concerns and interests
681
 
682
  IMPORTANT Style Guidelines:
683
+ - End sentences with nouns or concise phrases
684
  - Avoid long verb endings like "μž…λ‹ˆλ‹€", "μŠ΅λ‹ˆλ‹€"
685
  - Use short endings like "μž„", "함" or noun forms
686
+ - Start each bullet with a relevant emoji that {audience_type} would appreciate
687
  - Be extremely concise and impactful
688
 
689
+ Audience-specific emoji guidelines:
690
+ - For executives: πŸ“Š 🎯 πŸ’° πŸ† πŸš€ πŸ“ˆ πŸ” πŸ’‘
691
+ - For investors: πŸ’° πŸ“ˆ πŸ’Ž 🏦 πŸ’Έ πŸ“Š πŸš€ πŸ”’
692
+ - For technical teams: πŸ”§ πŸ’» πŸ› οΈ βš™οΈ πŸ” 🌐 πŸ“± πŸ€–
693
+ - For general staff: 🀝 πŸ’‘ πŸ“‹ βœ… 🎯 🌟 πŸ“… πŸ’ͺ
694
+ - For customers: ⭐ 🎁 πŸ’ πŸ›‘οΈ 🌟 ✨ πŸ… πŸ‘
695
+ - For general public: 😊 🏠 🌍 ❀️ πŸŽ‰ 🌈 ✨ 🎯
 
 
 
 
696
 
697
  Output format (EXACTLY FOLLOW THIS FORMAT):
698
  Subtitle: [subtitle here]
699
+ - 🎯 [Point 1 - tailored for {audience_type}]
700
+ - πŸ“Š [Point 2 - tailored for {audience_type}]
701
+ - πŸ’‘ [Point 3 - tailored for {audience_type}]
702
+ - πŸš€ [Point 4 - tailored for {audience_type}]
703
+ - ⚑ [Point 5 - tailored for {audience_type}]"""
704
 
705
  user_message = f"""Topic: {topic}
706
  Slide Title: {slide_title}
707
+ Context: {slide_context}
708
+ Target Audience: {audience_type}"""
709
 
710
  # μ—…λ‘œλ“œλœ μ½˜ν…μΈ κ°€ 있으면 μΆ”κ°€
711
  if uploaded_content:
 
718
  search_context += f"- {result['title']}: {result['description']}\n"
719
  user_message += search_context
720
 
721
+ user_message += f"\n\nCreate compelling content for this presentation slide specifically tailored for {audience_type}. Remember to use emojis and concise noun-ending style."
722
 
723
  payload = {
724
  "model": "dep89a2fld32mcm",
 
790
  clean_point = point.replace('β€’', '').strip()
791
 
792
  # 이λͺ¨μ§€μ™€ ν…μŠ€νŠΈ 뢄리 (첫 번째 곡백을 κΈ°μ€€μœΌλ‘œ)
793
+ if len(clean_point) > 0 and clean_point[0] in 'πŸŽ―πŸ“ŠπŸ’‘πŸš€βš‘πŸ”πŸ“ˆπŸ’°πŸ†πŸ”§πŸŒπŸ”β­πŸŽ¨πŸ“±πŸ€πŸ“πŸŽ–οΈπŸ—οΈπŸŒ±πŸ’»πŸ› οΈβš™οΈπŸ€–πŸ“‹βœ…πŸŒŸπŸ“…πŸ’ͺπŸŽπŸ’πŸ›‘οΈβœ¨πŸ…πŸ‘πŸ˜ŠπŸ πŸŒβ€οΈπŸŽ‰πŸŒˆ':
794
  # 이λͺ¨μ§€κ°€ μžˆλŠ” 경우
795
  emoji = clean_point[0]
796
  text = clean_point[1:].strip()
 
1174
  return None, error_msg
1175
 
1176
  def create_slide_preview_html(slide_data: Dict) -> str:
1177
+ """16:9 λΉ„μœ¨μ˜ μŠ¬λΌμ΄λ“œ 프리뷰 HTML 생성 (νŽΈμ§‘ κΈ°λŠ₯ 제거)"""
1178
 
1179
  # 이미지λ₯Ό base64둜 인코딩
1180
  img_base64 = ""
 
1335
  <!-- 이미지 μ˜μ—­ (우츑) -->
1336
  <div class="image-area" style="
1337
  flex: 1;
 
 
1338
  display: flex;
1339
  align-items: center;
1340
  justify-content: center;
1341
  padding: 20px;
 
1342
  ">
1343
  """
1344
 
 
1375
 
1376
  return html
1377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1378
  def create_pptx_file(results: List[Dict], topic: str, template_name: str, theme_name: str = "λ―Έλ‹ˆλ©€ 라이트") -> str:
1379
  """μƒμ„±λœ κ²°κ³Όλ₯Ό PPTX 파일둜 λ³€ν™˜ (λ°œν‘œμž λ…ΈνŠΈ 포함)"""
1380
  print(f"[PPTX] 파일 생성 μ‹œμž‘... ν…Œλ§ˆ: {theme_name}")
 
1420
  except Exception as e:
1421
  print(f"[PPTX] ν‘œμ§€ 이미지 μΆ”κ°€ μ‹€νŒ¨: {str(e)}")
1422
 
1423
+ # 제λͺ© λ°°κ²½ λ°•μŠ€ (반투λͺ… - 더 투λͺ…ν•˜κ²Œ)
1424
  title_bg = slide.shapes.add_shape(
1425
  MSO_SHAPE.ROUNDED_RECTANGLE,
1426
  Inches(2), Inches(2.8),
 
1428
  )
1429
  title_bg.fill.solid()
1430
  title_bg.fill.fore_color.rgb = RGBColor(255, 255, 255) # 흰색 λ°°κ²½
1431
+ title_bg.fill.transparency = 0.8 # 50% 투λͺ…도 (더 투λͺ…ν•˜κ²Œ)
1432
  title_bg.line.fill.background()
1433
 
1434
+ # 그림자 효과 μΆ”κ°€ (더 μ•½ν•˜κ²Œ)
1435
  shadow = title_bg.shadow
1436
  shadow.visible = True
1437
+ shadow.distance = Pt(6)
1438
+ shadow.size = 100
1439
+ shadow.blur_radius = Pt(12)
1440
+ shadow.transparency = 0.8
1441
  shadow.angle = 45
1442
 
1443
  # 제λͺ© ν…μŠ€νŠΈ μΆ”κ°€
 
1487
  except Exception as e:
1488
  print(f"[PPTX] Thank You 이미지 μΆ”κ°€ μ‹€νŒ¨: {str(e)}")
1489
 
1490
+ # Thank You λ°°κ²½ λ°•μŠ€ (반투λͺ… - 더 투λͺ…ν•˜κ²Œ)
1491
  thanks_bg = slide.shapes.add_shape(
1492
  MSO_SHAPE.ROUNDED_RECTANGLE,
1493
  Inches(2), Inches(3.5),
 
1495
  )
1496
  thanks_bg.fill.solid()
1497
  thanks_bg.fill.fore_color.rgb = RGBColor(255, 255, 255) # 흰색 λ°°κ²½
1498
+ thanks_bg.fill.transparency = 0.8 # 50% 투λͺ…도 (더 투λͺ…ν•˜κ²Œ)
1499
  thanks_bg.line.fill.background()
1500
 
1501
+ # 그림자 효과 μΆ”κ°€ (더 μ•½ν•˜κ²Œ)
1502
  shadow = thanks_bg.shadow
1503
  shadow.visible = True
1504
+ shadow.distance = Pt(6)
1505
+ shadow.size = 100
1506
+ shadow.blur_radius = Pt(12)
1507
+ shadow.transparency = 0.8
1508
  shadow.angle = 45
1509
 
1510
  # Thank You ν…μŠ€νŠΈ (κ²°λ‘  문ꡬ)
 
1618
  # 뢈릿 색상
1619
  p.font.color.rgb = theme["text_color"]
1620
 
1621
+ # 우츑 이미지 μΆ”κ°€ (λ°°κ²½ λ°•μŠ€ 제거)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1622
  if slide_data.get('image'):
1623
  try:
1624
  img_buffer = BytesIO()
 
1694
 
1695
  return slides
1696
 
1697
+ def create_custom_slides_ui():
1698
+ """μ‚¬μš©μž μ •μ˜ μŠ¬λΌμ΄λ“œ ꡬ성 UI (3-20μž₯)"""
1699
+ slides = []
1700
+ for i in range(20): # μ΅œλŒ€ 20μž₯
1701
+ with gr.Row(visible=(i < 3)): # κΈ°λ³Έ 3μž₯만 ν‘œμ‹œ
1702
+ with gr.Column(scale=2):
1703
+ title = gr.Textbox(
1704
+ label=f"μŠ¬λΌμ΄λ“œ {i+1} 제λͺ©",
1705
+ placeholder="예: ν˜„ν™© 뢄석, μ†”λ£¨μ…˜, λ‘œλ“œλ§΅...",
1706
+ )
1707
+ with gr.Column(scale=3):
1708
+ style = gr.Dropdown(
1709
+ choices=list(STYLE_TEMPLATES.keys()),
1710
+ label=f"μŠ€νƒ€μΌ 선택",
1711
+ value="Colorful Mind Map"
1712
+ )
1713
+ with gr.Column(scale=3):
1714
+ hint = gr.Textbox(
1715
+ label=f"ν”„λ‘¬ν”„νŠΈ 힌트",
1716
+ placeholder="이 μŠ¬λΌμ΄λ“œμ—μ„œ ν‘œν˜„ν•˜κ³  싢은 λ‚΄μš©"
1717
+ )
1718
+ slides.append({"title": title, "style": style, "hint": hint, "row": gr.Row})
1719
+ return slides
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1720
 
1721
+ def generate_ppt_with_content(topic: str, template_name: str, audience_type: str, custom_slides: List[Dict],
1722
  slide_count: int, seed: int, uploaded_file,
1723
  use_web_search: bool, theme_name: str = "λ―Έλ‹ˆλ©€ 라이트",
1724
  progress=gr.Progress()):
 
1757
  print(f"\n[PPT 생성] μ‹œμž‘ - 총 {total_slides}개 μŠ¬λΌμ΄λ“œ (ν‘œμ§€ + λ³Έλ¬Έ {slide_count} + Thank You)")
1758
  print(f"[PPT 생성] 주제: {topic}")
1759
  print(f"[PPT 생성] ν…œν”Œλ¦Ώ: {template_name}")
1760
+ print(f"[PPT 생성] μ˜€λ””μ–ΈμŠ€: {audience_type}")
1761
  print(f"[PPT 생성] λ””μžμΈ ν…Œλ§ˆ: {theme_name}")
1762
  print(f"[PPT 생성] μ›Ή 검색: {'μ‚¬μš©' if use_web_search else 'λ―Έμ‚¬μš©'}")
1763
 
 
1790
 
1791
  # Thank You μŠ¬λΌμ΄λ“œλŠ” κ²°λ‘  문ꡬ 생성
1792
  if slide['title'] == 'Thank You':
1793
+ conclusion_phrase = generate_conclusion_phrase(topic, audience_type)
1794
  content = {
1795
  "subtitle": conclusion_phrase,
1796
  "bullet_points": []
1797
  }
1798
  # Thank You μŠ¬λΌμ΄λ“œμš© νŠΉλ³„ λ…ΈνŠΈ
1799
+ speaker_notes = generate_closing_notes(topic, conclusion_phrase, audience_type)
1800
  else:
1801
  content = generate_slide_content(
1802
+ topic, slide['title'], slide_context, audience_type,
1803
  uploaded_content, web_search_results
1804
  )
1805
  # 일반 μŠ¬λΌμ΄λ“œ λ°œν‘œμž λ…ΈνŠΈ
1806
+ speaker_notes = generate_presentation_notes(topic, slide['title'], content, audience_type)
1807
 
1808
  # ν”„λ‘¬ν”„νŠΈ 생성 및 이미지 생성
1809
  style_key = slide["style"]
 
1830
  "topic": topic # ν‘œμ§€μš©
1831
  }
1832
 
1833
+ # 프리뷰 HTML 생성 (νŽΈμ§‘ κΈ°λŠ₯ 제거)
1834
+ preview_html += create_slide_preview_html(slide_data)
 
 
 
1835
 
1836
  # ν˜„μž¬κΉŒμ§€μ˜ μƒνƒœ μ—…λ°μ΄νŠΈ
1837
  yield preview_html + "</div>", f"### πŸ”„ {slide_info} 생성 쀑...", None
 
1849
  except Exception as e:
1850
  print(f"[PPTX] 파일 생성 였λ₯˜: {str(e)}")
1851
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1852
  preview_html += "</div>"
1853
  progress(1.0, "μ™„λ£Œ!")
1854
  successful = sum(1 for r in results if r["success"])
1855
  final_status = f"### πŸŽ‰ 생성 μ™„λ£Œ! 총 {total_slides}개 μŠ¬λΌμ΄λ“œ 쀑 {successful}개 성곡"
1856
 
1857
+ # μ „μ—­ λ³€μˆ˜ μ €μž₯
1858
  global current_slides_data, current_template, current_theme
1859
  current_slides_data = results
1860
  current_template = template_name
 
1862
 
1863
  if pptx_path:
1864
  final_status += f"\n\n### πŸ“₯ PPTX 파일이 μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!"
1865
+ final_status += f"\n\n🎀 **{audience_type}λ₯Ό μœ„ν•œ λ°œν‘œμž λ…ΈνŠΈκ°€ 각 μŠ¬λΌμ΄λ“œμ— ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€!**"
 
1866
  if PROCESS_FLOW_AVAILABLE:
1867
  final_status += f"\n\nπŸ”§ **ν”„λ‘œμ„ΈμŠ€ ν”Œλ‘œμš° λ‹€μ΄μ–΄κ·Έλž¨μ΄ μžλ™μœΌλ‘œ μƒμ„±λ©λ‹ˆλ‹€ (ν•œκΈ€ 지원)**"
1868
 
1869
  yield preview_html, final_status, pptx_path
1870
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1871
  # Gradio μΈν„°νŽ˜μ΄μŠ€ 생성
1872
  with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
1873
  .preview-container { max-width: 1400px; margin: 0 auto; }
1874
  .slide-container { transition: all 0.3s ease; }
1875
  .slide-container:hover { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
1876
  """) as demo:
1877
  gr.Markdown("""
1878
  # 🎯 AI 기반 PPT 톡합 생성기 (κ°„κ²°ν•œ μŠ€νƒ€μΌ 버전)
1879
 
1880
+ ### ν…μŠ€νŠΈμ™€ 이미지가 μ™„λ²½ν•˜κ²Œ μ‘°ν™”λœ ν”„λ ˆμ  ν…Œμ΄μ…˜μ„ μžλ™μœΌλ‘œ μƒμ„±ν•˜μ„Έμš”!
1881
 
1882
  #### πŸ†• μƒˆλ‘œμš΄ κΈ°λŠ₯:
1883
+ - 🎭 **μ˜€λ””μ–ΈμŠ€λ³„ μ΅œμ ν™”**: λ°œν‘œ λŒ€μƒμ— 맞좘 λ‚΄μš©κ³Ό 톀 μžλ™ μ‘°μ •
1884
  - πŸ“ **κ°„κ²°ν•œ λͺ…μ‚¬ν˜• ν…μŠ€νŠΈ**: "~μž„", "~함" μŠ€νƒ€μΌμ˜ ν”„λ‘œνŽ˜μ…”λ„ν•œ 문체
1885
  - 🎨 **이λͺ¨μ§€ μžλ™ μΆ”κ°€**: 각 ν¬μΈνŠΈλ³„ μ μ ˆν•œ 이λͺ¨μ§€λ‘œ μ‹œκ°μ  ꡬ뢄
 
 
1886
  - πŸ“‹ **예제 ν…œν”Œλ¦Ώ**: λ²„νŠΌ 클릭으둜 예제 주제 뢈러였기
1887
  - 🎨 **λ””μžμΈ ν…Œλ§ˆ**: 5κ°€μ§€ ���문적인 λ””μžμΈ ν…Œλ§ˆ 선택 κ°€λŠ₯
1888
+ - πŸ’Ž **κ°œμ„ λœ μŠ¬λΌμ΄λ“œ λ””μžμΈ**: 더 투λͺ…ν•œ λ°°κ²½κ³Ό κΉ”λ”ν•œ λ ˆμ΄μ•„μ›ƒ
1889
  - πŸ“Š **ν‘œμ§€μ™€ Thank You μŠ¬λΌμ΄λ“œ** μžλ™ μΆ”κ°€
1890
+ - 🎀 **μ˜€λ””μ–ΈμŠ€λ³„ λ°œν‘œμž λ…ΈνŠΈ** μžλ™ 생성 (ꡬ어체)
1891
  - πŸ“ **파일 μ—…λ‘œλ“œ** 지원 (PDF/CSV/TXT)
1892
  - πŸ” **μ›Ή 검색** κΈ°λŠ₯ (Brave Search)
1893
  - 🎚️ **μŠ¬λΌμ΄λ“œ 수 쑰절** (6-20μž₯)
 
1921
  lines=2
1922
  )
1923
 
1924
+ # μ˜€λ””μ–ΈμŠ€ 선택 (μƒˆλ‘œ μΆ”κ°€)
1925
+ audience_select = gr.Dropdown(
1926
+ choices=list(AUDIENCE_TYPES.keys()),
1927
+ label="🎭 λ°œν‘œ λŒ€μƒ μ˜€λ””μ–ΈμŠ€",
1928
+ value="일반 직원",
1929
+ info="λ°œν‘œ λŒ€μƒμ— 따라 λ‚΄μš©κ³Ό 톀이 μžλ™μœΌλ‘œ μ΅œμ ν™”λ©λ‹ˆλ‹€"
1930
+ )
1931
+
1932
+ # μ˜€λ””μ–ΈμŠ€ μ„€λͺ… ν‘œμ‹œ
1933
+ audience_info = gr.Markdown()
1934
+
1935
  # PPT ν…œν”Œλ¦Ώ 선택
1936
  template_select = gr.Dropdown(
1937
  choices=list(PPT_TEMPLATES.keys()),
 
1995
  gr.Markdown("""
1996
  ### πŸ”„ μž‘μ—… μˆœμ„œ:
1997
  1. **예제 뢈러였기** λ²„νŠΌμœΌλ‘œ μƒ˜ν”Œ 주제λ₯Ό λ‘œλ“œν•˜κ±°λ‚˜ 직접 μž…λ ₯
1998
+ 2. **μ˜€λ””μ–ΈμŠ€ 선택**: λ°œν‘œ λŒ€μƒμ— 따라 λ‚΄μš©μ΄ μžλ™μœΌλ‘œ μ΅œμ ν™”λ©λ‹ˆλ‹€
1999
+ 3. **ν…œν”Œλ¦Ώκ³Ό ν…Œλ§ˆ 선택** ν›„ 생성 λ²„νŠΌ 클릭
2000
+ 4. **λ‹€μš΄λ‘œλ“œ**: 생성 μ™„λ£Œ ν›„ PPTX 파일 λ‹€μš΄λ‘œλ“œ
2001
+
2002
+ ### 🎭 μ˜€λ””μ–ΈμŠ€λ³„ νŠΉμ§•:
2003
+ - **κ²½μ˜μ§„/μž„μ›**: μ „λž΅μ  κ°€μΉ˜, ROI, λΉ„μ¦ˆλ‹ˆμŠ€ μž„νŒ©νŠΈ 쀑심
2004
+ - **투자자**: μ‹œμž₯ 기회, μ„±μž₯ κ°€λŠ₯μ„±, μˆ˜μ΅μ„± κ°•μ‘°
2005
+ - **κΈ°μˆ νŒ€**: 기술적 세뢀사항, κ΅¬ν˜„ 방법, μ„±λŠ₯ μ§€ν‘œ
2006
+ - **일반 직원**: 싀무적 λ‚΄μš©, ν˜‘μ—… λ°©μ•ˆ, μ‹€ν–‰ κ³„νš
2007
+ - **고객/νŒŒνŠΈλ„ˆ**: 고객 κ°€μΉ˜, ν˜œνƒ, 성곡 사둀
2008
+ - **일반 λŒ€μ€‘**: μ‰¬μš΄ μ„€λͺ…, μ‚¬μš© νŽΈμ˜μ„±, μ‹€μ§ˆμ  이점
2009
 
2010
  ### πŸ’‘ ν…μŠ€νŠΈ μŠ€νƒ€μΌ νŠΉμ§•:
2011
  - **κ°„κ²°ν•œ λͺ…μ‚¬ν˜• μ’…κ²°**: "~μž„", "~함" λ˜λŠ” λͺ…μ‚¬λ‘œ 끝남
2012
  - **이λͺ¨μ§€ ν™œμš©**: 각 포인트 μ•žμ— λ‚΄μš© κ΄€λ ¨ 이λͺ¨μ§€ μžλ™ μΆ”κ°€
2013
  - **8-12단어**: 각 뢈릿 ν¬μΈνŠΈλŠ” 맀우 κ°„κ²°ν•˜κ²Œ ꡬ성
2014
 
 
 
 
 
 
 
 
 
 
 
 
 
2015
  ### πŸ”§ ν”„λ‘œμ„ΈμŠ€ ν”Œλ‘œμš° λ‹€μ΄μ–΄κ·Έλž¨:
2016
  - **Business Workflow** λ˜λŠ” **Flowchart** μŠ€νƒ€μΌ 선택 μ‹œ μžλ™μœΌλ‘œ ν”„λ‘œμ„ΈμŠ€ ν”Œλ‘œμš° λ‹€μ΄μ–΄κ·Έλž¨ 생성
2017
  - ν•œκΈ€ ν…μŠ€νŠΈλ₯Ό μ™„λ²½ν•˜κ²Œ μ§€μ›ν•˜λŠ” λ‹€μ΄μ–΄κ·Έλž¨
 
2058
  example_topic = EXAMPLE_TOPICS.get(template_name, "")
2059
  return example_topic
2060
 
2061
+ def update_audience_info(audience_type):
2062
+ """μ˜€λ””μ–ΈμŠ€ 정보 ν‘œμ‹œ"""
2063
+ info = AUDIENCE_TYPES.get(audience_type, {})
2064
+ return f"""**{info.get('description', '')}**
2065
+ - 톀: {info.get('tone', '')}
2066
+ - 포컀슀: {info.get('focus', '')}"""
2067
+
2068
  def update_theme_preview(theme_name):
2069
  """ν…Œλ§ˆ 미리보기 HTML 생성"""
2070
  theme = DESIGN_THEMES.get(theme_name, DESIGN_THEMES["λ―Έλ‹ˆλ©€ 라이트"])
 
2139
  ])
2140
  return updates
2141
 
2142
+ def generate_ppt_handler(topic, template_name, audience_type, theme_name, slide_count, seed, file_upload,
2143
  use_web_search, custom_slide_count, progress=gr.Progress(),
2144
  *custom_inputs):
2145
  if not topic.strip():
 
2164
 
2165
  # PPT 생성
2166
  for preview, status, pptx_file in generate_ppt_with_content(
2167
+ topic, template_name, audience_type, custom_slides, slide_count, seed,
2168
  file_upload, use_web_search, theme_name, progress
2169
  ):
2170
  yield preview, status, pptx_file
 
2176
  outputs=[topic_input]
2177
  )
2178
 
2179
+ audience_select.change(
2180
+ fn=update_audience_info,
2181
+ inputs=[audience_select],
2182
+ outputs=[audience_info]
2183
+ )
2184
+
2185
  theme_select.change(
2186
  fn=update_theme_preview,
2187
  inputs=[theme_select],
 
2215
  generate_btn.click(
2216
  fn=generate_ppt_handler,
2217
  inputs=[
2218
+ topic_input, template_select, audience_select, theme_select, slide_count,
2219
  seed_input, file_upload, use_web_search, custom_slide_count
2220
  ] + all_custom_inputs,
2221
  outputs=[preview_output, status_output, download_file]
 
2224
  # 초기 λ‘œλ“œμ‹œ ν…œν”Œλ¦Ώ/ν…Œλ§ˆ 정보 ν‘œμ‹œ
2225
  demo.load(
2226
  fn=lambda: (update_template_info(template_select.value, slide_count.value),
2227
+ update_theme_preview(theme_select.value),
2228
+ update_audience_info(audience_select.value)),
2229
  inputs=[],
2230
+ outputs=[template_info, theme_preview, audience_info]
2231
  )
2232
 
2233
  # μ•± μ‹€ν–‰
2234
  if __name__ == "__main__":
2235
+ demo.launch(ssr_mode=False)