Spaces:
Running
Running
Update app-BACKUP.py
Browse files- app-BACKUP.py +197 -127
app-BACKUP.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()
|