Update app.py
Browse files
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
|
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
|
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
|
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.
|
687 |
-
3.
|
688 |
-
4.
|
689 |
-
5.
|
|
|
690 |
|
691 |
-
Examples:
|
692 |
-
- "
|
693 |
-
- "
|
694 |
-
- "
|
695 |
-
- "
|
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,
|
|
|
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
|
760 |
- Be extremely concise and impactful
|
761 |
|
762 |
-
|
763 |
-
-
|
764 |
-
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
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 -
|
777 |
-
- π [Point 2 -
|
778 |
-
- π‘ [Point 3 -
|
779 |
-
- π [Point 4 -
|
780 |
-
- β‘ [Point 5 -
|
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.
|
1525 |
title_bg.line.fill.background()
|
1526 |
|
1527 |
-
# κ·Έλ¦Όμ ν¨κ³Ό μΆκ° (λ
|
1528 |
shadow = title_bg.shadow
|
1529 |
shadow.visible = True
|
1530 |
-
shadow.distance = Pt(
|
1531 |
-
shadow.size =
|
1532 |
-
shadow.blur_radius = Pt(
|
1533 |
-
shadow.transparency = 0.
|
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.
|
1592 |
thanks_bg.line.fill.background()
|
1593 |
|
1594 |
-
# κ·Έλ¦Όμ ν¨κ³Ό μΆκ° (λ
|
1595 |
shadow = thanks_bg.shadow
|
1596 |
shadow.visible = True
|
1597 |
-
shadow.distance = Pt(
|
1598 |
-
shadow.size =
|
1599 |
-
shadow.blur_radius = Pt(
|
1600 |
-
shadow.transparency = 0.
|
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
|
1812 |
-
"""
|
1813 |
-
|
1814 |
-
#
|
1815 |
-
|
1816 |
-
|
1817 |
-
|
1818 |
-
|
1819 |
-
|
1820 |
-
|
1821 |
-
|
1822 |
-
|
1823 |
-
|
1824 |
-
|
1825 |
-
|
1826 |
-
|
1827 |
-
|
1828 |
-
|
1829 |
-
|
1830 |
-
|
1831 |
-
|
1832 |
-
|
1833 |
-
|
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 |
-
|
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.
|
2477 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|