Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -49,25 +49,47 @@ if not SERPHOUSE_API_KEY:
|
|
49 |
logger.warning("SERPHOUSE_API_KEY not set. Web search functionality will be limited.")
|
50 |
|
51 |
##############################################################################
|
52 |
-
# AI Image Generation API Configuration
|
53 |
##############################################################################
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
|
58 |
-
def
|
59 |
-
"""
|
60 |
-
global
|
61 |
|
62 |
try:
|
63 |
-
logger.info("Connecting to
|
64 |
-
|
65 |
-
|
66 |
-
logger.info("
|
67 |
return True
|
68 |
except Exception as e:
|
69 |
-
logger.error(f"Failed to connect to
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
return False
|
72 |
|
73 |
##############################################################################
|
@@ -308,6 +330,288 @@ def get_emoji_for_content(text: str) -> str:
|
|
308 |
else:
|
309 |
return 'โถ๏ธ'
|
310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
##############################################################################
|
312 |
# Icon and Shape Mappings
|
313 |
##############################################################################
|
@@ -559,114 +863,127 @@ def pdf_to_markdown(pdf_path: str) -> str:
|
|
559 |
return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
|
560 |
|
561 |
##############################################################################
|
562 |
-
# AI Image Generation Functions using API
|
563 |
##############################################################################
|
564 |
def generate_cover_image_prompt(topic: str, slides_data: list) -> str:
|
565 |
-
"""PPT ์ฃผ์ ์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ํ์ง ์ด๋ฏธ์ง ํ๋กฌํํธ ์์ฑ
|
566 |
|
567 |
# ์ฃผ์ ํค์๋ ์ถ์ถ
|
568 |
-
keywords =
|
569 |
-
topic_keywords = extract_keywords(topic, top_k=3)
|
570 |
-
keywords.extend(topic_keywords.split())
|
571 |
-
|
572 |
-
# ๊ฐ ์ฌ๋ผ์ด๋ ์ ๋ชฉ์์ ํค์๋ ์ถ์ถ
|
573 |
-
for slide in slides_data[:5]:
|
574 |
-
title = slide.get('title', '')
|
575 |
-
if title:
|
576 |
-
slide_keywords = extract_keywords(title, top_k=2)
|
577 |
-
keywords.extend(slide_keywords.split())
|
578 |
-
|
579 |
-
unique_keywords = list(dict.fromkeys(keywords))[:5]
|
580 |
|
581 |
# ์ฃผ์ ๋ถ์์ ํตํ ์คํ์ผ ๊ฒฐ์
|
582 |
-
style = "
|
583 |
topic_lower = topic.lower()
|
584 |
|
585 |
if any(word in topic_lower for word in ['๊ธฐ์ ', 'tech', 'ai', '์ธ๊ณต์ง๋ฅ', 'digital', '๋์งํธ']):
|
586 |
-
style = "
|
587 |
elif any(word in topic_lower for word in ['๋น์ฆ๋์ค', 'business', '๊ฒฝ์', 'management']):
|
588 |
-
style = "
|
589 |
elif any(word in topic_lower for word in ['๊ต์ก', 'education', 'ํ์ต', 'learning']):
|
590 |
-
style = "
|
591 |
elif any(word in topic_lower for word in ['ํ๊ฒฝ', 'environment', '์์ฐ', 'nature']):
|
592 |
-
style = "
|
593 |
elif any(word in topic_lower for word in ['์๋ฃ', 'medical', '๊ฑด๊ฐ', 'health']):
|
594 |
-
style = "
|
595 |
elif any(word in topic_lower for word in ['๊ธ์ต', 'finance', 'ํฌ์', 'investment']):
|
596 |
-
style = "
|
597 |
|
598 |
-
#
|
599 |
-
prompt = f"
|
600 |
|
601 |
return prompt
|
602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
604 |
|
605 |
-
def
|
606 |
-
"""API๋ฅผ ํตํด
|
607 |
-
if not
|
608 |
-
logger.warning("
|
609 |
return None
|
610 |
-
|
611 |
try:
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
height, width = 1024.0, 1024.0
|
617 |
-
steps, scales = 8.0, 3.5
|
618 |
-
seed = float(random.randint(0, 1_000_000))
|
619 |
-
logger.info(f"Calling AI image APIโฆ (h={height}, w={width}, steps={steps}, scales={scales}, seed={seed})")
|
620 |
-
|
621 |
-
result = ai_image_client.predict(
|
622 |
-
height=height,
|
623 |
-
width=width,
|
624 |
-
steps=steps,
|
625 |
-
scales=scales,
|
626 |
prompt=prompt,
|
627 |
-
|
628 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
629 |
)
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
634 |
-
|
635 |
-
with Image.open(
|
636 |
png_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
|
637 |
-
|
638 |
-
logger.info(f"
|
639 |
return png_tmp.name
|
640 |
-
|
641 |
-
logger.error(f"PNG ๋ณํ ์คํจ: {e}")
|
642 |
-
return None
|
643 |
-
|
644 |
-
# โโ 3. ๊ฒฐ๊ณผ ์ฒ๋ฆฌ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
645 |
-
image_path = None
|
646 |
-
if isinstance(result, dict):
|
647 |
-
image_path = result.get("path")
|
648 |
-
elif isinstance(result, str):
|
649 |
-
image_path = result
|
650 |
-
|
651 |
-
if image_path and os.path.exists(image_path):
|
652 |
-
ext = os.path.splitext(image_path)[1].lower()
|
653 |
-
if ext == ".png":
|
654 |
-
# ์ด๋ฏธ PNG๋ผ๋ฉด ๊ทธ๋๋ก ๋ณต์ฌ
|
655 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
|
656 |
-
shutil.copy2(image_path, tmp.name)
|
657 |
-
logger.info(f"PNG copied from {image_path} โ {tmp.name}")
|
658 |
-
return tmp.name
|
659 |
-
else:
|
660 |
-
# WEBPยทJPEG ๋ฑ์ PNG๋ก ๋ณํ
|
661 |
-
return _to_png(image_path)
|
662 |
-
|
663 |
-
logger.error(f"Image path not found or invalid: {result}")
|
664 |
return None
|
665 |
-
|
666 |
except Exception as e:
|
667 |
-
logger.error(f"Failed to generate
|
668 |
return None
|
669 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
670 |
|
671 |
##############################################################################
|
672 |
# PPT Generation Functions - FIXED VERSION
|
@@ -1039,9 +1356,11 @@ def create_advanced_ppt_from_content(
|
|
1039 |
topic: str,
|
1040 |
theme_name: str,
|
1041 |
include_charts: bool = False,
|
1042 |
-
include_ai_image: bool = False
|
|
|
|
|
1043 |
) -> str:
|
1044 |
-
"""Create advanced PPT file with consistent visual design and AI
|
1045 |
if not PPTX_AVAILABLE:
|
1046 |
raise ImportError("python-pptx library is required")
|
1047 |
|
@@ -1053,7 +1372,7 @@ def create_advanced_ppt_from_content(
|
|
1053 |
prs.slide_height = Inches(5.625)
|
1054 |
|
1055 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1056 |
-
# 1) ์ ๋ชฉ ์ฌ๋ผ์ด๋(ํ์ง) ์์ฑ
|
1057 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1058 |
title_slide_layout = prs.slide_layouts[0]
|
1059 |
slide = prs.slides.add_slide(title_slide_layout)
|
@@ -1061,50 +1380,14 @@ def create_advanced_ppt_from_content(
|
|
1061 |
# ๋ฐฐ๊ฒฝ ๊ทธ๋ผ๋์ธํธ
|
1062 |
add_gradient_background(slide, theme['colors']['primary'], theme['colors']['secondary'])
|
1063 |
|
1064 |
-
#
|
1065 |
-
title_top_position = Inches(2.0)
|
1066 |
-
if include_ai_image and AI_IMAGE_ENABLED:
|
1067 |
-
logger.info("Generating AI cover image via API...")
|
1068 |
-
ai_image_path = generate_ai_cover_image_via_api(topic, slides_data)
|
1069 |
-
if ai_image_path and os.path.exists(ai_image_path):
|
1070 |
-
img = Image.open(ai_image_path)
|
1071 |
-
img_width, img_height = img.size
|
1072 |
-
|
1073 |
-
# ์ด๋ฏธ์ง ๋ ํฌ๊ฒ (60% ํญ, ์ต๋ ๋์ด 4")
|
1074 |
-
max_width = Inches(6)
|
1075 |
-
ratio = img_height / img_width
|
1076 |
-
img_w = max_width
|
1077 |
-
img_h = max_width * ratio
|
1078 |
-
max_height = Inches(4)
|
1079 |
-
if img_h > max_height:
|
1080 |
-
img_h = max_height
|
1081 |
-
img_w = max_height / ratio
|
1082 |
-
|
1083 |
-
left = (prs.slide_width - img_w) / 2
|
1084 |
-
top = Inches(0.6)
|
1085 |
-
|
1086 |
-
pic = slide.shapes.add_picture(ai_image_path, left, top, width=img_w, height=img_h)
|
1087 |
-
pic.shadow.inherit = False
|
1088 |
-
pic.shadow.visible = True
|
1089 |
-
pic.shadow.blur_radius = Pt(15)
|
1090 |
-
pic.shadow.distance = Pt(8)
|
1091 |
-
pic.shadow.angle = 45
|
1092 |
-
|
1093 |
-
title_top_position = top + img_h + Inches(0.35)
|
1094 |
-
|
1095 |
-
try:
|
1096 |
-
os.unlink(ai_image_path)
|
1097 |
-
except Exception as e:
|
1098 |
-
logger.warning(f"Temp image delete failed: {e}")
|
1099 |
-
|
1100 |
-
# ์ ๋ชฉ / ๋ถ์ ๋ชฉ ํ
์คํธ
|
1101 |
title_shape = slide.shapes.title
|
1102 |
subtitle_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else None
|
1103 |
|
1104 |
if title_shape:
|
1105 |
title_shape.left = Inches(0.5)
|
1106 |
title_shape.width = prs.slide_width - Inches(1)
|
1107 |
-
title_shape.top =
|
1108 |
title_shape.height = Inches(1.2)
|
1109 |
|
1110 |
tf = title_shape.text_frame
|
@@ -1112,23 +1395,15 @@ def create_advanced_ppt_from_content(
|
|
1112 |
tf.text = topic
|
1113 |
p = tf.paragraphs[0]
|
1114 |
p.font.name = theme['fonts']['title']
|
1115 |
-
p.font.size = Pt(
|
1116 |
p.font.bold = True
|
1117 |
p.font.color.rgb = RGBColor(255, 255, 255)
|
1118 |
p.alignment = PP_ALIGN.CENTER
|
1119 |
|
1120 |
-
# ๊ฐ๋ก ๋ฐฉํฅ ๊ณ ์
|
1121 |
-
bodyPr = tf._txBody.bodyPr
|
1122 |
-
bodyPr.set('vert', 'horz')
|
1123 |
-
|
1124 |
-
# ์ต์์ ๋ ์ด์ด๋ก ์ด๋
|
1125 |
-
slide.shapes._spTree.remove(title_shape._element)
|
1126 |
-
slide.shapes._spTree.append(title_shape._element)
|
1127 |
-
|
1128 |
if subtitle_shape:
|
1129 |
subtitle_shape.left = Inches(0.5)
|
1130 |
subtitle_shape.width = prs.slide_width - Inches(1)
|
1131 |
-
subtitle_shape.top =
|
1132 |
subtitle_shape.height = Inches(0.9)
|
1133 |
|
1134 |
tf2 = subtitle_shape.text_frame
|
@@ -1136,15 +1411,60 @@ def create_advanced_ppt_from_content(
|
|
1136 |
tf2.text = f"์๋ ์์ฑ๋ ํ๋ ์ ํ
์ด์
โข ์ด {len(slides_data)}์ฅ"
|
1137 |
p2 = tf2.paragraphs[0]
|
1138 |
p2.font.name = theme['fonts']['subtitle']
|
1139 |
-
p2.font.size = Pt(
|
1140 |
p2.font.color.rgb = RGBColor(255, 255, 255)
|
1141 |
p2.alignment = PP_ALIGN.CENTER
|
1142 |
|
1143 |
-
|
1144 |
-
|
1145 |
-
|
1146 |
-
|
1147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1148 |
|
1149 |
# ์ฅ์ ์์
|
1150 |
add_decorative_shapes(slide, theme)
|
@@ -1192,149 +1512,275 @@ def create_advanced_ppt_from_content(
|
|
1192 |
except Exception as e:
|
1193 |
logger.warning(f"Title font sizing failed: {e}")
|
1194 |
|
1195 |
-
#
|
1196 |
-
|
1197 |
-
|
1198 |
-
|
1199 |
-
|
1200 |
-
|
1201 |
-
|
1202 |
-
|
1203 |
-
|
1204 |
-
|
1205 |
-
|
1206 |
-
|
1207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1208 |
|
1209 |
-
|
1210 |
-
|
1211 |
-
|
1212 |
-
|
1213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1214 |
|
1215 |
-
|
1216 |
-
|
1217 |
-
|
1218 |
-
|
1219 |
-
|
1220 |
-
line.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
1221 |
-
line.line.fill.background()
|
1222 |
-
|
1223 |
-
elif layout_type == 'two_content':
|
1224 |
-
content = slide_data.get('content', '')
|
1225 |
-
if content:
|
1226 |
-
logger.info(f"Creating two-column layout for slide {i+1}")
|
1227 |
-
content_lines = content.split('\n')
|
1228 |
-
mid_point = len(content_lines) // 2
|
1229 |
|
1230 |
-
|
1231 |
-
|
1232 |
-
|
1233 |
-
|
1234 |
-
|
1235 |
-
|
1236 |
-
|
1237 |
-
|
1238 |
-
|
1239 |
-
|
1240 |
-
|
1241 |
-
|
1242 |
-
|
1243 |
-
|
1244 |
-
|
1245 |
-
|
1246 |
-
|
1247 |
-
|
1248 |
-
|
1249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1250 |
|
1251 |
-
|
1252 |
-
|
1253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1254 |
)
|
1255 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
|
1259 |
-
|
1260 |
-
right_tf.word_wrap = True
|
1261 |
-
force_font_size(right_tf, 14, theme)
|
1262 |
-
|
1263 |
-
# Apply emoji bullets
|
1264 |
-
for paragraph in right_tf.paragraphs:
|
1265 |
-
text = paragraph.text.strip()
|
1266 |
-
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
1267 |
-
clean_text = text.lstrip('-โขโ ')
|
1268 |
-
emoji = get_emoji_for_content(clean_text)
|
1269 |
-
paragraph.text = f"{emoji} {clean_text}"
|
1270 |
-
force_font_size(right_tf, 14, theme)
|
1271 |
|
1272 |
else:
|
1273 |
-
#
|
1274 |
-
|
1275 |
-
|
1276 |
-
|
1277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1278 |
|
1279 |
-
|
1280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1281 |
|
1282 |
-
|
1283 |
-
|
1284 |
-
|
1285 |
-
textbox = slide.shapes.add_textbox(
|
1286 |
-
Inches(0.5), # left
|
1287 |
-
Inches(1.5), # top
|
1288 |
-
Inches(9), # width
|
1289 |
-
Inches(3.5) # height
|
1290 |
-
)
|
1291 |
-
|
1292 |
-
tf = textbox.text_frame
|
1293 |
-
tf.clear()
|
1294 |
-
|
1295 |
-
# ํ
์คํธ ์ค์
|
1296 |
-
tf.text = content.strip()
|
1297 |
-
tf.word_wrap = True
|
1298 |
-
|
1299 |
-
# ํ
์คํธ ํ๋ ์ ์ฌ๋ฐฑ ์ค์
|
1300 |
-
tf.margin_left = Inches(0.1)
|
1301 |
-
tf.margin_right = Inches(0.1)
|
1302 |
-
tf.margin_top = Inches(0.05)
|
1303 |
-
tf.margin_bottom = Inches(0.05)
|
1304 |
|
1305 |
-
|
1306 |
-
force_font_size(tf, 16, theme)
|
1307 |
|
1308 |
-
|
1309 |
-
|
1310 |
-
if paragraph.text.strip():
|
1311 |
-
# ์ด๋ชจ์ง ์ถ๊ฐ
|
1312 |
-
text = paragraph.text.strip()
|
1313 |
-
if text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
1314 |
-
clean_text = text.lstrip('-โขโ ')
|
1315 |
-
emoji = get_emoji_for_content(clean_text)
|
1316 |
-
paragraph.text = f"{emoji} {clean_text}"
|
1317 |
-
|
1318 |
-
# ํฐํธ ์ฌ์ ์ฉ - ๊ฐ run์ ๋ํด ๋ช
์์ ์ผ๋ก ์ค์
|
1319 |
-
if paragraph.runs:
|
1320 |
-
for run in paragraph.runs:
|
1321 |
-
run.font.size = Pt(16)
|
1322 |
-
run.font.name = theme['fonts']['body']
|
1323 |
-
run.font.color.rgb = theme['colors']['text']
|
1324 |
-
else:
|
1325 |
-
# runs๊ฐ ์์ผ๋ฉด ์์ฑ
|
1326 |
-
paragraph.font.size = Pt(16)
|
1327 |
-
paragraph.font.name = theme['fonts']['body']
|
1328 |
-
paragraph.font.color.rgb = theme['colors']['text']
|
1329 |
-
|
1330 |
-
# ๋จ๋ฝ ๊ฐ๊ฒฉ
|
1331 |
-
paragraph.space_before = Pt(6)
|
1332 |
-
paragraph.space_after = Pt(6)
|
1333 |
-
paragraph.line_spacing = 1.3
|
1334 |
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1338 |
|
1339 |
# Add slide notes if available
|
1340 |
if slide_data.get('notes'):
|
@@ -1578,7 +2024,7 @@ Notes: [Speaker script]
|
|
1578 |
yield f"โ ๏ธ Error generating content: {str(e)}"
|
1579 |
|
1580 |
##############################################################################
|
1581 |
-
# Main PPT Generation Function - IMPROVED VERSION with
|
1582 |
##############################################################################
|
1583 |
def generate_ppt(
|
1584 |
topic: str,
|
@@ -1590,9 +2036,11 @@ def generate_ppt(
|
|
1590 |
font_style: str = "modern",
|
1591 |
layout_style: str = "consistent",
|
1592 |
include_charts: bool = False,
|
1593 |
-
include_ai_image: bool = False
|
|
|
|
|
1594 |
) -> tuple:
|
1595 |
-
"""Main function to generate PPT with advanced design and
|
1596 |
|
1597 |
if not PPTX_AVAILABLE:
|
1598 |
return None, "โ python-pptx ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ค์น๋์ง ์์์ต๋๋ค.\n\n์ค์น ๋ช
๋ น: pip install python-pptx", ""
|
@@ -1604,14 +2052,32 @@ def generate_ppt(
|
|
1604 |
return None, "โ ์ฌ๋ผ์ด๋ ์๋ 3์ฅ ์ด์ 20์ฅ ์ดํ๋ก ์ค์ ํด์ฃผ์ธ์.", ""
|
1605 |
|
1606 |
try:
|
1607 |
-
#
|
1608 |
-
if include_ai_image and not
|
1609 |
-
yield None, "๐
|
1610 |
-
if
|
1611 |
-
yield None, "โ
|
1612 |
else:
|
1613 |
include_ai_image = False
|
1614 |
-
yield None, "โ ๏ธ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1615 |
|
1616 |
# Process reference files if provided
|
1617 |
additional_context = ""
|
@@ -1672,6 +2138,7 @@ def generate_ppt(
|
|
1672 |
# Debug logging
|
1673 |
logger.info(f"Parsed {len(slides_data)} slides from LLM response")
|
1674 |
logger.info(f"Design theme: {design_theme}, Layout style: {layout_style}")
|
|
|
1675 |
|
1676 |
if not slides_data:
|
1677 |
# Show the raw response for debugging
|
@@ -1682,20 +2149,42 @@ def generate_ppt(
|
|
1682 |
yield None, error_msg, llm_response
|
1683 |
return
|
1684 |
|
1685 |
-
# AI ์ด๋ฏธ์ง ์์ฑ ์๋ฆผ
|
1686 |
-
|
1687 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1688 |
|
1689 |
# Create PPT file with advanced design
|
1690 |
-
ppt_path = create_advanced_ppt_from_content(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1691 |
|
1692 |
success_msg = f"โ
PPT ํ์ผ์ด ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋์์ต๋๋ค!\n\n"
|
1693 |
success_msg += f"๐ ์ฃผ์ : {topic}\n"
|
1694 |
success_msg += f"๐ ์ฌ๋ผ์ด๋ ์: {len(slides_data)}์ฅ\n"
|
1695 |
success_msg += f"๐จ ๋์์ธ ํ
๋ง: {DESIGN_THEMES[design_theme]['name']}\n"
|
1696 |
success_msg += f"๐ ๋ ์ด์์ ์คํ์ผ: {layout_style}\n"
|
1697 |
-
|
1698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1699 |
success_msg += f"๐ ์์ฑ๋ ์ฌ๋ผ์ด๋:\n"
|
1700 |
|
1701 |
for i, slide in enumerate(slides_data[:5]): # Show first 5 slides
|
@@ -1899,7 +2388,7 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
1899 |
# ๐ฏ AI ๊ธฐ๋ฐ PPT ์๋ ์์ฑ ์์คํ
Pro
|
1900 |
|
1901 |
๊ณ ๊ธ ๋์์ธ ํ
๋ง์ ๋ ์ด์์์ ํ์ฉํ ์ ๋ฌธ์ ์ธ ํ๋ ์ ํ
์ด์
์ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
1902 |
-
|
1903 |
"""
|
1904 |
)
|
1905 |
|
@@ -1982,11 +2471,27 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
1982 |
info="CSV ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ์ฐจํธ ์์ฑ"
|
1983 |
)
|
1984 |
|
1985 |
-
|
1986 |
-
|
1987 |
-
|
1988 |
-
|
1989 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1990 |
|
1991 |
reference_files = gr.File(
|
1992 |
label="๐ ์ฐธ๊ณ ์๋ฃ (์ ํ์ฌํญ)",
|
@@ -2030,42 +2535,35 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
2030 |
1. **PPT ์ฃผ์ ์
๋ ฅ**: ๊ตฌ์ฒด์ ์ธ ์ฃผ์ ์ผ์๋ก ๋ ์ข์ ๊ฒฐ๊ณผ
|
2031 |
2. **์ฌ๋ผ์ด๋ ์ ์ ํ**: 3-20์ฅ ๋ฒ์์์ ์ ํ
|
2032 |
3. **๋์์ธ ํ
๋ง ์ ํ**: 5๊ฐ์ง ์ ๋ฌธ์ ์ธ ํ
๋ง ์ค ์ ํ
|
2033 |
-
4.
|
2034 |
-
5.
|
2035 |
-
6.
|
2036 |
-
|
2037 |
-
|
2038 |
-
|
2039 |
-
-
|
2040 |
-
-
|
2041 |
-
-
|
2042 |
-
-
|
2043 |
-
- **๋ฏธ๋๋ฉ**: ๊น๋ํ๊ณ ๋จ์ํ ํ๋ฐฑ ๋์์ธ
|
2044 |
-
|
2045 |
-
### โจ ์๋ก์ด ๊ธฐ๋ฅ
|
2046 |
-
- **5๊ฐ ํต์ฌ ํฌ์ธํธ**: ๊ฐ ์ฌ๋ผ์ด๋๋ง๋ค 5๊ฐ์ ํต์ฌ ๋ด์ฉ
|
2047 |
-
- **๋ฐํ์ ๋
ธํธ**: ๊ฐ ์ฌ๋ผ์ด๋๋ง๋ค ๋ฐํ์๋ฅผ ์ํ ๋
ธํธ ์๋ ์์ฑ
|
2048 |
-
- **ํฅ์๋ ํฐํธ ํฌ๊ธฐ**: ๋ ํฐ ํฐํธ๋ก ๊ฐ๋
์ฑ ํฅ์
|
2049 |
-
- **AI 3D ์ด๋ฏธ์ง**: API๋ฅผ ํตํ ๊ณ ํ์ง 3D ์คํ์ผ ํ์ง ์ด๋ฏธ์ง (ํ๊ธ ํ๋กฌํํธ ์ง์)
|
2050 |
|
2051 |
### ๐ก ๊ณ ๊ธ ํ
|
2052 |
-
-
|
2053 |
-
-
|
2054 |
-
-
|
2055 |
-
- ๋ค์ํ ๋ ์ด์์์ผ๋ก ์๊ฐ์ ํฅ๋ฏธ ์ ๋ฐ
|
2056 |
"""
|
2057 |
)
|
2058 |
|
2059 |
# Examples
|
2060 |
gr.Examples(
|
2061 |
examples=[
|
2062 |
-
["์ธ๊ณต์ง๋ฅ์ ๋ฏธ๋์ ์ฐ์
์ ์ฉ ์ฌ๋ก", 10, False, True, [], "professional", "modern", "consistent", False, False],
|
2063 |
-
["2024๋
๋์งํธ ๋ง์ผํ
ํธ๋ ๋", 12, True, True, [], "modern", "modern", "consistent", False, True],
|
2064 |
-
["๊ธฐํ๋ณํ์ ์ง์๊ฐ๋ฅํ ๋ฐ์ ", 15, True, True, [], "nature", "classic", "consistent", False, True],
|
2065 |
-
["์คํํธ์
์ฌ์
๊ณํ์", 8, False, True, [], "creative", "modern", "varied", False, True],
|
2066 |
],
|
2067 |
inputs=[topic_input, num_slides, use_web_search, use_korean, reference_files,
|
2068 |
-
design_theme, font_style, layout_style, include_charts, include_ai_image
|
|
|
2069 |
)
|
2070 |
|
2071 |
# Event handler
|
@@ -2081,10 +2579,19 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
|
|
2081 |
font_style,
|
2082 |
layout_style,
|
2083 |
include_charts,
|
2084 |
-
include_ai_image
|
|
|
|
|
2085 |
],
|
2086 |
outputs=[download_file, status_text, content_preview]
|
2087 |
)
|
2088 |
|
|
|
2089 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
2090 |
demo.launch()
|
|
|
49 |
logger.warning("SERPHOUSE_API_KEY not set. Web search functionality will be limited.")
|
50 |
|
51 |
##############################################################################
|
52 |
+
# AI Image Generation API Configuration - FLUX API
|
53 |
##############################################################################
|
54 |
+
FLUX_API_URL = "http://211.233.58.201:7896"
|
55 |
+
FLUX_API_ENABLED = False
|
56 |
+
flux_api_client = None
|
57 |
|
58 |
+
def initialize_flux_api():
|
59 |
+
"""FLUX API ์ด๊ธฐํ"""
|
60 |
+
global FLUX_API_ENABLED, flux_api_client
|
61 |
|
62 |
try:
|
63 |
+
logger.info("Connecting to FLUX API...")
|
64 |
+
flux_api_client = Client(FLUX_API_URL)
|
65 |
+
FLUX_API_ENABLED = True
|
66 |
+
logger.info("FLUX API connected successfully")
|
67 |
return True
|
68 |
except Exception as e:
|
69 |
+
logger.error(f"Failed to connect to FLUX API: {e}")
|
70 |
+
FLUX_API_ENABLED = False
|
71 |
+
return False
|
72 |
+
|
73 |
+
##############################################################################
|
74 |
+
# Diagram Generation API Configuration
|
75 |
+
##############################################################################
|
76 |
+
DIAGRAM_API_URL = "http://211.233.58.201:7860" # ChartGPT API URL
|
77 |
+
DIAGRAM_API_ENABLED = False
|
78 |
+
diagram_api_client = None
|
79 |
+
|
80 |
+
def initialize_diagram_api():
|
81 |
+
"""๋ค์ด์ด๊ทธ๋จ ์์ฑ API ์ด๊ธฐํ"""
|
82 |
+
global DIAGRAM_API_ENABLED, diagram_api_client
|
83 |
+
|
84 |
+
try:
|
85 |
+
logger.info("Connecting to Diagram Generation API...")
|
86 |
+
diagram_api_client = Client(DIAGRAM_API_URL)
|
87 |
+
DIAGRAM_API_ENABLED = True
|
88 |
+
logger.info("Diagram API connected successfully")
|
89 |
+
return True
|
90 |
+
except Exception as e:
|
91 |
+
logger.error(f"Failed to connect to Diagram API: {e}")
|
92 |
+
DIAGRAM_API_ENABLED = False
|
93 |
return False
|
94 |
|
95 |
##############################################################################
|
|
|
330 |
else:
|
331 |
return 'โถ๏ธ'
|
332 |
|
333 |
+
##############################################################################
|
334 |
+
# Diagram Type Detection
|
335 |
+
##############################################################################
|
336 |
+
def detect_diagram_type(title: str, content: str) -> Optional[str]:
|
337 |
+
"""์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๋ถ์ํ์ฌ ์ ์ ํ ๋ค์ด์ด๊ทธ๋จ ํ์
๊ฒฐ์ """
|
338 |
+
combined_text = f"{title} {content}".lower()
|
339 |
+
|
340 |
+
# Process Flow keywords
|
341 |
+
if any(word in combined_text for word in ['ํ๋ก์ธ์ค', 'process', '์ ์ฐจ', 'procedure', '๋จ๊ณ', 'step', 'flow', 'ํ๋ฆ', '์ํฌํ๋ก์ฐ', 'workflow']):
|
342 |
+
return "Process Flow"
|
343 |
+
|
344 |
+
# WBS keywords
|
345 |
+
elif any(word in combined_text for word in ['wbs', '์์
๋ถํด', 'ํ๋ก์ ํธ', 'project', '์
๋ฌด๋ถํด', 'breakdown', '๊ตฌ์กฐ๋']):
|
346 |
+
return "WBS Diagram"
|
347 |
+
|
348 |
+
# Concept Map keywords
|
349 |
+
elif any(word in combined_text for word in ['๊ฐ๋
', 'concept', '๊ด๊ณ', 'relationship', '์ฐ๊ด', 'connection', '๋ง์ธ๋๋งต', 'mindmap']):
|
350 |
+
return "Concept Map"
|
351 |
+
|
352 |
+
# Radial Diagram keywords
|
353 |
+
elif any(word in combined_text for word in ['์ค์ฌ', 'central', '๋ฐฉ์ฌํ', 'radial', 'ํต์ฌ', 'core', '์ฃผ์', 'main']):
|
354 |
+
return "Radial Diagram"
|
355 |
+
|
356 |
+
# Synoptic Chart keywords
|
357 |
+
elif any(word in combined_text for word in ['๊ฐ์', 'overview', '์ ์ฒด', 'overall', '์์ฝ', 'summary', '์๋ํฑ', 'synoptic']):
|
358 |
+
return "Synoptic Chart"
|
359 |
+
|
360 |
+
return None
|
361 |
+
|
362 |
+
##############################################################################
|
363 |
+
# Generate Diagram JSON using LLM
|
364 |
+
##############################################################################
|
365 |
+
def generate_diagram_json(title: str, content: str, diagram_type: str) -> Optional[str]:
|
366 |
+
"""LLM์ ์ฌ์ฉํ์ฌ ๋ค์ด์ด๊ทธ๋จ์ฉ JSON ์์ฑ"""
|
367 |
+
if not FRIENDLI_TOKEN:
|
368 |
+
return None
|
369 |
+
|
370 |
+
# ๋ค์ด์ด๊ทธ๋จ ํ์
๋ณ JSON ๊ตฌ์กฐ ๊ฐ์ด๋
|
371 |
+
json_guides = {
|
372 |
+
"Concept Map": """Generate a JSON for a concept map with the EXACT following structure:
|
373 |
+
{
|
374 |
+
"central_node": "Main Topic",
|
375 |
+
"nodes": [
|
376 |
+
{
|
377 |
+
"id": "node1",
|
378 |
+
"label": "First Concept",
|
379 |
+
"relationship": "is part of",
|
380 |
+
"subnodes": [
|
381 |
+
{
|
382 |
+
"id": "node1_1",
|
383 |
+
"label": "Sub Concept 1",
|
384 |
+
"relationship": "includes",
|
385 |
+
"subnodes": []
|
386 |
+
}
|
387 |
+
]
|
388 |
+
}
|
389 |
+
]
|
390 |
+
}""",
|
391 |
+
"Process Flow": """Generate a JSON for a process flow diagram with the EXACT following structure:
|
392 |
+
{
|
393 |
+
"start_node": "Start Process",
|
394 |
+
"nodes": [
|
395 |
+
{"id": "step1", "label": "First Step", "type": "process"},
|
396 |
+
{"id": "step2", "label": "Decision Point", "type": "decision"},
|
397 |
+
{"id": "end", "label": "End Process", "type": "end"}
|
398 |
+
],
|
399 |
+
"connections": [
|
400 |
+
{"from": "start_node", "to": "step1", "label": "Begin"},
|
401 |
+
{"from": "step1", "to": "step2", "label": "Next"},
|
402 |
+
{"from": "step2", "to": "end", "label": "Complete"}
|
403 |
+
]
|
404 |
+
}""",
|
405 |
+
"WBS Diagram": """Generate a JSON for a WBS diagram with the EXACT following structure:
|
406 |
+
{
|
407 |
+
"project_title": "Project Name",
|
408 |
+
"phases": [
|
409 |
+
{
|
410 |
+
"id": "phase1",
|
411 |
+
"label": "Phase 1",
|
412 |
+
"tasks": [
|
413 |
+
{
|
414 |
+
"id": "task1_1",
|
415 |
+
"label": "Task 1.1",
|
416 |
+
"subtasks": []
|
417 |
+
}
|
418 |
+
]
|
419 |
+
}
|
420 |
+
]
|
421 |
+
}""",
|
422 |
+
"Radial Diagram": """Generate a JSON for a radial diagram with the EXACT following structure:
|
423 |
+
{
|
424 |
+
"central_node": "Central Concept",
|
425 |
+
"nodes": [
|
426 |
+
{
|
427 |
+
"id": "branch1",
|
428 |
+
"label": "Branch 1",
|
429 |
+
"relationship": "connected to",
|
430 |
+
"subnodes": []
|
431 |
+
}
|
432 |
+
]
|
433 |
+
}""",
|
434 |
+
"Synoptic Chart": """Generate a JSON for a synoptic chart with the EXACT following structure:
|
435 |
+
{
|
436 |
+
"central_node": "Chart Title",
|
437 |
+
"nodes": [
|
438 |
+
{
|
439 |
+
"id": "phase1",
|
440 |
+
"label": "Phase 1 Name",
|
441 |
+
"relationship": "starts with",
|
442 |
+
"subnodes": []
|
443 |
+
}
|
444 |
+
]
|
445 |
+
}"""
|
446 |
+
}
|
447 |
+
|
448 |
+
system_prompt = f"""You are a helpful assistant that generates JSON structures for diagrams.
|
449 |
+
{json_guides.get(diagram_type, '')}
|
450 |
+
|
451 |
+
Important rules:
|
452 |
+
1. Generate ONLY valid JSON without any explanation or markdown formatting
|
453 |
+
2. The JSON must follow the EXACT structure shown above
|
454 |
+
3. Create content based on the provided title and content
|
455 |
+
4. Use the user's language (Korean or English) for the content values
|
456 |
+
5. Keep it simple with 3-5 main nodes/steps"""
|
457 |
+
|
458 |
+
messages = [
|
459 |
+
{"role": "system", "content": system_prompt},
|
460 |
+
{"role": "user", "content": f"Create a {diagram_type} JSON for:\nTitle: {title}\nContent: {content}"}
|
461 |
+
]
|
462 |
+
|
463 |
+
headers = {
|
464 |
+
"Authorization": f"Bearer {FRIENDLI_TOKEN}",
|
465 |
+
"Content-Type": "application/json"
|
466 |
+
}
|
467 |
+
|
468 |
+
payload = {
|
469 |
+
"model": FRIENDLI_MODEL_ID,
|
470 |
+
"messages": messages,
|
471 |
+
"max_tokens": 1000,
|
472 |
+
"temperature": 0.7,
|
473 |
+
"stream": False
|
474 |
+
}
|
475 |
+
|
476 |
+
try:
|
477 |
+
response = requests.post(FRIENDLI_API_URL, headers=headers, json=payload, timeout=30)
|
478 |
+
if response.status_code == 200:
|
479 |
+
response_data = response.json()
|
480 |
+
if 'choices' in response_data and len(response_data['choices']) > 0:
|
481 |
+
content = response_data['choices'][0]['message']['content']
|
482 |
+
# Extract JSON from response
|
483 |
+
content = content.strip()
|
484 |
+
if content.startswith("```json"):
|
485 |
+
content = content[7:]
|
486 |
+
if content.startswith("```"):
|
487 |
+
content = content[3:]
|
488 |
+
if content.endswith("```"):
|
489 |
+
content = content[:-3]
|
490 |
+
|
491 |
+
# Validate JSON
|
492 |
+
json.loads(content) # This will raise exception if invalid
|
493 |
+
return content
|
494 |
+
except Exception as e:
|
495 |
+
logger.error(f"Error generating diagram JSON: {e}")
|
496 |
+
|
497 |
+
return None
|
498 |
+
|
499 |
+
##############################################################################
|
500 |
+
# Generate Diagram using API
|
501 |
+
##############################################################################
|
502 |
+
def generate_diagram_via_api(json_data: str, diagram_type: str) -> Optional[str]:
|
503 |
+
"""๋ค์ด์ด๊ทธ๋จ API๋ฅผ ํตํด ๋ค์ด์ด๊ทธ๋จ ์์ฑ"""
|
504 |
+
if not DIAGRAM_API_ENABLED or not diagram_api_client:
|
505 |
+
return None
|
506 |
+
|
507 |
+
try:
|
508 |
+
# API ํธ์ถ
|
509 |
+
result = diagram_api_client.predict(
|
510 |
+
prompt_input=f"Generate {diagram_type}", # ํ๋กฌํํธ
|
511 |
+
diagram_type_select=diagram_type, # ๋ค์ด์ด๊ทธ๋จ ํ์
|
512 |
+
design_type_select="None", # ๋์์ธ ํ์
์ None
|
513 |
+
output_format_radio="png", # PNG ํ์
|
514 |
+
use_search_checkbox=False, # ๊ฒ์ ์ฌ์ฉ ์ํจ
|
515 |
+
api_name="/generate_with_llm"
|
516 |
+
)
|
517 |
+
|
518 |
+
# ๊ฒฐ๊ณผ์์ ์ด๋ฏธ์ง ๊ฒฝ๋ก ์ถ์ถ
|
519 |
+
if isinstance(result, tuple) and len(result) > 0:
|
520 |
+
image_path = result[0]
|
521 |
+
if image_path and os.path.exists(image_path):
|
522 |
+
# ์์ ํ์ผ๋ก ๋ณต์ฌ
|
523 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
|
524 |
+
shutil.copy2(image_path, tmp.name)
|
525 |
+
return tmp.name
|
526 |
+
|
527 |
+
return None
|
528 |
+
|
529 |
+
except Exception as e:
|
530 |
+
logger.error(f"Failed to generate diagram via API: {e}")
|
531 |
+
return None
|
532 |
+
|
533 |
+
##############################################################################
|
534 |
+
# FLUX Image Generation Functions
|
535 |
+
##############################################################################
|
536 |
+
def generate_flux_prompt(title: str, content: str) -> str:
|
537 |
+
"""์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก FLUX ์ด๋ฏธ์ง ํ๋กฌํ๏ฟฝ๏ฟฝ ์์ฑ"""
|
538 |
+
|
539 |
+
# LLM์ ์ฌ์ฉํ์ฌ ํ๋กฌํํธ ์์ฑ
|
540 |
+
system_prompt = """You are an expert at creating visual prompts for AI image generation.
|
541 |
+
Create a concise, visually descriptive prompt in English based on the slide content.
|
542 |
+
The prompt should describe a professional, modern illustration that represents the key concepts.
|
543 |
+
Keep it under 100 words and focus on visual elements, style, and mood.
|
544 |
+
Output ONLY the prompt without any explanation."""
|
545 |
+
|
546 |
+
messages = [
|
547 |
+
{"role": "system", "content": system_prompt},
|
548 |
+
{"role": "user", "content": f"Create an image prompt for:\nTitle: {title}\nContent: {content[:500]}"}
|
549 |
+
]
|
550 |
+
|
551 |
+
headers = {
|
552 |
+
"Authorization": f"Bearer {FRIENDLI_TOKEN}",
|
553 |
+
"Content-Type": "application/json"
|
554 |
+
}
|
555 |
+
|
556 |
+
payload = {
|
557 |
+
"model": FRIENDLI_MODEL_ID,
|
558 |
+
"messages": messages,
|
559 |
+
"max_tokens": 200,
|
560 |
+
"temperature": 0.8,
|
561 |
+
"stream": False
|
562 |
+
}
|
563 |
+
|
564 |
+
try:
|
565 |
+
response = requests.post(FRIENDLI_API_URL, headers=headers, json=payload, timeout=30)
|
566 |
+
if response.status_code == 200:
|
567 |
+
response_data = response.json()
|
568 |
+
if 'choices' in response_data and len(response_data['choices']) > 0:
|
569 |
+
prompt = response_data['choices'][0]['message']['content'].strip()
|
570 |
+
return f"Professional business presentation slide illustration: {prompt}, modern clean style, corporate colors, white background"
|
571 |
+
except Exception as e:
|
572 |
+
logger.error(f"Error generating FLUX prompt: {e}")
|
573 |
+
|
574 |
+
# Fallback prompt
|
575 |
+
return f"Professional business presentation illustration about {title}, modern minimalist style, clean design, corporate colors"
|
576 |
+
|
577 |
+
def generate_flux_image_via_api(prompt: str) -> Optional[str]:
|
578 |
+
"""FLUX API๋ฅผ ํตํด ์ด๋ฏธ์ง ์์ฑ"""
|
579 |
+
if not FLUX_API_ENABLED or not flux_api_client:
|
580 |
+
return None
|
581 |
+
|
582 |
+
try:
|
583 |
+
logger.info(f"Generating FLUX image with prompt: {prompt[:100]}...")
|
584 |
+
|
585 |
+
result = flux_api_client.predict(
|
586 |
+
prompt=prompt,
|
587 |
+
width=768,
|
588 |
+
height=768,
|
589 |
+
guidance=3.5,
|
590 |
+
inference_steps=8,
|
591 |
+
seed=random.randint(1, 1000000),
|
592 |
+
do_img2img=False,
|
593 |
+
init_image=None,
|
594 |
+
image2image_strength=0.8,
|
595 |
+
resize_img=True,
|
596 |
+
api_name="/generate_image"
|
597 |
+
)
|
598 |
+
|
599 |
+
if isinstance(result, tuple) and len(result) > 0:
|
600 |
+
image_path = result[0]
|
601 |
+
if image_path and os.path.exists(image_path):
|
602 |
+
# PNG๋ก ๋ณํ
|
603 |
+
with Image.open(image_path) as img:
|
604 |
+
png_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
|
605 |
+
img.save(png_tmp.name, format="PNG")
|
606 |
+
logger.info(f"FLUX image generated and saved to {png_tmp.name}")
|
607 |
+
return png_tmp.name
|
608 |
+
|
609 |
+
return None
|
610 |
+
|
611 |
+
except Exception as e:
|
612 |
+
logger.error(f"Failed to generate FLUX image: {e}")
|
613 |
+
return None
|
614 |
+
|
615 |
##############################################################################
|
616 |
# Icon and Shape Mappings
|
617 |
##############################################################################
|
|
|
863 |
return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
|
864 |
|
865 |
##############################################################################
|
866 |
+
# AI Image Generation Functions using FLUX API
|
867 |
##############################################################################
|
868 |
def generate_cover_image_prompt(topic: str, slides_data: list) -> str:
|
869 |
+
"""PPT ์ฃผ์ ์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ํ์ง ์ด๋ฏธ์ง ํ๋กฌํํธ ์์ฑ"""
|
870 |
|
871 |
# ์ฃผ์ ํค์๋ ์ถ์ถ
|
872 |
+
keywords = extract_keywords(topic, top_k=3).split()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
873 |
|
874 |
# ์ฃผ์ ๋ถ์์ ํตํ ์คํ์ผ ๊ฒฐ์
|
875 |
+
style = "modern professional"
|
876 |
topic_lower = topic.lower()
|
877 |
|
878 |
if any(word in topic_lower for word in ['๊ธฐ์ ', 'tech', 'ai', '์ธ๊ณต์ง๋ฅ', 'digital', '๋์งํธ']):
|
879 |
+
style = "futuristic technology"
|
880 |
elif any(word in topic_lower for word in ['๋น์ฆ๋์ค', 'business', '๊ฒฝ์', 'management']):
|
881 |
+
style = "corporate business"
|
882 |
elif any(word in topic_lower for word in ['๊ต์ก', 'education', 'ํ์ต', 'learning']):
|
883 |
+
style = "educational inspiring"
|
884 |
elif any(word in topic_lower for word in ['ํ๊ฒฝ', 'environment', '์์ฐ', 'nature']):
|
885 |
+
style = "nature eco-friendly"
|
886 |
elif any(word in topic_lower for word in ['์๋ฃ', 'medical', '๊ฑด๊ฐ', 'health']):
|
887 |
+
style = "medical healthcare"
|
888 |
elif any(word in topic_lower for word in ['๊ธ์ต', 'finance', 'ํฌ์', 'investment']):
|
889 |
+
style = "financial professional"
|
890 |
|
891 |
+
# FLUX๋ฅผ ์ํ ์์ด ํ๋กฌํํธ ๊ตฌ์ฑ
|
892 |
+
prompt = f"Professional presentation cover design, {style} theme, abstract geometric shapes representing {' '.join(keywords)}, modern minimalist style, gradient background, clean composition, high quality, corporate design"
|
893 |
|
894 |
return prompt
|
895 |
|
896 |
+
def generate_conclusion_image_prompt(title: str, content: str) -> str:
|
897 |
+
"""๊ฒฐ๋ก ์ฌ๋ผ์ด๋์ฉ ์ด๋ฏธ์ง ํ๋กฌํํธ ์์ฑ"""
|
898 |
+
|
899 |
+
# ์ฃผ์ ํค์๋ ์ถ์ถ
|
900 |
+
keywords = extract_keywords(f"{title} {content}", top_k=5).split()
|
901 |
+
|
902 |
+
# ๊ฒฐ๋ก /ํ์ด๋ผ์ดํธ ๊ด๋ จ ํค์๋ ๊ฐ์ง
|
903 |
+
conclusion_keywords = ['๊ฒฐ๋ก ', 'conclusion', '์์ฝ', 'summary', 'ํต์ฌ', 'key',
|
904 |
+
'์ค์', 'important', '๋ฏธ๋', 'future', '์ ๋ง', 'outlook']
|
905 |
+
|
906 |
+
# ํ๋กฌํํธ ์คํ์ผ ๊ฒฐ์
|
907 |
+
if any(word in title.lower() + content.lower() for word in conclusion_keywords):
|
908 |
+
style = "inspirational conclusion with bright future vision"
|
909 |
+
else:
|
910 |
+
style = "professional summary visualization"
|
911 |
+
|
912 |
+
# FLUX ํ๋กฌํํธ ๊ตฌ์ฑ
|
913 |
+
prompt = f"Professional presentation conclusion slide, {style}, highlighting {' '.join(keywords[:3])}, modern abstract visualization, uplifting mood, corporate design, high quality"
|
914 |
+
|
915 |
+
return prompt
|
916 |
|
917 |
+
def generate_flux_prompt(title: str, content: str) -> str:
|
918 |
+
"""์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก FLUX ์ด๋ฏธ์ง ํ๋กฌํํธ ์์ฑ"""
|
919 |
+
|
920 |
+
# ์ฃผ์ ํค์๋ ์ถ์ถ
|
921 |
+
keywords = extract_keywords(f"{title} {content}", top_k=4).split()
|
922 |
+
|
923 |
+
# ๋ด์ฉ ๋ถ์์ ํตํ ์คํ์ผ ๊ฒฐ์
|
924 |
+
content_lower = (title + " " + content).lower()
|
925 |
+
|
926 |
+
style = "professional business"
|
927 |
+
if any(word in content_lower for word in ['data', '๋ฐ์ดํฐ', 'analysis', '๋ถ์', 'chart', '์ฐจํธ']):
|
928 |
+
style = "data visualization analytics"
|
929 |
+
elif any(word in content_lower for word in ['process', 'ํ๋ก์ธ์ค', 'workflow', '์ํฌํ๋ก์ฐ']):
|
930 |
+
style = "workflow process diagram"
|
931 |
+
elif any(word in content_lower for word in ['team', 'ํ', 'collaboration', 'ํ์
']):
|
932 |
+
style = "teamwork collaboration"
|
933 |
+
elif any(word in content_lower for word in ['growth', '์ฑ์ฅ', 'increase', '์ฆ๊ฐ']):
|
934 |
+
style = "growth success metrics"
|
935 |
+
|
936 |
+
# FLUX ํ๋กฌํํธ ๊ตฌ์ฑ
|
937 |
+
prompt = f"Professional presentation slide illustration, {style} theme, featuring {' '.join(keywords[:3])}, modern business graphics, clean corporate design, white background, high quality"
|
938 |
+
|
939 |
+
return prompt
|
940 |
|
941 |
+
def generate_flux_image_via_api(prompt: str) -> Optional[str]:
|
942 |
+
"""FLUX API๋ฅผ ํตํด ์ด๋ฏธ์ง ์์ฑ"""
|
943 |
+
if not FLUX_API_ENABLED or not flux_api_client:
|
944 |
+
logger.warning("FLUX API is not available")
|
945 |
return None
|
946 |
+
|
947 |
try:
|
948 |
+
logger.info(f"Generating FLUX image with prompt: {prompt[:100]}...")
|
949 |
+
|
950 |
+
result = flux_api_client.predict(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
951 |
prompt=prompt,
|
952 |
+
width=768,
|
953 |
+
height=768,
|
954 |
+
guidance=3.5,
|
955 |
+
inference_steps=8,
|
956 |
+
seed=random.randint(1, 1000000),
|
957 |
+
do_img2img=False,
|
958 |
+
init_image=None,
|
959 |
+
image2image_strength=0.8,
|
960 |
+
resize_img=True,
|
961 |
+
api_name="/generate_image"
|
962 |
)
|
963 |
+
|
964 |
+
if isinstance(result, tuple) and len(result) > 0:
|
965 |
+
image_path = result[0]
|
966 |
+
if image_path and os.path.exists(image_path):
|
967 |
+
# PNG๋ก ๋ณํ
|
968 |
+
with Image.open(image_path) as img:
|
969 |
png_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
|
970 |
+
img.save(png_tmp.name, format="PNG")
|
971 |
+
logger.info(f"FLUX image generated and saved to {png_tmp.name}")
|
972 |
return png_tmp.name
|
973 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
974 |
return None
|
975 |
+
|
976 |
except Exception as e:
|
977 |
+
logger.error(f"Failed to generate FLUX image: {e}")
|
978 |
return None
|
979 |
|
980 |
+
def generate_ai_cover_image_via_flux(topic: str, slides_data: list) -> Optional[str]:
|
981 |
+
"""FLUX API๋ฅผ ํตํด AI ํ์ง ์ด๋ฏธ์ง ์์ฑ"""
|
982 |
+
if not FLUX_API_ENABLED:
|
983 |
+
return None
|
984 |
+
|
985 |
+
prompt = generate_cover_image_prompt(topic, slides_data)
|
986 |
+
return generate_flux_image_via_api(prompt)
|
987 |
|
988 |
##############################################################################
|
989 |
# PPT Generation Functions - FIXED VERSION
|
|
|
1356 |
topic: str,
|
1357 |
theme_name: str,
|
1358 |
include_charts: bool = False,
|
1359 |
+
include_ai_image: bool = False,
|
1360 |
+
include_diagrams: bool = False,
|
1361 |
+
include_flux_images: bool = False
|
1362 |
) -> str:
|
1363 |
+
"""Create advanced PPT file with consistent visual design and AI-generated visuals"""
|
1364 |
if not PPTX_AVAILABLE:
|
1365 |
raise ImportError("python-pptx library is required")
|
1366 |
|
|
|
1372 |
prs.slide_height = Inches(5.625)
|
1373 |
|
1374 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1375 |
+
# 1) ์ ๋ชฉ ์ฌ๋ผ์ด๋(ํ์ง) ์์ฑ - ์์ ๋ ๋ ์ด์์
|
1376 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
1377 |
title_slide_layout = prs.slide_layouts[0]
|
1378 |
slide = prs.slides.add_slide(title_slide_layout)
|
|
|
1380 |
# ๋ฐฐ๊ฒฝ ๊ทธ๋ผ๋์ธํธ
|
1381 |
add_gradient_background(slide, theme['colors']['primary'], theme['colors']['secondary'])
|
1382 |
|
1383 |
+
# ์ ๋ชฉ๊ณผ ๋ถ์ ๋ชฉ์ ๋จผ์ ์ค์ ์๋จ์ ๋ฐฐ์น
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1384 |
title_shape = slide.shapes.title
|
1385 |
subtitle_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else None
|
1386 |
|
1387 |
if title_shape:
|
1388 |
title_shape.left = Inches(0.5)
|
1389 |
title_shape.width = prs.slide_width - Inches(1)
|
1390 |
+
title_shape.top = Inches(0.8) # ์๋จ์ ๋ฐฐ์น
|
1391 |
title_shape.height = Inches(1.2)
|
1392 |
|
1393 |
tf = title_shape.text_frame
|
|
|
1395 |
tf.text = topic
|
1396 |
p = tf.paragraphs[0]
|
1397 |
p.font.name = theme['fonts']['title']
|
1398 |
+
p.font.size = Pt(36)
|
1399 |
p.font.bold = True
|
1400 |
p.font.color.rgb = RGBColor(255, 255, 255)
|
1401 |
p.alignment = PP_ALIGN.CENTER
|
1402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1403 |
if subtitle_shape:
|
1404 |
subtitle_shape.left = Inches(0.5)
|
1405 |
subtitle_shape.width = prs.slide_width - Inches(1)
|
1406 |
+
subtitle_shape.top = Inches(2.0) # ์ ๋ชฉ ์๋์ ๋ฐฐ์น
|
1407 |
subtitle_shape.height = Inches(0.9)
|
1408 |
|
1409 |
tf2 = subtitle_shape.text_frame
|
|
|
1411 |
tf2.text = f"์๋ ์์ฑ๋ ํ๋ ์ ํ
์ด์
โข ์ด {len(slides_data)}์ฅ"
|
1412 |
p2 = tf2.paragraphs[0]
|
1413 |
p2.font.name = theme['fonts']['subtitle']
|
1414 |
+
p2.font.size = Pt(20)
|
1415 |
p2.font.color.rgb = RGBColor(255, 255, 255)
|
1416 |
p2.alignment = PP_ALIGN.CENTER
|
1417 |
|
1418 |
+
# AI ์ด๋ฏธ์ง๋ฅผ ์ฐ์ธก์ ๋ฐฐ์น
|
1419 |
+
if include_ai_image and FLUX_API_ENABLED:
|
1420 |
+
logger.info("Generating AI cover image via FLUX API...")
|
1421 |
+
ai_image_path = generate_ai_cover_image_via_flux(topic, slides_data)
|
1422 |
+
if ai_image_path and os.path.exists(ai_image_path):
|
1423 |
+
try:
|
1424 |
+
img = Image.open(ai_image_path)
|
1425 |
+
img_width, img_height = img.size
|
1426 |
+
|
1427 |
+
# ์ด๋ฏธ์ง๋ฅผ ์ฐ์ธก์ ๋ฐฐ์น
|
1428 |
+
max_width = Inches(4)
|
1429 |
+
max_height = Inches(3.5)
|
1430 |
+
|
1431 |
+
ratio = img_height / img_width
|
1432 |
+
img_w = max_width
|
1433 |
+
img_h = max_width * ratio
|
1434 |
+
|
1435 |
+
if img_h > max_height:
|
1436 |
+
img_h = max_height
|
1437 |
+
img_w = max_height / ratio
|
1438 |
+
|
1439 |
+
# ์ฐ์ธก ๋ฐฐ์น
|
1440 |
+
left = prs.slide_width - img_w - Inches(0.5)
|
1441 |
+
top = (prs.slide_height - img_h) / 2
|
1442 |
+
|
1443 |
+
pic = slide.shapes.add_picture(ai_image_path, left, top, width=img_w, height=img_h)
|
1444 |
+
pic.shadow.inherit = False
|
1445 |
+
pic.shadow.visible = True
|
1446 |
+
pic.shadow.blur_radius = Pt(15)
|
1447 |
+
pic.shadow.distance = Pt(8)
|
1448 |
+
pic.shadow.angle = 45
|
1449 |
+
|
1450 |
+
# ์ด๋ฏธ์ง ์๋์ ์์ ์บก์
์ถ๊ฐ
|
1451 |
+
caption_box = slide.shapes.add_textbox(
|
1452 |
+
left, top + img_h + Inches(0.1),
|
1453 |
+
img_w, Inches(0.3)
|
1454 |
+
)
|
1455 |
+
caption_tf = caption_box.text_frame
|
1456 |
+
caption_tf.text = "AI Generated"
|
1457 |
+
caption_p = caption_tf.paragraphs[0]
|
1458 |
+
caption_p.font.size = Pt(10)
|
1459 |
+
caption_p.font.color.rgb = RGBColor(255, 255, 255)
|
1460 |
+
caption_p.alignment = PP_ALIGN.CENTER
|
1461 |
+
|
1462 |
+
try:
|
1463 |
+
os.unlink(ai_image_path)
|
1464 |
+
except Exception as e:
|
1465 |
+
logger.warning(f"Temp image delete failed: {e}")
|
1466 |
+
except Exception as e:
|
1467 |
+
logger.error(f"Failed to add cover image: {e}")
|
1468 |
|
1469 |
# ์ฅ์ ์์
|
1470 |
add_decorative_shapes(slide, theme)
|
|
|
1512 |
except Exception as e:
|
1513 |
logger.warning(f"Title font sizing failed: {e}")
|
1514 |
|
1515 |
+
# Detect if this slide should have a diagram or image
|
1516 |
+
slide_title = slide_data.get('title', '')
|
1517 |
+
slide_content = slide_data.get('content', '')
|
1518 |
+
|
1519 |
+
# ๊ฒฐ๋ก /ํ์ด๋ผ์ดํธ ์ฌ๋ผ์ด๋ ๊ฐ์ง
|
1520 |
+
is_conclusion_slide = any(word in slide_title.lower() for word in
|
1521 |
+
['๊ฒฐ๋ก ', 'conclusion', '์์ฝ', 'summary', 'ํต์ฌ', 'key',
|
1522 |
+
'๋ง๋ฌด๋ฆฌ', 'closing', '์ ๋ฆฌ', 'takeaway', '์์ฌ์ ', 'implication'])
|
1523 |
+
|
1524 |
+
# ๋ค์ด์ด๊ทธ๋จ ๋๋ ์ด๋ฏธ์ง ์์ฑ ์ฌ๋ถ ๊ฒฐ์
|
1525 |
+
should_add_visual = False
|
1526 |
+
visual_type = None
|
1527 |
+
|
1528 |
+
# ๊ฒฐ๋ก ์ฌ๋ผ์ด๋๋ ํญ์ FLUX ์ด๋ฏธ์ง ์ถ๊ฐ
|
1529 |
+
if is_conclusion_slide and include_flux_images and FLUX_API_ENABLED:
|
1530 |
+
should_add_visual = True
|
1531 |
+
visual_type = ('flux_conclusion', None)
|
1532 |
+
elif include_diagrams:
|
1533 |
+
diagram_type = detect_diagram_type(slide_title, slide_content)
|
1534 |
+
if diagram_type:
|
1535 |
+
should_add_visual = True
|
1536 |
+
visual_type = ('diagram', diagram_type)
|
1537 |
+
elif not should_add_visual and include_flux_images and i % 3 == 0: # ๋งค 3๋ฒ์งธ ์ฌ๋ผ์ด๋์ FLUX ์ด๋ฏธ์ง
|
1538 |
+
should_add_visual = True
|
1539 |
+
visual_type = ('flux', None)
|
1540 |
+
|
1541 |
+
# ์๊ฐ์ ์์๊ฐ ์๋ ๊ฒฝ์ฐ ์ข-์ฐ ๋ ์ด์์ ์ ์ฉ
|
1542 |
+
if should_add_visual and layout_type not in ['section_header']:
|
1543 |
+
# ์ข์ธก์ ํ
์คํธ ๋ฐฐ์น
|
1544 |
+
left_box = slide.shapes.add_textbox(
|
1545 |
+
Inches(0.5), Inches(1.5), Inches(4.5), Inches(3.5)
|
1546 |
+
)
|
1547 |
+
left_tf = left_box.text_frame
|
1548 |
+
left_tf.clear()
|
1549 |
+
left_tf.text = slide_content
|
1550 |
+
left_tf.word_wrap = True
|
1551 |
+
force_font_size(left_tf, 14, theme)
|
1552 |
+
|
1553 |
+
# Apply emoji bullets
|
1554 |
+
for paragraph in left_tf.paragraphs:
|
1555 |
+
text = paragraph.text.strip()
|
1556 |
+
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
1557 |
+
clean_text = text.lstrip('-โขโ ')
|
1558 |
+
emoji = get_emoji_for_content(clean_text)
|
1559 |
+
paragraph.text = f"{emoji} {clean_text}"
|
1560 |
+
force_font_size(left_tf, 14, theme)
|
1561 |
+
|
1562 |
+
# ์ฐ์ธก์ ์๊ฐ์ ์์ ์ถ๊ฐ
|
1563 |
+
visual_added = False
|
1564 |
+
|
1565 |
+
if visual_type[0] == 'diagram':
|
1566 |
+
# ๋ค์ด์ด๊ทธ๋จ ์์ฑ
|
1567 |
+
logger.info(f"Generating {visual_type[1]} for slide {i+1}")
|
1568 |
+
diagram_json = generate_diagram_json(slide_title, slide_content, visual_type[1])
|
1569 |
|
1570 |
+
if diagram_json:
|
1571 |
+
diagram_path = generate_diagram_via_api(diagram_json, visual_type[1])
|
1572 |
+
if diagram_path and os.path.exists(diagram_path):
|
1573 |
+
try:
|
1574 |
+
# ๋ค์ด์ด๊ทธ๋จ ์ด๋ฏธ์ง ์ถ๊ฐ
|
1575 |
+
pic = slide.shapes.add_picture(
|
1576 |
+
diagram_path,
|
1577 |
+
Inches(5.2), Inches(1.5),
|
1578 |
+
width=Inches(4.3), height=Inches(3.0)
|
1579 |
+
)
|
1580 |
+
visual_added = True
|
1581 |
+
|
1582 |
+
# ์์ ํ์ผ ์ญ์
|
1583 |
+
os.unlink(diagram_path)
|
1584 |
+
except Exception as e:
|
1585 |
+
logger.error(f"Failed to add diagram: {e}")
|
1586 |
|
1587 |
+
elif visual_type[0] == 'flux_conclusion':
|
1588 |
+
# ๊ฒฐ๋ก ์ฌ๋ผ์ด๋์ฉ FLUX ์ด๋ฏธ์ง ์์ฑ
|
1589 |
+
logger.info(f"Generating conclusion FLUX image for slide {i+1}")
|
1590 |
+
conclusion_prompt = generate_conclusion_image_prompt(slide_title, slide_content)
|
1591 |
+
flux_image_path = generate_flux_image_via_api(conclusion_prompt)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1592 |
|
1593 |
+
if flux_image_path and os.path.exists(flux_image_path):
|
1594 |
+
try:
|
1595 |
+
# FLUX ์ด๋ฏธ์ง ์ถ๊ฐ
|
1596 |
+
pic = slide.shapes.add_picture(
|
1597 |
+
flux_image_path,
|
1598 |
+
Inches(5.2), Inches(1.5),
|
1599 |
+
width=Inches(4.3), height=Inches(3.0)
|
1600 |
+
)
|
1601 |
+
visual_added = True
|
1602 |
+
|
1603 |
+
# ์ด๋ฏธ์ง ์บก์
์ถ๊ฐ
|
1604 |
+
caption_box = slide.shapes.add_textbox(
|
1605 |
+
Inches(5.2), Inches(4.6), Inches(4.3), Inches(0.3)
|
1606 |
+
)
|
1607 |
+
caption_tf = caption_box.text_frame
|
1608 |
+
caption_tf.text = "Key Takeaway Visualization"
|
1609 |
+
caption_p = caption_tf.paragraphs[0]
|
1610 |
+
caption_p.font.size = Pt(10)
|
1611 |
+
caption_p.font.color.rgb = theme['colors']['secondary']
|
1612 |
+
caption_p.alignment = PP_ALIGN.CENTER
|
1613 |
+
|
1614 |
+
# ์์ ํ์ผ ์ญ์
|
1615 |
+
os.unlink(flux_image_path)
|
1616 |
+
except Exception as e:
|
1617 |
+
logger.error(f"Failed to add conclusion FLUX image: {e}")
|
1618 |
+
|
1619 |
+
elif visual_type[0] == 'flux':
|
1620 |
+
# FLUX ์ด๋ฏธ์ง ์์ฑ
|
1621 |
+
logger.info(f"Generating FLUX image for slide {i+1}")
|
1622 |
+
flux_prompt = generate_flux_prompt(slide_title, slide_content)
|
1623 |
+
flux_image_path = generate_flux_image_via_api(flux_prompt)
|
1624 |
|
1625 |
+
if flux_image_path and os.path.exists(flux_image_path):
|
1626 |
+
try:
|
1627 |
+
# FLUX ์ด๋ฏธ์ง ์ถ๊ฐ
|
1628 |
+
pic = slide.shapes.add_picture(
|
1629 |
+
flux_image_path,
|
1630 |
+
Inches(5.2), Inches(1.5),
|
1631 |
+
width=Inches(4.3), height=Inches(3.0)
|
1632 |
+
)
|
1633 |
+
visual_added = True
|
1634 |
+
|
1635 |
+
# ์์ ํ์ผ ์ญ์
|
1636 |
+
os.unlink(flux_image_path)
|
1637 |
+
except Exception as e:
|
1638 |
+
logger.error(f"Failed to add FLUX image: {e}")
|
1639 |
+
|
1640 |
+
# ์๊ฐ์ ์์๊ฐ ์ถ๊ฐ๋์ง ์์ ๊ฒฝ์ฐ ํ๋ ์ด์คํ๋ ์ถ๊ฐ
|
1641 |
+
if not visual_added:
|
1642 |
+
placeholder_box = slide.shapes.add_textbox(
|
1643 |
+
Inches(5.2), Inches(2.5), Inches(4.3), Inches(1.0)
|
1644 |
)
|
1645 |
+
placeholder_tf = placeholder_box.text_frame
|
1646 |
+
placeholder_tf.text = f"{visual_type[1] if visual_type[0] == 'diagram' else 'Visual'} Placeholder"
|
1647 |
+
placeholder_tf.paragraphs[0].font.size = Pt(14)
|
1648 |
+
placeholder_tf.paragraphs[0].font.color.rgb = theme['colors']['secondary']
|
1649 |
+
placeholder_tf.paragraphs[0].alignment = PP_ALIGN.CENTER
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1650 |
|
1651 |
else:
|
1652 |
+
# ๊ธฐ๋ณธ ๋ ์ด์์ (์๊ฐ์ ์์ ์์)
|
1653 |
+
if layout_type == 'section_header':
|
1654 |
+
# Section header content handling
|
1655 |
+
content = slide_data.get('content', '')
|
1656 |
+
if content:
|
1657 |
+
logger.info(f"Adding content to section header slide {i+1}: {content[:50]}...")
|
1658 |
+
textbox = slide.shapes.add_textbox(
|
1659 |
+
Inches(1), Inches(3.5), Inches(8), Inches(1.5)
|
1660 |
+
)
|
1661 |
+
tf = textbox.text_frame
|
1662 |
+
tf.clear()
|
1663 |
+
tf.text = content
|
1664 |
+
tf.word_wrap = True
|
1665 |
+
|
1666 |
+
for paragraph in tf.paragraphs:
|
1667 |
+
paragraph.font.name = theme['fonts']['body']
|
1668 |
+
paragraph.font.size = Pt(16)
|
1669 |
+
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
1670 |
+
paragraph.alignment = PP_ALIGN.CENTER
|
1671 |
+
|
1672 |
+
# Add decorative line
|
1673 |
+
line = slide.shapes.add_shape(
|
1674 |
+
MSO_SHAPE.RECTANGLE, Inches(3), Inches(3.2), Inches(4), Pt(4)
|
1675 |
+
)
|
1676 |
+
line.fill.solid()
|
1677 |
+
line.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
1678 |
+
line.line.fill.background()
|
1679 |
|
1680 |
+
elif layout_type == 'two_content':
|
1681 |
+
content = slide_data.get('content', '')
|
1682 |
+
if content:
|
1683 |
+
logger.info(f"Creating two-column layout for slide {i+1}")
|
1684 |
+
content_lines = content.split('\n')
|
1685 |
+
mid_point = len(content_lines) // 2
|
1686 |
+
|
1687 |
+
# Left column
|
1688 |
+
left_box = slide.shapes.add_textbox(
|
1689 |
+
Inches(0.5), Inches(1.5), Inches(4.5), Inches(3.5)
|
1690 |
+
)
|
1691 |
+
left_tf = left_box.text_frame
|
1692 |
+
left_tf.clear()
|
1693 |
+
left_content = '\n'.join(content_lines[:mid_point])
|
1694 |
+
if left_content:
|
1695 |
+
left_tf.text = left_content
|
1696 |
+
left_tf.word_wrap = True
|
1697 |
+
force_font_size(left_tf, 14, theme)
|
1698 |
+
|
1699 |
+
# Apply emoji bullets
|
1700 |
+
for paragraph in left_tf.paragraphs:
|
1701 |
+
text = paragraph.text.strip()
|
1702 |
+
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
1703 |
+
clean_text = text.lstrip('-โขโ ')
|
1704 |
+
emoji = get_emoji_for_content(clean_text)
|
1705 |
+
paragraph.text = f"{emoji} {clean_text}"
|
1706 |
+
force_font_size(left_tf, 14, theme)
|
1707 |
+
|
1708 |
+
# Right column
|
1709 |
+
right_box = slide.shapes.add_textbox(
|
1710 |
+
Inches(5), Inches(1.5), Inches(4.5), Inches(3.5)
|
1711 |
+
)
|
1712 |
+
right_tf = right_box.text_frame
|
1713 |
+
right_tf.clear()
|
1714 |
+
right_content = '\n'.join(content_lines[mid_point:])
|
1715 |
+
if right_content:
|
1716 |
+
right_tf.text = right_content
|
1717 |
+
right_tf.word_wrap = True
|
1718 |
+
force_font_size(right_tf, 14, theme)
|
1719 |
+
|
1720 |
+
# Apply emoji bullets
|
1721 |
+
for paragraph in right_tf.paragraphs:
|
1722 |
+
text = paragraph.text.strip()
|
1723 |
+
if text and text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
1724 |
+
clean_text = text.lstrip('-โขโ ')
|
1725 |
+
emoji = get_emoji_for_content(clean_text)
|
1726 |
+
paragraph.text = f"{emoji} {clean_text}"
|
1727 |
+
force_font_size(right_tf, 14, theme)
|
1728 |
|
1729 |
+
else:
|
1730 |
+
# Regular content
|
1731 |
+
content = slide_data.get('content', '')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1732 |
|
1733 |
+
logger.info(f"Slide {i+1} - Content to add: '{content[:100]}...' (length: {len(content)})")
|
|
|
1734 |
|
1735 |
+
if include_charts and slide_data.get('chart_data'):
|
1736 |
+
create_chart_slide(slide, slide_data['chart_data'], theme)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1737 |
|
1738 |
+
if content and content.strip():
|
1739 |
+
textbox = slide.shapes.add_textbox(
|
1740 |
+
Inches(0.5), # left
|
1741 |
+
Inches(1.5), # top
|
1742 |
+
Inches(9), # width
|
1743 |
+
Inches(3.5) # height
|
1744 |
+
)
|
1745 |
+
|
1746 |
+
tf = textbox.text_frame
|
1747 |
+
tf.clear()
|
1748 |
+
|
1749 |
+
tf.text = content.strip()
|
1750 |
+
tf.word_wrap = True
|
1751 |
+
|
1752 |
+
tf.margin_left = Inches(0.1)
|
1753 |
+
tf.margin_right = Inches(0.1)
|
1754 |
+
tf.margin_top = Inches(0.05)
|
1755 |
+
tf.margin_bottom = Inches(0.05)
|
1756 |
+
|
1757 |
+
force_font_size(tf, 16, theme)
|
1758 |
+
|
1759 |
+
for p_idx, paragraph in enumerate(tf.paragraphs):
|
1760 |
+
if paragraph.text.strip():
|
1761 |
+
text = paragraph.text.strip()
|
1762 |
+
if text.startswith(('-', 'โข', 'โ')) and not has_emoji(text):
|
1763 |
+
clean_text = text.lstrip('-โขโ ')
|
1764 |
+
emoji = get_emoji_for_content(clean_text)
|
1765 |
+
paragraph.text = f"{emoji} {clean_text}"
|
1766 |
+
|
1767 |
+
if paragraph.runs:
|
1768 |
+
for run in paragraph.runs:
|
1769 |
+
run.font.size = Pt(16)
|
1770 |
+
run.font.name = theme['fonts']['body']
|
1771 |
+
run.font.color.rgb = theme['colors']['text']
|
1772 |
+
else:
|
1773 |
+
paragraph.font.size = Pt(16)
|
1774 |
+
paragraph.font.name = theme['fonts']['body']
|
1775 |
+
paragraph.font.color.rgb = theme['colors']['text']
|
1776 |
+
|
1777 |
+
paragraph.space_before = Pt(6)
|
1778 |
+
paragraph.space_after = Pt(6)
|
1779 |
+
paragraph.line_spacing = 1.3
|
1780 |
+
|
1781 |
+
logger.info(f"Successfully added content to slide {i+1}")
|
1782 |
+
else:
|
1783 |
+
logger.warning(f"Slide {i+1} has no content or empty content")
|
1784 |
|
1785 |
# Add slide notes if available
|
1786 |
if slide_data.get('notes'):
|
|
|
2024 |
yield f"โ ๏ธ Error generating content: {str(e)}"
|
2025 |
|
2026 |
##############################################################################
|
2027 |
+
# Main PPT Generation Function - IMPROVED VERSION with Enhanced Features
|
2028 |
##############################################################################
|
2029 |
def generate_ppt(
|
2030 |
topic: str,
|
|
|
2036 |
font_style: str = "modern",
|
2037 |
layout_style: str = "consistent",
|
2038 |
include_charts: bool = False,
|
2039 |
+
include_ai_image: bool = False,
|
2040 |
+
include_diagrams: bool = False,
|
2041 |
+
include_flux_images: bool = False
|
2042 |
) -> tuple:
|
2043 |
+
"""Main function to generate PPT with advanced design and enhanced visuals"""
|
2044 |
|
2045 |
if not PPTX_AVAILABLE:
|
2046 |
return None, "โ python-pptx ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ค์น๋์ง ์์์ต๋๋ค.\n\n์ค์น ๋ช
๋ น: pip install python-pptx", ""
|
|
|
2052 |
return None, "โ ์ฌ๋ผ์ด๋ ์๋ 3์ฅ ์ด์ 20์ฅ ์ดํ๋ก ์ค์ ํด์ฃผ์ธ์.", ""
|
2053 |
|
2054 |
try:
|
2055 |
+
# FLUX API ์ด๊ธฐํ (ํ์ง ์ด๋ฏธ์ง์ฉ)
|
2056 |
+
if include_ai_image and not FLUX_API_ENABLED:
|
2057 |
+
yield None, "๐ FLUX ์ด๋ฏธ์ง ์์ฑ API์ ์ฐ๊ฒฐํ๋ ์ค...", ""
|
2058 |
+
if initialize_flux_api():
|
2059 |
+
yield None, "โ
FLUX API ์ฐ๊ฒฐ ์ฑ๊ณต!", ""
|
2060 |
else:
|
2061 |
include_ai_image = False
|
2062 |
+
yield None, "โ ๏ธ FLUX API ์ฐ๊ฒฐ ์คํจ. AI ์ด๋ฏธ์ง ์์ด ์งํํฉ๋๋ค.", ""
|
2063 |
+
|
2064 |
+
# ๋ค์ด์ด๊ทธ๋จ API ์ด๊ธฐํ
|
2065 |
+
if include_diagrams and not DIAGRAM_API_ENABLED:
|
2066 |
+
yield None, "๐ ๋ค์ด์ด๊ทธ๋จ ์์ฑ API์ ์ฐ๊ฒฐํ๋ ์ค...", ""
|
2067 |
+
if initialize_diagram_api():
|
2068 |
+
yield None, "โ
๋ค์ด์ด๊ทธ๋จ API ์ฐ๊ฒฐ ์ฑ๊ณต!", ""
|
2069 |
+
else:
|
2070 |
+
include_diagrams = False
|
2071 |
+
yield None, "โ ๏ธ ๋ค์ด์ด๊ทธ๋จ API ์ฐ๊ฒฐ ์คํจ. ๋ค์ด์ด๊ทธ๋จ ์์ด ์งํํฉ๋๋ค.", ""
|
2072 |
+
|
2073 |
+
# FLUX API ์ด๊ธฐํ (์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง์ฉ)
|
2074 |
+
if include_flux_images and not FLUX_API_ENABLED:
|
2075 |
+
yield None, "๐ FLUX ์ด๋ฏธ์ง ์์ฑ API์ ์ฐ๊ฒฐํ๋ ์ค...", ""
|
2076 |
+
if initialize_flux_api():
|
2077 |
+
yield None, "โ
FLUX API ์ฐ๊ฒฐ ์ฑ๊ณต!", ""
|
2078 |
+
else:
|
2079 |
+
include_flux_images = False
|
2080 |
+
yield None, "โ ๏ธ FLUX API ์ฐ๊ฒฐ ์คํจ. ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง ์์ด ์งํํฉ๋๋ค.", ""
|
2081 |
|
2082 |
# Process reference files if provided
|
2083 |
additional_context = ""
|
|
|
2138 |
# Debug logging
|
2139 |
logger.info(f"Parsed {len(slides_data)} slides from LLM response")
|
2140 |
logger.info(f"Design theme: {design_theme}, Layout style: {layout_style}")
|
2141 |
+
logger.info(f"Include diagrams: {include_diagrams}, Include FLUX images: {include_flux_images}")
|
2142 |
|
2143 |
if not slides_data:
|
2144 |
# Show the raw response for debugging
|
|
|
2149 |
yield None, error_msg, llm_response
|
2150 |
return
|
2151 |
|
2152 |
+
# AI ์ด๋ฏธ์ง ๋ฐ ๋ค์ด์ด๊ทธ๋จ ์์ฑ ์๋ฆผ
|
2153 |
+
visual_features = []
|
2154 |
+
if include_ai_image and FLUX_API_ENABLED:
|
2155 |
+
visual_features.append("AI 3D ํ์ง ์ด๋ฏธ์ง")
|
2156 |
+
if include_diagrams and DIAGRAM_API_ENABLED:
|
2157 |
+
visual_features.append("๋ค์ด์ด๊ทธ๋จ")
|
2158 |
+
if include_flux_images and FLUX_API_ENABLED:
|
2159 |
+
visual_features.append("AI ์์ฑ ์ด๋ฏธ์ง")
|
2160 |
+
|
2161 |
+
if visual_features:
|
2162 |
+
yield None, f"๐ ์ฌ๋ผ์ด๋ ์์ฑ ์๋ฃ!\n\n๐จ ์์ฑ ์ค: {', '.join(visual_features)}... (์๊ฐ์ด ์์๋ ์ ์์ต๋๋ค)", llm_response
|
2163 |
|
2164 |
# Create PPT file with advanced design
|
2165 |
+
ppt_path = create_advanced_ppt_from_content(
|
2166 |
+
slides_data,
|
2167 |
+
topic,
|
2168 |
+
design_theme,
|
2169 |
+
include_charts,
|
2170 |
+
include_ai_image,
|
2171 |
+
include_diagrams,
|
2172 |
+
include_flux_images
|
2173 |
+
)
|
2174 |
|
2175 |
success_msg = f"โ
PPT ํ์ผ์ด ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋์์ต๋๋ค!\n\n"
|
2176 |
success_msg += f"๐ ์ฃผ์ : {topic}\n"
|
2177 |
success_msg += f"๐ ์ฌ๋ผ์ด๋ ์: {len(slides_data)}์ฅ\n"
|
2178 |
success_msg += f"๐จ ๋์์ธ ํ
๋ง: {DESIGN_THEMES[design_theme]['name']}\n"
|
2179 |
success_msg += f"๐ ๋ ์ด์์ ์คํ์ผ: {layout_style}\n"
|
2180 |
+
|
2181 |
+
if include_ai_image and FLUX_API_ENABLED:
|
2182 |
+
success_msg += f"๐ผ๏ธ AI ์์ฑ ํ์ง ์ด๋ฏธ์ง ํฌํจ\n"
|
2183 |
+
if include_diagrams and DIAGRAM_API_ENABLED:
|
2184 |
+
success_msg += f"๐ AI ์์ฑ ๋ค์ด์ด๊ทธ๋จ ํฌํจ\n"
|
2185 |
+
if include_flux_images and FLUX_API_ENABLED:
|
2186 |
+
success_msg += f"๐จ AI ์์ฑ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง ํฌํจ\n"
|
2187 |
+
|
2188 |
success_msg += f"๐ ์์ฑ๋ ์ฌ๋ผ์ด๋:\n"
|
2189 |
|
2190 |
for i, slide in enumerate(slides_data[:5]): # Show first 5 slides
|
|
|
2388 |
# ๐ฏ AI ๊ธฐ๋ฐ PPT ์๋ ์์ฑ ์์คํ
Pro
|
2389 |
|
2390 |
๊ณ ๊ธ ๋์์ธ ํ
๋ง์ ๋ ์ด์์์ ํ์ฉํ ์ ๋ฌธ์ ์ธ ํ๋ ์ ํ
์ด์
์ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
2391 |
+
FLUX AI๋ก ์์ฑํ ๊ณ ํ์ง ์ด๋ฏธ์ง์ ๋ค์ด์ด๊ทธ๋จ์ ํฌํจํ์ฌ ์๊ฐ์ ์ผ๋ก ํ๋ถํ PPT๋ฅผ ๋ง๋ญ๋๋ค.
|
2392 |
"""
|
2393 |
)
|
2394 |
|
|
|
2471 |
info="CSV ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ์ฐจํธ ์์ฑ"
|
2472 |
)
|
2473 |
|
2474 |
+
# Visual Enhancement Options
|
2475 |
+
gr.Markdown("<div class='section-header'>๐ผ๏ธ ์๊ฐ์ ํฅ์ ์ต์
</div>")
|
2476 |
+
|
2477 |
+
with gr.Row():
|
2478 |
+
include_ai_image = gr.Checkbox(
|
2479 |
+
label="๐ผ๏ธ AI ํ์ง ์ด๋ฏธ์ง",
|
2480 |
+
value=False,
|
2481 |
+
info="FLUX๋ก ์์ฑํ ํ์ง ์ด๋ฏธ์ง ์ถ๊ฐ"
|
2482 |
+
)
|
2483 |
+
|
2484 |
+
include_diagrams = gr.Checkbox(
|
2485 |
+
label="๐ AI ๋ค์ด์ด๊ทธ๋จ",
|
2486 |
+
value=False,
|
2487 |
+
info="๋ด์ฉ์ ๋ง๋ ๋ค์ด์ด๊ทธ๋จ ์๋ ์์ฑ"
|
2488 |
+
)
|
2489 |
+
|
2490 |
+
include_flux_images = gr.Checkbox(
|
2491 |
+
label="๐จ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง",
|
2492 |
+
value=False,
|
2493 |
+
info="์ผ๋ถ ์ฌ๋ผ์ด๋์ FLUX ์ด๋ฏธ์ง ์ถ๊ฐ"
|
2494 |
+
)
|
2495 |
|
2496 |
reference_files = gr.File(
|
2497 |
label="๐ ์ฐธ๊ณ ์๋ฃ (์ ํ์ฌํญ)",
|
|
|
2535 |
1. **PPT ์ฃผ์ ์
๋ ฅ**: ๊ตฌ์ฒด์ ์ธ ์ฃผ์ ์ผ์๋ก ๋ ์ข์ ๊ฒฐ๊ณผ
|
2536 |
2. **์ฌ๋ผ์ด๋ ์ ์ ํ**: 3-20์ฅ ๋ฒ์์์ ์ ํ
|
2537 |
3. **๋์์ธ ํ
๋ง ์ ํ**: 5๊ฐ์ง ์ ๋ฌธ์ ์ธ ํ
๋ง ์ค ์ ํ
|
2538 |
+
4. **์๊ฐ์ ์ต์
์ค์ **: AI ์ด๋ฏธ์ง, ๋ค์ด์ด๊ทธ๋จ, FLUX ์ด๋ฏธ์ง ์ถ๊ฐ
|
2539 |
+
5. **์ฐธ๊ณ ์๋ฃ ์
๋ก๋**: PDF, CSV, TXT ํ์ผ ์ง์
|
2540 |
+
6. **์์ฑ ๋ฒํผ ํด๋ฆญ**: AI๊ฐ ์๋์ผ๋ก PPT ์์ฑ
|
2541 |
+
|
2542 |
+
### ๐จ ์๋ก์ด ๊ธฐ๋ฅ
|
2543 |
+
- **FLUX AI ํ์ง ์ด๋ฏธ์ง**: FLUX API๋ฅผ ํตํ ๊ณ ํ์ง ํ์ง ์ด๋ฏธ์ง (์ฐ์ธก ๋ฐฐ์น)
|
2544 |
+
- **AI ๋ค์ด์ด๊ทธ๋จ**: ๋ด์ฉ ๋ถ์ ํ ์ ์ ํ ๋ค์ด์ด๊ทธ๋จ ์๋ ์์ฑ
|
2545 |
+
- **FLUX ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง**: ์ฃผ์ ์ฌ๋ผ์ด๋์ AI ์์ฑ ์ด๋ฏธ์ง ์ถ๊ฐ
|
2546 |
+
- **๊ฒฐ๋ก ์ฌ๋ผ์ด๋ ๊ฐ์กฐ**: ๊ฒฐ๋ก /์์ฝ ์ฌ๋ผ์ด๋์ ํน๋ณํ FLUX ์ด๋ฏธ์ง ์ถ๊ฐ
|
2547 |
+
- **์ข-์ฐ ๋ ์ด์์**: ํ
์คํธ๋ ์ข์ธก, ์๊ฐ์ ์์๋ ์ฐ์ธก ๋ฐฐ์น
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2548 |
|
2549 |
### ๐ก ๊ณ ๊ธ ํ
|
2550 |
+
- ํ์ง์ ์ ๋ชฉ/๋ถ์ ๋ ์ค์ ์๋จ, AI ์ด๋ฏธ์ง๋ ์ฐ์ธก์ ๋ฐฐ์น๋ฉ๋๋ค
|
2551 |
+
- ๊ฒฐ๋ก /์์ฝ ์ฌ๋ผ์ด๋๋ ์๋์ผ๋ก ๊ฐ์ง๋์ด ํน๋ณํ ์ด๋ฏธ์ง๊ฐ ์ถ๊ฐ๋ฉ๋๋ค
|
2552 |
+
- FLUX ์ด๋ฏธ์ง๋ ์ฌ๋ผ์ด๋ ๋ด์ฉ์ ๋ถ์ํ์ฌ ๊ด๋ จ ์ด๋ฏธ์ง๋ฅผ ์์ฑํฉ๋๋ค
|
|
|
2553 |
"""
|
2554 |
)
|
2555 |
|
2556 |
# Examples
|
2557 |
gr.Examples(
|
2558 |
examples=[
|
2559 |
+
["์ธ๊ณต์ง๋ฅ์ ๋ฏธ๋์ ์ฐ์
์ ์ฉ ์ฌ๋ก", 10, False, True, [], "professional", "modern", "consistent", False, True, True, False],
|
2560 |
+
["2024๋
๋์งํธ ๋ง์ผํ
ํธ๋ ๋", 12, True, True, [], "modern", "modern", "consistent", False, True, True, True],
|
2561 |
+
["๊ธฐํ๋ณํ์ ์ง์๊ฐ๋ฅํ ๋ฐ์ ", 15, True, True, [], "nature", "classic", "consistent", False, True, True, True],
|
2562 |
+
["์คํํธ์
์ฌ์
๊ณํ์", 8, False, True, [], "creative", "modern", "varied", False, True, True, True],
|
2563 |
],
|
2564 |
inputs=[topic_input, num_slides, use_web_search, use_korean, reference_files,
|
2565 |
+
design_theme, font_style, layout_style, include_charts, include_ai_image,
|
2566 |
+
include_diagrams, include_flux_images],
|
2567 |
)
|
2568 |
|
2569 |
# Event handler
|
|
|
2579 |
font_style,
|
2580 |
layout_style,
|
2581 |
include_charts,
|
2582 |
+
include_ai_image,
|
2583 |
+
include_diagrams,
|
2584 |
+
include_flux_images
|
2585 |
],
|
2586 |
outputs=[download_file, status_text, content_preview]
|
2587 |
)
|
2588 |
|
2589 |
+
# Initialize APIs on startup
|
2590 |
if __name__ == "__main__":
|
2591 |
+
# Try to initialize APIs
|
2592 |
+
if FLUX_API_URL:
|
2593 |
+
initialize_flux_api()
|
2594 |
+
if DIAGRAM_API_URL:
|
2595 |
+
initialize_diagram_api()
|
2596 |
+
|
2597 |
demo.launch()
|