Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -47,6 +47,11 @@ except ImportError as e:
|
|
47 |
DIAGRAM_GENERATORS_AVAILABLE = False
|
48 |
logger.warning(f"๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค: {e}")
|
49 |
|
|
|
|
|
|
|
|
|
|
|
50 |
##############################################################################
|
51 |
# API Configuration
|
52 |
##############################################################################
|
@@ -106,11 +111,6 @@ def initialize_flux_api():
|
|
106 |
FLUX_API_ENABLED = False
|
107 |
return False
|
108 |
|
109 |
-
##############################################################################
|
110 |
-
# Diagram Generation API Configuration - REMOVED (using local generators)
|
111 |
-
##############################################################################
|
112 |
-
# ๊ธฐ์กด Diagram API ๊ด๋ จ ์ฝ๋ ์ ๊ฑฐํ๊ณ ๋ก์ปฌ ์์ฑ๊ธฐ ์ฌ์ฉ
|
113 |
-
|
114 |
##############################################################################
|
115 |
# Design Themes and Color Schemes
|
116 |
##############################################################################
|
@@ -350,41 +350,70 @@ def get_emoji_for_content(text: str) -> str:
|
|
350 |
return 'โถ๏ธ'
|
351 |
|
352 |
##############################################################################
|
353 |
-
# Diagram Type Detection
|
354 |
##############################################################################
|
355 |
-
def
|
356 |
-
"""์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๋ถ์ํ์ฌ ์ ์ ํ ๋ค์ด์ด๊ทธ๋จ
|
357 |
combined_text = f"{title} {content}".lower()
|
358 |
|
359 |
-
#
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
# Concept Map keywords
|
368 |
-
elif any(word in combined_text for word in ['๊ฐ๋
', 'concept', '๊ด๊ณ', 'relationship', '์ฐ๊ด', 'connection', '๋ง์ธ๋๋งต', 'mindmap', '๊ตฌ์กฐ', 'structure', '์ฒด๊ณ', 'system']):
|
369 |
-
return "Concept Map"
|
370 |
-
|
371 |
-
# Radial Diagram keywords
|
372 |
-
elif any(word in combined_text for word in ['์ค์ฌ', 'central', '๋ฐฉ์ฌํ', 'radial', 'ํต์ฌ', 'core', '์ฃผ์', 'main', '์ค์', 'center']):
|
373 |
-
return "Radial Diagram"
|
374 |
-
|
375 |
-
# Synoptic Chart keywords
|
376 |
-
elif any(word in combined_text for word in ['๊ฐ์', 'overview', '์ ์ฒด', 'overall', '์์ฝ', 'summary', '์๋ํฑ', 'synoptic', '์ด๊ด', 'comprehensive']):
|
377 |
-
return "Synoptic Chart"
|
378 |
|
379 |
-
#
|
380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
381 |
if content.count('\n-') > 3 or content.count('\nโข') > 3:
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
return "Process Flow"
|
386 |
|
387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
|
389 |
##############################################################################
|
390 |
# Generate Diagram JSON using LLM
|
@@ -394,7 +423,7 @@ def generate_diagram_json(title: str, content: str, diagram_type: str) -> Option
|
|
394 |
if not FRIENDLI_TOKEN:
|
395 |
return None
|
396 |
|
397 |
-
# ๋ค์ด์ด๊ทธ๋จ ํ์
๋ณ JSON ๊ตฌ์กฐ ๊ฐ์ด๋
|
398 |
json_guides = {
|
399 |
"Concept Map": """Generate a JSON for a concept map with the EXACT following structure:
|
400 |
{
|
@@ -526,15 +555,18 @@ Important rules:
|
|
526 |
return None
|
527 |
|
528 |
##############################################################################
|
529 |
-
# Generate Diagram using Local Generators
|
530 |
##############################################################################
|
531 |
def generate_diagram_locally(json_data: str, diagram_type: str, output_format: str = "png") -> Optional[str]:
|
532 |
-
"""๋ก์ปฌ ์์ฑ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด์ด๊ทธ๋จ ์์ฑ"""
|
533 |
if not DIAGRAM_GENERATORS_AVAILABLE:
|
534 |
logger.error("๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฌ์ฉํ ์ ์์ต๋๋ค")
|
535 |
return None
|
536 |
|
537 |
try:
|
|
|
|
|
|
|
538 |
# ์ ์ ํ ์์ฑ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด์ด๊ทธ๋จ ์์ฑ
|
539 |
if diagram_type == "Concept Map":
|
540 |
result = generate_concept_map(json_data, output_format)
|
@@ -1067,7 +1099,7 @@ def generate_images_parallel(prompt_3d: str, prompt_photo: str) -> Tuple[Optiona
|
|
1067 |
return image_3d, image_photo
|
1068 |
|
1069 |
##############################################################################
|
1070 |
-
# PPT Generation Functions - FIXED
|
1071 |
##############################################################################
|
1072 |
def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> list:
|
1073 |
"""Parse LLM response to extract slide content - FIXED VERSION"""
|
@@ -1495,7 +1527,7 @@ def create_advanced_ppt_from_content(
|
|
1495 |
include_diagrams: bool = False,
|
1496 |
include_flux_images: bool = False
|
1497 |
) -> str:
|
1498 |
-
"""Create advanced PPT file with
|
1499 |
if not PPTX_AVAILABLE:
|
1500 |
raise ImportError("python-pptx library is required")
|
1501 |
|
@@ -1506,8 +1538,32 @@ def create_advanced_ppt_from_content(
|
|
1506 |
prs.slide_width = Inches(10)
|
1507 |
prs.slide_height = Inches(5.625)
|
1508 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1509 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1510 |
-
# 1) ์ ๋ชฉ ์ฌ๋ผ์ด๋(ํ์ง) ์์ฑ
|
1511 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1512 |
title_slide_layout = prs.slide_layouts[0]
|
1513 |
slide = prs.slides.add_slide(title_slide_layout)
|
@@ -1522,7 +1578,7 @@ def create_advanced_ppt_from_content(
|
|
1522 |
if title_shape:
|
1523 |
title_shape.left = Inches(0.5)
|
1524 |
title_shape.width = prs.slide_width - Inches(1)
|
1525 |
-
title_shape.top = Inches(1.0)
|
1526 |
title_shape.height = Inches(1.2)
|
1527 |
|
1528 |
tf = title_shape.text_frame
|
@@ -1538,7 +1594,7 @@ def create_advanced_ppt_from_content(
|
|
1538 |
if subtitle_shape:
|
1539 |
subtitle_shape.left = Inches(0.5)
|
1540 |
subtitle_shape.width = prs.slide_width - Inches(1)
|
1541 |
-
subtitle_shape.top = Inches(2.2)
|
1542 |
subtitle_shape.height = Inches(0.9)
|
1543 |
|
1544 |
tf2 = subtitle_shape.text_frame
|
@@ -1550,25 +1606,27 @@ def create_advanced_ppt_from_content(
|
|
1550 |
p2.font.color.rgb = RGBColor(255, 255, 255)
|
1551 |
p2.alignment = PP_ALIGN.CENTER
|
1552 |
|
1553 |
-
#
|
1554 |
if include_ai_image and (AI_IMAGE_ENABLED or FLUX_API_ENABLED):
|
1555 |
logger.info("Generating AI cover images via parallel APIs...")
|
1556 |
|
1557 |
-
# ๋ ๊ฐ์ง ํ๋กฌํํธ ์์ฑ
|
1558 |
prompt_3d, prompt_photo = generate_cover_image_prompts(topic, slides_data)
|
1559 |
-
|
1560 |
-
# ๋ณ๋ ฌ๋ก ์ด๋ฏธ์ง ์์ฑ
|
1561 |
image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
|
1562 |
|
1563 |
-
#
|
1564 |
-
ai_image_path =
|
|
|
|
|
|
|
|
|
|
|
|
|
1565 |
|
1566 |
if ai_image_path and os.path.exists(ai_image_path):
|
1567 |
try:
|
1568 |
img = Image.open(ai_image_path)
|
1569 |
img_width, img_height = img.size
|
1570 |
|
1571 |
-
# ์ด๋ฏธ์ง๋ฅผ ์ฐ์ธก ํ๋จ์ ๋ฐฐ์น
|
1572 |
max_width = Inches(3.5)
|
1573 |
max_height = Inches(2.5)
|
1574 |
|
@@ -1580,7 +1638,6 @@ def create_advanced_ppt_from_content(
|
|
1580 |
img_h = max_height
|
1581 |
img_w = max_height / ratio
|
1582 |
|
1583 |
-
# ์ฐ์ธก ํ๋จ ๋ฐฐ์น
|
1584 |
left = prs.slide_width - img_w - Inches(0.5)
|
1585 |
top = prs.slide_height - img_h - Inches(0.8)
|
1586 |
|
@@ -1591,7 +1648,6 @@ def create_advanced_ppt_from_content(
|
|
1591 |
pic.shadow.distance = Pt(8)
|
1592 |
pic.shadow.angle = 45
|
1593 |
|
1594 |
-
# ์ด๋ฏธ์ง ์์ ์์ ์บก์
์ถ๊ฐ
|
1595 |
caption_box = slide.shapes.add_textbox(
|
1596 |
left, top - Inches(0.3),
|
1597 |
img_w, Inches(0.3)
|
@@ -1614,14 +1670,14 @@ def create_advanced_ppt_from_content(
|
|
1614 |
except Exception as e:
|
1615 |
logger.error(f"Failed to add cover image: {e}")
|
1616 |
|
1617 |
-
# ์ฅ์ ์์
|
1618 |
add_decorative_shapes(slide, theme)
|
1619 |
|
1620 |
-
#
|
|
|
|
|
1621 |
for i, slide_data in enumerate(slides_data):
|
1622 |
layout_type = slide_data.get('layout', 'title_content')
|
1623 |
|
1624 |
-
# Log slide creation
|
1625 |
logger.info(f"Creating slide {i+1}: {slide_data.get('title', 'No title')}")
|
1626 |
logger.debug(f"Content length: {len(slide_data.get('content', ''))}")
|
1627 |
|
@@ -1643,24 +1699,23 @@ def create_advanced_ppt_from_content(
|
|
1643 |
# Set title
|
1644 |
if slide.shapes.title:
|
1645 |
slide.shapes.title.text = slide_data.get('title', '์ ๋ชฉ ์์')
|
1646 |
-
# IMMEDIATELY set title font size after setting text
|
1647 |
try:
|
1648 |
title_text_frame = slide.shapes.title.text_frame
|
1649 |
if title_text_frame and title_text_frame.paragraphs:
|
1650 |
for paragraph in title_text_frame.paragraphs:
|
1651 |
if layout_type == 'section_header':
|
1652 |
-
paragraph.font.size = Pt(28)
|
1653 |
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
1654 |
paragraph.alignment = PP_ALIGN.CENTER
|
1655 |
else:
|
1656 |
-
paragraph.font.size = Pt(24)
|
1657 |
paragraph.font.color.rgb = theme['colors']['primary']
|
1658 |
paragraph.font.bold = True
|
1659 |
paragraph.font.name = theme['fonts']['title']
|
1660 |
except Exception as e:
|
1661 |
logger.warning(f"Title font sizing failed: {e}")
|
1662 |
|
1663 |
-
#
|
1664 |
slide_title = slide_data.get('title', '')
|
1665 |
slide_content = slide_data.get('content', '')
|
1666 |
|
@@ -1669,20 +1724,22 @@ def create_advanced_ppt_from_content(
|
|
1669 |
['๊ฒฐ๋ก ', 'conclusion', '์์ฝ', 'summary', 'ํต์ฌ', 'key',
|
1670 |
'๋ง๋ฌด๋ฆฌ', 'closing', '์ ๋ฆฌ', 'takeaway', '์์ฌ์ ', 'implication'])
|
1671 |
|
1672 |
-
#
|
1673 |
should_add_visual = False
|
1674 |
visual_type = None
|
1675 |
|
1676 |
-
#
|
1677 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
1678 |
should_add_visual = True
|
1679 |
visual_type = ('conclusion_images', None)
|
1680 |
-
|
1681 |
-
|
1682 |
-
if diagram_type:
|
1683 |
-
should_add_visual = True
|
1684 |
-
visual_type = ('diagram', diagram_type)
|
1685 |
-
elif not should_add_visual and include_flux_images and i % 2 == 0: # ๋งค 2๋ฒ์งธ ์ฌ๋ผ์ด๋์ ์ด๋ฏธ์ง
|
1686 |
should_add_visual = True
|
1687 |
visual_type = ('diverse_images', None)
|
1688 |
|
@@ -1712,14 +1769,13 @@ def create_advanced_ppt_from_content(
|
|
1712 |
|
1713 |
if visual_type[0] == 'diagram' and DIAGRAM_GENERATORS_AVAILABLE:
|
1714 |
# ๋ค์ด์ด๊ทธ๋จ ์์ฑ
|
1715 |
-
logger.info(f"Generating {visual_type[1]} for slide {i+1}")
|
1716 |
diagram_json = generate_diagram_json(slide_title, slide_content, visual_type[1])
|
1717 |
|
1718 |
if diagram_json:
|
1719 |
diagram_path = generate_diagram_locally(diagram_json, visual_type[1], "png")
|
1720 |
if diagram_path and os.path.exists(diagram_path):
|
1721 |
try:
|
1722 |
-
# ๋ค์ด์ด๊ทธ๋จ ์ด๋ฏธ์ง ์ถ๊ฐ
|
1723 |
pic = slide.shapes.add_picture(
|
1724 |
diagram_path,
|
1725 |
Inches(5.2), Inches(1.5),
|
@@ -1727,7 +1783,6 @@ def create_advanced_ppt_from_content(
|
|
1727 |
)
|
1728 |
visual_added = True
|
1729 |
|
1730 |
-
# ๋ค์ด์ด๊ทธ๋จ ์บก์
์ถ๊ฐ
|
1731 |
caption_box = slide.shapes.add_textbox(
|
1732 |
Inches(5.2), Inches(4.6), Inches(4.3), Inches(0.3)
|
1733 |
)
|
@@ -1738,7 +1793,6 @@ def create_advanced_ppt_from_content(
|
|
1738 |
caption_p.font.color.rgb = theme['colors']['secondary']
|
1739 |
caption_p.alignment = PP_ALIGN.CENTER
|
1740 |
|
1741 |
-
# ์์ ํ์ผ ์ญ์
|
1742 |
try:
|
1743 |
os.unlink(diagram_path)
|
1744 |
except:
|
@@ -1747,13 +1801,26 @@ def create_advanced_ppt_from_content(
|
|
1747 |
logger.error(f"Failed to add diagram: {e}")
|
1748 |
|
1749 |
elif visual_type[0] == 'conclusion_images':
|
1750 |
-
# ๊ฒฐ๋ก ์ฌ๋ผ์ด๋์ฉ ์ด๋ฏธ์ง ์์ฑ
|
1751 |
logger.info(f"Generating conclusion images for slide {i+1}")
|
1752 |
prompt_3d, prompt_photo = generate_conclusion_image_prompts(slide_title, slide_content)
|
1753 |
-
image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
|
1754 |
|
1755 |
-
|
1756 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1757 |
|
1758 |
if selected_image and os.path.exists(selected_image):
|
1759 |
try:
|
@@ -1764,7 +1831,6 @@ def create_advanced_ppt_from_content(
|
|
1764 |
)
|
1765 |
visual_added = True
|
1766 |
|
1767 |
-
# ์ด๋ฏธ์ง ์บก์
์ถ๊ฐ
|
1768 |
caption_box = slide.shapes.add_textbox(
|
1769 |
Inches(5.2), Inches(4.6), Inches(4.3), Inches(0.3)
|
1770 |
)
|
@@ -1775,7 +1841,6 @@ def create_advanced_ppt_from_content(
|
|
1775 |
caption_p.font.color.rgb = theme['colors']['secondary']
|
1776 |
caption_p.alignment = PP_ALIGN.CENTER
|
1777 |
|
1778 |
-
# ์์ ํ์ผ ์ ๋ฆฌ
|
1779 |
for temp_path in [image_3d, image_photo]:
|
1780 |
if temp_path and os.path.exists(temp_path):
|
1781 |
try:
|
@@ -1786,15 +1851,23 @@ def create_advanced_ppt_from_content(
|
|
1786 |
logger.error(f"Failed to add conclusion image: {e}")
|
1787 |
|
1788 |
elif visual_type[0] == 'diverse_images':
|
1789 |
-
# ๋ค์ํ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง ์์ฑ (
|
1790 |
-
logger.info(f"Generating diverse images for slide {i+1}")
|
1791 |
prompt_3d, prompt_photo = generate_diverse_prompt(slide_title, slide_content, i)
|
1792 |
-
image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
|
1793 |
|
1794 |
-
|
1795 |
-
|
1796 |
-
|
1797 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1798 |
|
1799 |
if selected_image and os.path.exists(selected_image):
|
1800 |
try:
|
@@ -1805,13 +1878,8 @@ def create_advanced_ppt_from_content(
|
|
1805 |
)
|
1806 |
visual_added = True
|
1807 |
|
1808 |
-
|
1809 |
-
|
1810 |
-
if temp_path and os.path.exists(temp_path):
|
1811 |
-
try:
|
1812 |
-
os.unlink(temp_path)
|
1813 |
-
except:
|
1814 |
-
pass
|
1815 |
except Exception as e:
|
1816 |
logger.error(f"Failed to add slide image: {e}")
|
1817 |
|
@@ -1829,7 +1897,6 @@ def create_advanced_ppt_from_content(
|
|
1829 |
else:
|
1830 |
# ๊ธฐ๋ณธ ๋ ์ด์์ (์๊ฐ์ ์์ ์์)
|
1831 |
if layout_type == 'section_header':
|
1832 |
-
# Section header content handling
|
1833 |
content = slide_data.get('content', '')
|
1834 |
if content:
|
1835 |
logger.info(f"Adding content to section header slide {i+1}: {content[:50]}...")
|
@@ -1847,7 +1914,6 @@ def create_advanced_ppt_from_content(
|
|
1847 |
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
1848 |
paragraph.alignment = PP_ALIGN.CENTER
|
1849 |
|
1850 |
-
# Add decorative line
|
1851 |
line = slide.shapes.add_shape(
|
1852 |
MSO_SHAPE.RECTANGLE, Inches(3), Inches(3.2), Inches(4), Pt(4)
|
1853 |
)
|
@@ -1874,7 +1940,6 @@ def create_advanced_ppt_from_content(
|
|
1874 |
left_tf.word_wrap = True
|
1875 |
force_font_size(left_tf, 14, theme)
|
1876 |
|
1877 |
-
# Apply emoji bullets
|
1878 |
for paragraph in left_tf.paragraphs:
|
1879 |
text = paragraph.text.strip()
|
1880 |
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
@@ -1895,7 +1960,6 @@ def create_advanced_ppt_from_content(
|
|
1895 |
right_tf.word_wrap = True
|
1896 |
force_font_size(right_tf, 14, theme)
|
1897 |
|
1898 |
-
# Apply emoji bullets
|
1899 |
for paragraph in right_tf.paragraphs:
|
1900 |
text = paragraph.text.strip()
|
1901 |
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
@@ -1915,10 +1979,7 @@ def create_advanced_ppt_from_content(
|
|
1915 |
|
1916 |
if content and content.strip():
|
1917 |
textbox = slide.shapes.add_textbox(
|
1918 |
-
Inches(0.5),
|
1919 |
-
Inches(1.5), # top
|
1920 |
-
Inches(9), # width
|
1921 |
-
Inches(3.5) # height
|
1922 |
)
|
1923 |
|
1924 |
tf = textbox.text_frame
|
@@ -1969,7 +2030,7 @@ def create_advanced_ppt_from_content(
|
|
1969 |
except Exception as e:
|
1970 |
logger.warning(f"Failed to add slide notes: {e}")
|
1971 |
|
1972 |
-
# Add slide number
|
1973 |
slide_number_bg = slide.shapes.add_shape(
|
1974 |
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1975 |
Inches(8.3), Inches(5.0), Inches(1.5), Inches(0.5)
|
@@ -1984,12 +2045,11 @@ def create_advanced_ppt_from_content(
|
|
1984 |
)
|
1985 |
slide_number_frame = slide_number_box.text_frame
|
1986 |
slide_number_frame.text = f"{i + 1} / {len(slides_data)}"
|
1987 |
-
slide_number_frame.paragraphs[0].font.size = Pt(10)
|
1988 |
slide_number_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
1989 |
slide_number_frame.paragraphs[0].font.bold = False
|
1990 |
slide_number_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
1991 |
|
1992 |
-
# Add subtle design element on alternating slides
|
1993 |
if i % 2 == 0:
|
1994 |
accent_shape = slide.shapes.add_shape(
|
1995 |
MSO_SHAPE.OVAL,
|
@@ -2000,31 +2060,32 @@ def create_advanced_ppt_from_content(
|
|
2000 |
accent_shape.fill.fore_color.rgb = theme['colors']['accent']
|
2001 |
accent_shape.line.fill.background()
|
2002 |
|
2003 |
-
#
|
|
|
|
|
|
|
2004 |
thank_you_layout = prs.slide_layouts[5] if len(prs.slide_layouts) > 5 else prs.slide_layouts[0]
|
2005 |
thank_you_slide = prs.slides.add_slide(thank_you_layout)
|
2006 |
|
2007 |
-
# Apply gradient background
|
2008 |
add_gradient_background(thank_you_slide, theme['colors']['secondary'], theme['colors']['primary'])
|
2009 |
|
2010 |
if thank_you_slide.shapes.title:
|
2011 |
thank_you_slide.shapes.title.text = "๊ฐ์ฌํฉ๋๋ค"
|
2012 |
try:
|
2013 |
if thank_you_slide.shapes.title.text_frame and thank_you_slide.shapes.title.text_frame.paragraphs:
|
2014 |
-
thank_you_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(36)
|
2015 |
thank_you_slide.shapes.title.text_frame.paragraphs[0].font.bold = True
|
2016 |
thank_you_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
2017 |
thank_you_slide.shapes.title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
2018 |
except Exception as e:
|
2019 |
logger.warning(f"Thank you slide styling failed: {e}")
|
2020 |
|
2021 |
-
# Add contact or additional info placeholder
|
2022 |
info_box = thank_you_slide.shapes.add_textbox(
|
2023 |
Inches(2), Inches(3.5), Inches(6), Inches(1)
|
2024 |
)
|
2025 |
info_tf = info_box.text_frame
|
2026 |
info_tf.text = "AI๋ก ์์ฑ๋ ํ๋ ์ ํ
์ด์
"
|
2027 |
-
info_tf.paragraphs[0].font.size = Pt(18)
|
2028 |
info_tf.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
2029 |
info_tf.paragraphs[0].alignment = PP_ALIGN.CENTER
|
2030 |
|
@@ -2255,6 +2316,10 @@ def generate_ppt(
|
|
2255 |
yield None, "โ ๏ธ ๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค. ๋ค์ด์ด๊ทธ๋จ ์์ด ์งํํฉ๋๋ค.", ""
|
2256 |
include_diagrams = False
|
2257 |
|
|
|
|
|
|
|
|
|
2258 |
# Process reference files if provided
|
2259 |
additional_context = ""
|
2260 |
chart_data = None
|
@@ -2327,12 +2392,12 @@ def generate_ppt(
|
|
2327 |
|
2328 |
# AI ์ด๋ฏธ์ง ๋ฐ ๋ค์ด์ด๊ทธ๋จ ์์ฑ ์๋ฆผ
|
2329 |
visual_features = []
|
2330 |
-
if include_ai_image and FLUX_API_ENABLED:
|
2331 |
visual_features.append("AI 3D ํ์ง ์ด๋ฏธ์ง")
|
2332 |
if include_diagrams and DIAGRAM_GENERATORS_AVAILABLE:
|
2333 |
-
visual_features.append("๋ค์ด์ด๊ทธ๋จ")
|
2334 |
if include_flux_images and FLUX_API_ENABLED:
|
2335 |
-
visual_features.append("AI ์์ฑ ์ด๋ฏธ์ง")
|
2336 |
|
2337 |
if visual_features:
|
2338 |
yield None, f"๐ ์ฌ๋ผ์ด๋ ์์ฑ ์๋ฃ!\n\n๐จ ์์ฑ ์ค: {', '.join(visual_features)}... (์๊ฐ์ด ์์๋ ์ ์์ต๋๋ค)", llm_response
|
@@ -2354,12 +2419,12 @@ def generate_ppt(
|
|
2354 |
success_msg += f"๐จ ๋์์ธ ํ
๋ง: {DESIGN_THEMES[design_theme]['name']}\n"
|
2355 |
success_msg += f"๐ ๋ ์ด์์ ์คํ์ผ: {layout_style}\n"
|
2356 |
|
2357 |
-
if include_ai_image and FLUX_API_ENABLED:
|
2358 |
success_msg += f"๐ผ๏ธ AI ์์ฑ ํ์ง ์ด๋ฏธ์ง ํฌํจ\n"
|
2359 |
if include_diagrams and DIAGRAM_GENERATORS_AVAILABLE:
|
2360 |
-
success_msg += f"๐ AI ์์ฑ ๋ค์ด์ด๊ทธ๋จ
|
2361 |
if include_flux_images and FLUX_API_ENABLED:
|
2362 |
-
success_msg += f"๐จ AI ์์ฑ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง
|
2363 |
|
2364 |
success_msg += f"๐ ์์ฑ๋ ์ฌ๋ผ์ด๋:\n"
|
2365 |
|
@@ -2677,13 +2742,13 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
2677 |
include_diagrams = gr.Checkbox(
|
2678 |
label="๐ AI ๋ค์ด์ด๊ทธ๋จ",
|
2679 |
value=False,
|
2680 |
-
info="
|
2681 |
)
|
2682 |
|
2683 |
include_flux_images = gr.Checkbox(
|
2684 |
label="๐จ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง",
|
2685 |
value=False,
|
2686 |
-
info="
|
2687 |
)
|
2688 |
|
2689 |
reference_files = gr.File(
|
@@ -2732,19 +2797,17 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
2732 |
5. **์ฐธ๊ณ ์๋ฃ ์
๋ก๋**: PDF, CSV, TXT ํ์ผ ์ง์
|
2733 |
6. **์์ฑ ๋ฒํผ ํด๋ฆญ**: AI๊ฐ ์๋์ผ๋ก PPT ์์ฑ
|
2734 |
|
2735 |
-
### ๐จ
|
2736 |
-
-
|
2737 |
-
-
|
2738 |
-
-
|
2739 |
-
-
|
2740 |
-
- **Synoptic Chart**: ์ ์ฒด ๊ฐ์์ ์์ฝ์ ํ๋์ ๋ณด์ฌ์ฃผ๋ ์ฐจํธ
|
2741 |
-
- **์๋ ๊ฐ์ง**: ์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๋ถ์ํ์ฌ ์ ์ ํ ๋ค์ด์ด๊ทธ๋จ ํ์
์๋ ์ ํ
|
2742 |
|
2743 |
### ๐ก ๊ณ ๊ธ ํ
|
2744 |
-
- **๋ค์ด์ด๊ทธ๋จ
|
2745 |
-
-
|
2746 |
-
-
|
2747 |
-
-
|
2748 |
"""
|
2749 |
)
|
2750 |
|
@@ -2785,6 +2848,13 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
2785 |
|
2786 |
# Initialize APIs on startup
|
2787 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2788 |
# Try to initialize APIs in parallel
|
2789 |
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
|
2790 |
futures = []
|
@@ -2807,4 +2877,4 @@ if __name__ == "__main__":
|
|
2807 |
else:
|
2808 |
logger.warning("โ ๏ธ ๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค. ๋ค์ด์ด๊ทธ๋จ ๊ธฐ๋ฅ์ด ๋นํ์ฑํ๋ฉ๋๋ค.")
|
2809 |
|
2810 |
-
demo.launch()
|
|
|
47 |
DIAGRAM_GENERATORS_AVAILABLE = False
|
48 |
logger.warning(f"๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค: {e}")
|
49 |
|
50 |
+
# ํ๊ธ ํฐํธ ๊ฒฝ๋ก ์ค์
|
51 |
+
KOREAN_FONT_PATH = os.path.join(os.path.dirname(__file__), "NanumGothic-Regular.ttf")
|
52 |
+
if not os.path.exists(KOREAN_FONT_PATH):
|
53 |
+
logger.warning(f"ํ๊ธ ํฐํธ ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {KOREAN_FONT_PATH}")
|
54 |
+
|
55 |
##############################################################################
|
56 |
# API Configuration
|
57 |
##############################################################################
|
|
|
111 |
FLUX_API_ENABLED = False
|
112 |
return False
|
113 |
|
|
|
|
|
|
|
|
|
|
|
114 |
##############################################################################
|
115 |
# Design Themes and Color Schemes
|
116 |
##############################################################################
|
|
|
350 |
return 'โถ๏ธ'
|
351 |
|
352 |
##############################################################################
|
353 |
+
# Diagram Type Detection with Priority Score
|
354 |
##############################################################################
|
355 |
+
def detect_diagram_type_with_score(title: str, content: str) -> Tuple[Optional[str], float]:
|
356 |
+
"""์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๋ถ์ํ์ฌ ์ ์ ํ ๋ค์ด์ด๊ทธ๋จ ํ์
๊ณผ ํ์๋ ์ ์ ๊ฒฐ์ """
|
357 |
combined_text = f"{title} {content}".lower()
|
358 |
|
359 |
+
# ๊ฐ ๋ค์ด์ด๊ทธ๋จ ํ์
๋ณ ํค์๋์ ๊ฐ์ค์น
|
360 |
+
diagram_scores = {
|
361 |
+
"Process Flow": 0,
|
362 |
+
"WBS Diagram": 0,
|
363 |
+
"Concept Map": 0,
|
364 |
+
"Radial Diagram": 0,
|
365 |
+
"Synoptic Chart": 0
|
366 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
|
368 |
+
# Process Flow keywords with weights
|
369 |
+
if any(word in combined_text for word in ['ํ๋ก์ธ์ค', 'process', '์ ์ฐจ', 'procedure']):
|
370 |
+
diagram_scores["Process Flow"] += 3
|
371 |
+
if any(word in combined_text for word in ['๋จ๊ณ', 'step', 'flow', 'ํ๋ฆ']):
|
372 |
+
diagram_scores["Process Flow"] += 2
|
373 |
+
if any(word in combined_text for word in ['์ํฌํ๋ก์ฐ', 'workflow', '์์', 'sequence']):
|
374 |
+
diagram_scores["Process Flow"] += 2
|
375 |
+
|
376 |
+
# WBS keywords with weights
|
377 |
+
if any(word in combined_text for word in ['wbs', '์์
๋ถํด', 'ํ๋ก์ ํธ', 'project']):
|
378 |
+
diagram_scores["WBS Diagram"] += 3
|
379 |
+
if any(word in combined_text for word in ['์
๋ฌด๋ถํด', 'breakdown', '๊ตฌ์กฐ๋', '์์
๊ตฌ์กฐ']):
|
380 |
+
diagram_scores["WBS Diagram"] += 2
|
381 |
+
|
382 |
+
# Concept Map keywords with weights
|
383 |
+
if any(word in combined_text for word in ['๊ฐ๋
', 'concept', '๊ด๊ณ', 'relationship']):
|
384 |
+
diagram_scores["Concept Map"] += 3
|
385 |
+
if any(word in combined_text for word in ['์ฐ๊ด', 'connection', '๋ง์ธ๋๋งต', 'mindmap']):
|
386 |
+
diagram_scores["Concept Map"] += 2
|
387 |
+
if any(word in combined_text for word in ['๊ตฌ์กฐ', 'structure', '์ฒด๊ณ', 'system']):
|
388 |
+
diagram_scores["Concept Map"] += 1
|
389 |
+
|
390 |
+
# Radial Diagram keywords with weights
|
391 |
+
if any(word in combined_text for word in ['์ค์ฌ', 'central', '๋ฐฉ์ฌํ', 'radial']):
|
392 |
+
diagram_scores["Radial Diagram"] += 3
|
393 |
+
if any(word in combined_text for word in ['ํต์ฌ', 'core', '์ฃผ์', 'main']):
|
394 |
+
diagram_scores["Radial Diagram"] += 2
|
395 |
+
|
396 |
+
# Synoptic Chart keywords with weights
|
397 |
+
if any(word in combined_text for word in ['๊ฐ์', 'overview', '์ ์ฒด', 'overall']):
|
398 |
+
diagram_scores["Synoptic Chart"] += 3
|
399 |
+
if any(word in combined_text for word in ['์์ฝ', 'summary', '์๋ํฑ', 'synoptic']):
|
400 |
+
diagram_scores["Synoptic Chart"] += 2
|
401 |
+
|
402 |
+
# ์ถ๊ฐ ์ ์ ๋ถ์ฌ: ๋ฆฌ์คํธ๋ ๊ตฌ์กฐํ๋ ๋ด์ฉ์ด ๋ง์ ๊ฒฝ์ฐ
|
403 |
if content.count('\n-') > 3 or content.count('\nโข') > 3:
|
404 |
+
diagram_scores["Concept Map"] += 1
|
405 |
+
if any(char in content for char in ['1.', '2.', '3.', 'โ ', 'โก', 'โข']):
|
406 |
+
diagram_scores["Process Flow"] += 1
|
|
|
407 |
|
408 |
+
# ๊ฐ์ฅ ๋์ ์ ์์ ๋ค์ด์ด๊ทธ๋จ ํ์
์ ํ
|
409 |
+
max_score = max(diagram_scores.values())
|
410 |
+
if max_score > 0:
|
411 |
+
best_type = max(diagram_scores.items(), key=lambda x: x[1])[0]
|
412 |
+
# ํ์๋ ์ ์ ๊ณ์ฐ (0-1 ๋ฒ์)
|
413 |
+
necessity_score = min(max_score / 5.0, 1.0) # ์ต๋ 5์ ์ 1.0์ผ๋ก ์ ๊ทํ
|
414 |
+
return best_type, necessity_score
|
415 |
+
|
416 |
+
return None, 0.0
|
417 |
|
418 |
##############################################################################
|
419 |
# Generate Diagram JSON using LLM
|
|
|
423 |
if not FRIENDLI_TOKEN:
|
424 |
return None
|
425 |
|
426 |
+
# ๋ค์ด์ด๊ทธ๋จ ํ์
๋ณ JSON ๊ตฌ์กฐ ๊ฐ์ด๋
|
427 |
json_guides = {
|
428 |
"Concept Map": """Generate a JSON for a concept map with the EXACT following structure:
|
429 |
{
|
|
|
555 |
return None
|
556 |
|
557 |
##############################################################################
|
558 |
+
# Generate Diagram using Local Generators with Korean Font
|
559 |
##############################################################################
|
560 |
def generate_diagram_locally(json_data: str, diagram_type: str, output_format: str = "png") -> Optional[str]:
|
561 |
+
"""๋ก์ปฌ ์์ฑ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด์ด๊ทธ๋จ ์์ฑ (ํ๊ธ ํฐํธ ์ ์ฉ)"""
|
562 |
if not DIAGRAM_GENERATORS_AVAILABLE:
|
563 |
logger.error("๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฌ์ฉํ ์ ์์ต๋๋ค")
|
564 |
return None
|
565 |
|
566 |
try:
|
567 |
+
# ํ๊ธ ํฐํธ ๊ฒฝ๋ก๋ฅผ ํ๊ฒฝ๋ณ์๋ก ์ค์
|
568 |
+
os.environ['KOREAN_FONT_PATH'] = KOREAN_FONT_PATH
|
569 |
+
|
570 |
# ์ ์ ํ ์์ฑ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด์ด๊ทธ๋จ ์์ฑ
|
571 |
if diagram_type == "Concept Map":
|
572 |
result = generate_concept_map(json_data, output_format)
|
|
|
1099 |
return image_3d, image_photo
|
1100 |
|
1101 |
##############################################################################
|
1102 |
+
# PPT Generation Functions - FIXED WITH LIMITED DIAGRAMS AND IMAGES
|
1103 |
##############################################################################
|
1104 |
def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> list:
|
1105 |
"""Parse LLM response to extract slide content - FIXED VERSION"""
|
|
|
1527 |
include_diagrams: bool = False,
|
1528 |
include_flux_images: bool = False
|
1529 |
) -> str:
|
1530 |
+
"""Create advanced PPT file with limited visuals (max 6 images + 2 diagrams)"""
|
1531 |
if not PPTX_AVAILABLE:
|
1532 |
raise ImportError("python-pptx library is required")
|
1533 |
|
|
|
1538 |
prs.slide_width = Inches(10)
|
1539 |
prs.slide_height = Inches(5.625)
|
1540 |
|
1541 |
+
# ์ด๋ฏธ์ง์ ๋ค์ด์ด๊ทธ๋จ ์นด์ดํฐ ๋ฐ ์ถ์
|
1542 |
+
image_count_3d = 0
|
1543 |
+
image_count_flux = 0
|
1544 |
+
diagram_count = 0
|
1545 |
+
max_images_per_api = 3
|
1546 |
+
max_diagrams = 2
|
1547 |
+
|
1548 |
+
# ๋ค์ด์ด๊ทธ๋จ์ด ํ์ํ ์ฌ๋ผ์ด๋๋ฅผ ๋ฏธ๋ฆฌ ๋ถ์
|
1549 |
+
diagram_candidates = []
|
1550 |
+
if include_diagrams:
|
1551 |
+
for i, slide_data in enumerate(slides_data):
|
1552 |
+
title = slide_data.get('title', '')
|
1553 |
+
content = slide_data.get('content', '')
|
1554 |
+
diagram_type, score = detect_diagram_type_with_score(title, content)
|
1555 |
+
if diagram_type and score > 0:
|
1556 |
+
diagram_candidates.append((i, diagram_type, score))
|
1557 |
+
|
1558 |
+
# ํ์๋ ์ ์๊ฐ ๋์ ์์ผ๋ก ์ ๋ ฌํ๊ณ ์์ 2๊ฐ๋ง ์ ํ
|
1559 |
+
diagram_candidates.sort(key=lambda x: x[2], reverse=True)
|
1560 |
+
diagram_candidates = diagram_candidates[:max_diagrams]
|
1561 |
+
diagram_slide_indices = [x[0] for x in diagram_candidates]
|
1562 |
+
else:
|
1563 |
+
diagram_slide_indices = []
|
1564 |
+
|
1565 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1566 |
+
# 1) ์ ๋ชฉ ์ฌ๋ผ์ด๋(ํ์ง) ์์ฑ
|
1567 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1568 |
title_slide_layout = prs.slide_layouts[0]
|
1569 |
slide = prs.slides.add_slide(title_slide_layout)
|
|
|
1578 |
if title_shape:
|
1579 |
title_shape.left = Inches(0.5)
|
1580 |
title_shape.width = prs.slide_width - Inches(1)
|
1581 |
+
title_shape.top = Inches(1.0)
|
1582 |
title_shape.height = Inches(1.2)
|
1583 |
|
1584 |
tf = title_shape.text_frame
|
|
|
1594 |
if subtitle_shape:
|
1595 |
subtitle_shape.left = Inches(0.5)
|
1596 |
subtitle_shape.width = prs.slide_width - Inches(1)
|
1597 |
+
subtitle_shape.top = Inches(2.2)
|
1598 |
subtitle_shape.height = Inches(0.9)
|
1599 |
|
1600 |
tf2 = subtitle_shape.text_frame
|
|
|
1606 |
p2.font.color.rgb = RGBColor(255, 255, 255)
|
1607 |
p2.alignment = PP_ALIGN.CENTER
|
1608 |
|
1609 |
+
# ํ์ง ์ด๋ฏธ์ง (์นด์ดํธ์ ํฌํจ)
|
1610 |
if include_ai_image and (AI_IMAGE_ENABLED or FLUX_API_ENABLED):
|
1611 |
logger.info("Generating AI cover images via parallel APIs...")
|
1612 |
|
|
|
1613 |
prompt_3d, prompt_photo = generate_cover_image_prompts(topic, slides_data)
|
|
|
|
|
1614 |
image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
|
1615 |
|
1616 |
+
# 3D ์ฐ์ ์ ํ
|
1617 |
+
ai_image_path = None
|
1618 |
+
if image_3d and image_count_3d < max_images_per_api:
|
1619 |
+
ai_image_path = image_3d
|
1620 |
+
image_count_3d += 1
|
1621 |
+
elif image_photo and image_count_flux < max_images_per_api:
|
1622 |
+
ai_image_path = image_photo
|
1623 |
+
image_count_flux += 1
|
1624 |
|
1625 |
if ai_image_path and os.path.exists(ai_image_path):
|
1626 |
try:
|
1627 |
img = Image.open(ai_image_path)
|
1628 |
img_width, img_height = img.size
|
1629 |
|
|
|
1630 |
max_width = Inches(3.5)
|
1631 |
max_height = Inches(2.5)
|
1632 |
|
|
|
1638 |
img_h = max_height
|
1639 |
img_w = max_height / ratio
|
1640 |
|
|
|
1641 |
left = prs.slide_width - img_w - Inches(0.5)
|
1642 |
top = prs.slide_height - img_h - Inches(0.8)
|
1643 |
|
|
|
1648 |
pic.shadow.distance = Pt(8)
|
1649 |
pic.shadow.angle = 45
|
1650 |
|
|
|
1651 |
caption_box = slide.shapes.add_textbox(
|
1652 |
left, top - Inches(0.3),
|
1653 |
img_w, Inches(0.3)
|
|
|
1670 |
except Exception as e:
|
1671 |
logger.error(f"Failed to add cover image: {e}")
|
1672 |
|
|
|
1673 |
add_decorative_shapes(slide, theme)
|
1674 |
|
1675 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1676 |
+
# 2) ์ปจํ
์ธ ์ฌ๋ผ์ด๋ ์์ฑ
|
1677 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1678 |
for i, slide_data in enumerate(slides_data):
|
1679 |
layout_type = slide_data.get('layout', 'title_content')
|
1680 |
|
|
|
1681 |
logger.info(f"Creating slide {i+1}: {slide_data.get('title', 'No title')}")
|
1682 |
logger.debug(f"Content length: {len(slide_data.get('content', ''))}")
|
1683 |
|
|
|
1699 |
# Set title
|
1700 |
if slide.shapes.title:
|
1701 |
slide.shapes.title.text = slide_data.get('title', '์ ๋ชฉ ์์')
|
|
|
1702 |
try:
|
1703 |
title_text_frame = slide.shapes.title.text_frame
|
1704 |
if title_text_frame and title_text_frame.paragraphs:
|
1705 |
for paragraph in title_text_frame.paragraphs:
|
1706 |
if layout_type == 'section_header':
|
1707 |
+
paragraph.font.size = Pt(28)
|
1708 |
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
1709 |
paragraph.alignment = PP_ALIGN.CENTER
|
1710 |
else:
|
1711 |
+
paragraph.font.size = Pt(24)
|
1712 |
paragraph.font.color.rgb = theme['colors']['primary']
|
1713 |
paragraph.font.bold = True
|
1714 |
paragraph.font.name = theme['fonts']['title']
|
1715 |
except Exception as e:
|
1716 |
logger.warning(f"Title font sizing failed: {e}")
|
1717 |
|
1718 |
+
# ์ฌ๋ผ์ด๋ ์ ๋ณด
|
1719 |
slide_title = slide_data.get('title', '')
|
1720 |
slide_content = slide_data.get('content', '')
|
1721 |
|
|
|
1724 |
['๊ฒฐ๋ก ', 'conclusion', '์์ฝ', 'summary', 'ํต์ฌ', 'key',
|
1725 |
'๋ง๋ฌด๋ฆฌ', 'closing', '์ ๋ฆฌ', 'takeaway', '์์ฌ์ ', 'implication'])
|
1726 |
|
1727 |
+
# ์๊ฐ์ ์์ ์ถ๊ฐ ์ฌ๋ถ ๊ฒฐ์
|
1728 |
should_add_visual = False
|
1729 |
visual_type = None
|
1730 |
|
1731 |
+
# 1. ๋ค์ด์ด๊ทธ๋จ ์ฐ์ ํ์ธ
|
1732 |
+
if i in diagram_slide_indices and diagram_count < max_diagrams:
|
1733 |
+
should_add_visual = True
|
1734 |
+
diagram_info = next(x for x in diagram_candidates if x[0] == i)
|
1735 |
+
visual_type = ('diagram', diagram_info[1])
|
1736 |
+
diagram_count += 1
|
1737 |
+
# 2. ๊ฒฐ๋ก ์ฌ๋ผ์ด๋๋ ์ด๋ฏธ์ง ์ถ๊ฐ (์ด๋ฏธ์ง ์ ํ ๋ด์์)
|
1738 |
+
elif is_conclusion_slide and include_flux_images and (image_count_3d + image_count_flux) < (max_images_per_api * 2):
|
1739 |
should_add_visual = True
|
1740 |
visual_type = ('conclusion_images', None)
|
1741 |
+
# 3. ์ผ๋ฐ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง (์ด๋ฏธ์ง ์ ํ ๋ด์์, 3~4์ฅ๋ง๋ค ํ๋์ฉ)
|
1742 |
+
elif include_flux_images and (image_count_3d + image_count_flux) < (max_images_per_api * 2) and i % 3 == 0:
|
|
|
|
|
|
|
|
|
1743 |
should_add_visual = True
|
1744 |
visual_type = ('diverse_images', None)
|
1745 |
|
|
|
1769 |
|
1770 |
if visual_type[0] == 'diagram' and DIAGRAM_GENERATORS_AVAILABLE:
|
1771 |
# ๋ค์ด์ด๊ทธ๋จ ์์ฑ
|
1772 |
+
logger.info(f"Generating {visual_type[1]} for slide {i+1} (Diagram {diagram_count}/{max_diagrams})")
|
1773 |
diagram_json = generate_diagram_json(slide_title, slide_content, visual_type[1])
|
1774 |
|
1775 |
if diagram_json:
|
1776 |
diagram_path = generate_diagram_locally(diagram_json, visual_type[1], "png")
|
1777 |
if diagram_path and os.path.exists(diagram_path):
|
1778 |
try:
|
|
|
1779 |
pic = slide.shapes.add_picture(
|
1780 |
diagram_path,
|
1781 |
Inches(5.2), Inches(1.5),
|
|
|
1783 |
)
|
1784 |
visual_added = True
|
1785 |
|
|
|
1786 |
caption_box = slide.shapes.add_textbox(
|
1787 |
Inches(5.2), Inches(4.6), Inches(4.3), Inches(0.3)
|
1788 |
)
|
|
|
1793 |
caption_p.font.color.rgb = theme['colors']['secondary']
|
1794 |
caption_p.alignment = PP_ALIGN.CENTER
|
1795 |
|
|
|
1796 |
try:
|
1797 |
os.unlink(diagram_path)
|
1798 |
except:
|
|
|
1801 |
logger.error(f"Failed to add diagram: {e}")
|
1802 |
|
1803 |
elif visual_type[0] == 'conclusion_images':
|
1804 |
+
# ๊ฒฐ๋ก ์ฌ๋ผ์ด๋์ฉ ์ด๋ฏธ์ง ์์ฑ
|
1805 |
logger.info(f"Generating conclusion images for slide {i+1}")
|
1806 |
prompt_3d, prompt_photo = generate_conclusion_image_prompts(slide_title, slide_content)
|
|
|
1807 |
|
1808 |
+
image_3d = None
|
1809 |
+
image_photo = None
|
1810 |
+
|
1811 |
+
if image_count_3d < max_images_per_api:
|
1812 |
+
image_3d = generate_ai_image_via_3d_api(prompt_3d)
|
1813 |
+
if image_count_flux < max_images_per_api:
|
1814 |
+
image_photo = generate_flux_image_via_api(prompt_photo)
|
1815 |
+
|
1816 |
+
# ์ ํ
|
1817 |
+
selected_image = None
|
1818 |
+
if image_photo and image_count_flux < max_images_per_api:
|
1819 |
+
selected_image = image_photo
|
1820 |
+
image_count_flux += 1
|
1821 |
+
elif image_3d and image_count_3d < max_images_per_api:
|
1822 |
+
selected_image = image_3d
|
1823 |
+
image_count_3d += 1
|
1824 |
|
1825 |
if selected_image and os.path.exists(selected_image):
|
1826 |
try:
|
|
|
1831 |
)
|
1832 |
visual_added = True
|
1833 |
|
|
|
1834 |
caption_box = slide.shapes.add_textbox(
|
1835 |
Inches(5.2), Inches(4.6), Inches(4.3), Inches(0.3)
|
1836 |
)
|
|
|
1841 |
caption_p.font.color.rgb = theme['colors']['secondary']
|
1842 |
caption_p.alignment = PP_ALIGN.CENTER
|
1843 |
|
|
|
1844 |
for temp_path in [image_3d, image_photo]:
|
1845 |
if temp_path and os.path.exists(temp_path):
|
1846 |
try:
|
|
|
1851 |
logger.error(f"Failed to add conclusion image: {e}")
|
1852 |
|
1853 |
elif visual_type[0] == 'diverse_images':
|
1854 |
+
# ๋ค์ํ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง ์์ฑ (API๋ณ ์ ํ ํ์ธ)
|
1855 |
+
logger.info(f"Generating diverse images for slide {i+1} (3D: {image_count_3d}/{max_images_per_api}, FLUX: {image_count_flux}/{max_images_per_api})")
|
1856 |
prompt_3d, prompt_photo = generate_diverse_prompt(slide_title, slide_content, i)
|
|
|
1857 |
|
1858 |
+
selected_image = None
|
1859 |
+
|
1860 |
+
# API๋ณ ์ ํ์ ๋ฐ๋ผ ์ด๋ฏธ์ง ์์ฑ
|
1861 |
+
if i % 2 == 0 and image_count_3d < max_images_per_api:
|
1862 |
+
image_3d = generate_ai_image_via_3d_api(prompt_3d)
|
1863 |
+
if image_3d:
|
1864 |
+
selected_image = image_3d
|
1865 |
+
image_count_3d += 1
|
1866 |
+
elif image_count_flux < max_images_per_api:
|
1867 |
+
image_photo = generate_flux_image_via_api(prompt_photo)
|
1868 |
+
if image_photo:
|
1869 |
+
selected_image = image_photo
|
1870 |
+
image_count_flux += 1
|
1871 |
|
1872 |
if selected_image and os.path.exists(selected_image):
|
1873 |
try:
|
|
|
1878 |
)
|
1879 |
visual_added = True
|
1880 |
|
1881 |
+
if selected_image:
|
1882 |
+
os.unlink(selected_image)
|
|
|
|
|
|
|
|
|
|
|
1883 |
except Exception as e:
|
1884 |
logger.error(f"Failed to add slide image: {e}")
|
1885 |
|
|
|
1897 |
else:
|
1898 |
# ๊ธฐ๋ณธ ๋ ์ด์์ (์๊ฐ์ ์์ ์์)
|
1899 |
if layout_type == 'section_header':
|
|
|
1900 |
content = slide_data.get('content', '')
|
1901 |
if content:
|
1902 |
logger.info(f"Adding content to section header slide {i+1}: {content[:50]}...")
|
|
|
1914 |
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
1915 |
paragraph.alignment = PP_ALIGN.CENTER
|
1916 |
|
|
|
1917 |
line = slide.shapes.add_shape(
|
1918 |
MSO_SHAPE.RECTANGLE, Inches(3), Inches(3.2), Inches(4), Pt(4)
|
1919 |
)
|
|
|
1940 |
left_tf.word_wrap = True
|
1941 |
force_font_size(left_tf, 14, theme)
|
1942 |
|
|
|
1943 |
for paragraph in left_tf.paragraphs:
|
1944 |
text = paragraph.text.strip()
|
1945 |
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
|
|
1960 |
right_tf.word_wrap = True
|
1961 |
force_font_size(right_tf, 14, theme)
|
1962 |
|
|
|
1963 |
for paragraph in right_tf.paragraphs:
|
1964 |
text = paragraph.text.strip()
|
1965 |
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
|
|
1979 |
|
1980 |
if content and content.strip():
|
1981 |
textbox = slide.shapes.add_textbox(
|
1982 |
+
Inches(0.5), Inches(1.5), Inches(9), Inches(3.5)
|
|
|
|
|
|
|
1983 |
)
|
1984 |
|
1985 |
tf = textbox.text_frame
|
|
|
2030 |
except Exception as e:
|
2031 |
logger.warning(f"Failed to add slide notes: {e}")
|
2032 |
|
2033 |
+
# Add slide number
|
2034 |
slide_number_bg = slide.shapes.add_shape(
|
2035 |
MSO_SHAPE.ROUNDED_RECTANGLE,
|
2036 |
Inches(8.3), Inches(5.0), Inches(1.5), Inches(0.5)
|
|
|
2045 |
)
|
2046 |
slide_number_frame = slide_number_box.text_frame
|
2047 |
slide_number_frame.text = f"{i + 1} / {len(slides_data)}"
|
2048 |
+
slide_number_frame.paragraphs[0].font.size = Pt(10)
|
2049 |
slide_number_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
2050 |
slide_number_frame.paragraphs[0].font.bold = False
|
2051 |
slide_number_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
2052 |
|
|
|
2053 |
if i % 2 == 0:
|
2054 |
accent_shape = slide.shapes.add_shape(
|
2055 |
MSO_SHAPE.OVAL,
|
|
|
2060 |
accent_shape.fill.fore_color.rgb = theme['colors']['accent']
|
2061 |
accent_shape.line.fill.background()
|
2062 |
|
2063 |
+
# ์ด๋ฏธ์ง ์์ฑ ๋ก๊ทธ
|
2064 |
+
logger.info(f"Total images generated - 3D: {image_count_3d}, FLUX: {image_count_flux}, Diagrams: {diagram_count}")
|
2065 |
+
|
2066 |
+
# Add thank you slide
|
2067 |
thank_you_layout = prs.slide_layouts[5] if len(prs.slide_layouts) > 5 else prs.slide_layouts[0]
|
2068 |
thank_you_slide = prs.slides.add_slide(thank_you_layout)
|
2069 |
|
|
|
2070 |
add_gradient_background(thank_you_slide, theme['colors']['secondary'], theme['colors']['primary'])
|
2071 |
|
2072 |
if thank_you_slide.shapes.title:
|
2073 |
thank_you_slide.shapes.title.text = "๊ฐ์ฌํฉ๋๋ค"
|
2074 |
try:
|
2075 |
if thank_you_slide.shapes.title.text_frame and thank_you_slide.shapes.title.text_frame.paragraphs:
|
2076 |
+
thank_you_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(36)
|
2077 |
thank_you_slide.shapes.title.text_frame.paragraphs[0].font.bold = True
|
2078 |
thank_you_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
2079 |
thank_you_slide.shapes.title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
2080 |
except Exception as e:
|
2081 |
logger.warning(f"Thank you slide styling failed: {e}")
|
2082 |
|
|
|
2083 |
info_box = thank_you_slide.shapes.add_textbox(
|
2084 |
Inches(2), Inches(3.5), Inches(6), Inches(1)
|
2085 |
)
|
2086 |
info_tf = info_box.text_frame
|
2087 |
info_tf.text = "AI๋ก ์์ฑ๋ ํ๋ ์ ํ
์ด์
"
|
2088 |
+
info_tf.paragraphs[0].font.size = Pt(18)
|
2089 |
info_tf.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
2090 |
info_tf.paragraphs[0].alignment = PP_ALIGN.CENTER
|
2091 |
|
|
|
2316 |
yield None, "โ ๏ธ ๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค. ๋ค์ด์ด๊ทธ๋จ ์์ด ์งํํฉ๋๋ค.", ""
|
2317 |
include_diagrams = False
|
2318 |
|
2319 |
+
# ํ๊ธ ํฐํธ ํ์ธ
|
2320 |
+
if include_diagrams and not os.path.exists(KOREAN_FONT_PATH):
|
2321 |
+
yield None, f"โ ๏ธ ํ๊ธ ํฐํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: {KOREAN_FONT_PATH}\n๋ค์ด์ด๊ทธ๋จ์ ํ๊ธ์ด ๊นจ์ง ์ ์์ต๋๋ค.", ""
|
2322 |
+
|
2323 |
# Process reference files if provided
|
2324 |
additional_context = ""
|
2325 |
chart_data = None
|
|
|
2392 |
|
2393 |
# AI ์ด๋ฏธ์ง ๋ฐ ๋ค์ด์ด๊ทธ๋จ ์์ฑ ์๋ฆผ
|
2394 |
visual_features = []
|
2395 |
+
if include_ai_image and (AI_IMAGE_ENABLED or FLUX_API_ENABLED):
|
2396 |
visual_features.append("AI 3D ํ์ง ์ด๋ฏธ์ง")
|
2397 |
if include_diagrams and DIAGRAM_GENERATORS_AVAILABLE:
|
2398 |
+
visual_features.append("๋ค์ด์ด๊ทธ๋จ (์ต๋ 2๊ฐ)")
|
2399 |
if include_flux_images and FLUX_API_ENABLED:
|
2400 |
+
visual_features.append("AI ์์ฑ ์ด๋ฏธ์ง (๊ฐ API๋ณ ์ต๋ 3๊ฐ)")
|
2401 |
|
2402 |
if visual_features:
|
2403 |
yield None, f"๐ ์ฌ๋ผ์ด๋ ์์ฑ ์๋ฃ!\n\n๐จ ์์ฑ ์ค: {', '.join(visual_features)}... (์๊ฐ์ด ์์๋ ์ ์์ต๋๋ค)", llm_response
|
|
|
2419 |
success_msg += f"๐จ ๋์์ธ ํ
๋ง: {DESIGN_THEMES[design_theme]['name']}\n"
|
2420 |
success_msg += f"๐ ๋ ์ด์์ ์คํ์ผ: {layout_style}\n"
|
2421 |
|
2422 |
+
if include_ai_image and (AI_IMAGE_ENABLED or FLUX_API_ENABLED):
|
2423 |
success_msg += f"๐ผ๏ธ AI ์์ฑ ํ์ง ์ด๋ฏธ์ง ํฌํจ\n"
|
2424 |
if include_diagrams and DIAGRAM_GENERATORS_AVAILABLE:
|
2425 |
+
success_msg += f"๐ AI ์์ฑ ๋ค์ด์ด๊ทธ๋จ ํฌํจ (์ต๋ 2๊ฐ)\n"
|
2426 |
if include_flux_images and FLUX_API_ENABLED:
|
2427 |
+
success_msg += f"๐จ AI ์์ฑ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง ํฌํจ (API๋ณ ์ต๋ 3๊ฐ)\n"
|
2428 |
|
2429 |
success_msg += f"๐ ์์ฑ๋ ์ฌ๋ผ์ด๋:\n"
|
2430 |
|
|
|
2742 |
include_diagrams = gr.Checkbox(
|
2743 |
label="๐ AI ๋ค์ด์ด๊ทธ๋จ",
|
2744 |
value=False,
|
2745 |
+
info="ํ์๋๊ฐ ๋์ 2๊ฐ ์ฌ๋ผ์ด๋์ ๋ค์ด์ด๊ทธ๋จ ์๋ ์์ฑ"
|
2746 |
)
|
2747 |
|
2748 |
include_flux_images = gr.Checkbox(
|
2749 |
label="๐จ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง",
|
2750 |
value=False,
|
2751 |
+
info="์ฃผ์ ์ฌ๋ผ์ด๋์ FLUX ์ด๋ฏธ์ง ์ถ๊ฐ (API๋ณ ์ต๋ 3๊ฐ)"
|
2752 |
)
|
2753 |
|
2754 |
reference_files = gr.File(
|
|
|
2797 |
5. **์ฐธ๊ณ ์๋ฃ ์
๋ก๋**: PDF, CSV, TXT ํ์ผ ์ง์
|
2798 |
6. **์์ฑ ๋ฒํผ ํด๋ฆญ**: AI๊ฐ ์๋์ผ๋ก PPT ์์ฑ
|
2799 |
|
2800 |
+
### ๐จ ๊ฐ์ ๋ ๊ธฐ๋ฅ - ์ต์ ํ๋ ์๊ฐ ์์
|
2801 |
+
- **๋ค์ด์ด๊ทธ๋จ ๊ฐ์ ์ ํ**: ์ ์ฒด ์ฌ๋ผ์ด๋ ์ค ํ์๋๊ฐ ๊ฐ์ฅ ๋์ 2๊ฐ ์ฌ๋ผ์ด๋์๋ง ๋ค์ด์ด๊ทธ๋จ ์์ฑ
|
2802 |
+
- **์ด๋ฏธ์ง ๊ฐ์ ์ ํ**: ๊ฐ API๋ณ๋ก ์ต๋ 3๊ฐ์ฉ, ์ด 6๊ฐ์ AI ์์ฑ ์ด๋ฏธ์ง
|
2803 |
+
- **ํ๊ธ ํฐํธ ์ง์**: NanumGothic-Regular.ttf๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด์ด๊ทธ๋จ์ ํ๊ธ ๊นจ์ง ๋ฌธ์ ํด๊ฒฐ
|
2804 |
+
- **์ค๋งํธ ๋ฐฐ์น**: ๋ค์ด์ด๊ทธ๋จ๊ณผ ์ด๋ฏธ์ง๋ ํ
์คํธ์ ๊ฒน์น์ง ์๋๋ก ์ฐ์ธก์ ์๋ ๋ฐฐ์น
|
|
|
|
|
2805 |
|
2806 |
### ๐ก ๊ณ ๊ธ ํ
|
2807 |
+
- **๋ค์ด์ด๊ทธ๋จ ์ฐ์ ๏ฟฝ๏ฟฝ์**: ํ๋ก์ธ์ค, WBS, ๊ฐ๋
๋ ๋ฑ์ ํค์๋๊ฐ ์๋ ์ฌ๋ผ์ด๋๊ฐ ์ฐ์ ์ ํ๋ฉ๋๋ค
|
2808 |
+
- **์ด๋ฏธ์ง ๋ถ๋ฐฐ**: 3D ์คํ์ผ๊ณผ ํฌํ ๋ฆฌ์ผ๋ฆฌ์คํฑ ์ด๋ฏธ์ง๊ฐ ๊ท ํ์๊ฒ ๋ถ๋ฐฐ๋ฉ๋๋ค
|
2809 |
+
- **ํ์ง ์ด๋ฏธ์ง**: ์ฃผ์ ์ ๋ง๋ ํ๋ฆฌ๋ฏธ์ ํ์ง ์ด๋ฏธ์ง๊ฐ ์๋ ์์ฑ๋ฉ๋๋ค
|
2810 |
+
- **ํ๊ธ ์ง์**: app.py์ ๊ฐ์ ๊ฒฝ๋ก์ NanumGothic-Regular.ttf ํ์ผ์ ๋ฐฐ์นํ์ธ์
|
2811 |
"""
|
2812 |
)
|
2813 |
|
|
|
2848 |
|
2849 |
# Initialize APIs on startup
|
2850 |
if __name__ == "__main__":
|
2851 |
+
# ํ๊ธ ํฐํธ ํ์ธ
|
2852 |
+
if os.path.exists(KOREAN_FONT_PATH):
|
2853 |
+
logger.info(f"โ
ํ๊ธ ํฐํธ ํ์ผ์ ์ฐพ์์ต๋๋ค: {KOREAN_FONT_PATH}")
|
2854 |
+
else:
|
2855 |
+
logger.warning(f"โ ๏ธ ํ๊ธ ํฐํธ ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {KOREAN_FONT_PATH}")
|
2856 |
+
logger.warning("๋ค์ด์ด๊ทธ๋จ์์ ํ๊ธ์ด ๊นจ์ง ์ ์์ต๋๋ค. NanumGothic-Regular.ttf ํ์ผ์ app.py์ ๊ฐ์ ๊ฒฝ๋ก์ ๋ฐฐ์นํ์ธ์.")
|
2857 |
+
|
2858 |
# Try to initialize APIs in parallel
|
2859 |
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
|
2860 |
futures = []
|
|
|
2877 |
else:
|
2878 |
logger.warning("โ ๏ธ ๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค. ๋ค์ด์ด๊ทธ๋จ ๊ธฐ๋ฅ์ด ๋นํ์ฑํ๋ฉ๋๋ค.")
|
2879 |
|
2880 |
+
demo.launch()
|