openfree commited on
Commit
1be82b3
ยท
verified ยท
1 Parent(s): b7a60fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +834 -327
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
- AI_IMAGE_API_URL = "http://211.233.58.201:7971/"
55
- AI_IMAGE_ENABLED = False
56
- ai_image_client = None
57
 
58
- def initialize_ai_image_api():
59
- """AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ API ์ดˆ๊ธฐํ™”"""
60
- global AI_IMAGE_ENABLED, ai_image_client
61
 
62
  try:
63
- logger.info("Connecting to AI image generation API...")
64
- ai_image_client = Client(AI_IMAGE_API_URL)
65
- AI_IMAGE_ENABLED = True
66
- logger.info("AI image generation API connected successfully")
67
  return True
68
  except Exception as e:
69
- logger.error(f"Failed to connect to AI image API: {e}")
70
- AI_IMAGE_ENABLED = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # wbgmsst ์Šคํƒ€์ผ์„ ์œ„ํ•œ ํ•œ๊ธ€ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
599
- prompt = f"wbgmsst, 3D, {' '.join(unique_keywords)}๋ฅผ ์ƒ์ง•ํ•˜๋Š” ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ์Šคํƒ€์ผ์˜ {style} ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ด์…˜, ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ, ๊น”๋”ํ•œ ๋ฏธ๋‹ˆ๋ฉ€๋ฆฌ์Šคํ‹ฑ ๊ตฌ์„ฑ, ์ „๋ฌธ์ ์ธ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ํ‘œ์ง€, ๊ณ ํ’ˆ์งˆ, ์ŠคํŠœ๋””์˜ค ์กฐ๋ช…"
600
 
601
  return prompt
602
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
604
 
605
- def generate_ai_cover_image_via_api(topic: str, slides_data: list) -> Optional[str]:
606
- """API๋ฅผ ํ†ตํ•ด AI ํ‘œ์ง€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ(PNG ๋ณ€ํ™˜ ํฌํ•จ)"""
607
- if not AI_IMAGE_ENABLED or not ai_image_client:
608
- logger.warning("AI image generation API is not available")
609
  return None
610
-
611
  try:
612
- # โ”€โ”€ 1. ํ”„๋กฌํ”„ํŠธ ๋ฐ ํŒŒ๋ผ๋ฏธํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
613
- prompt = generate_cover_image_prompt(topic, slides_data)
614
- logger.info(f"Generated image prompt: {prompt}")
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
- seed=seed,
628
- api_name="/process_and_save_image"
 
 
 
 
 
 
 
 
629
  )
630
- logger.info(f"API call successful. Result type: {type(result)}")
631
-
632
- # โ”€โ”€ 2. ๋‚ด๋ถ€ ํ—ฌํผ: WEBP โ†’ PNG ๋ณ€ํ™˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
633
- def _to_png(src_path: str) -> Optional[str]:
634
- try:
635
- with Image.open(src_path) as im:
636
  png_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
637
- im.save(png_tmp.name, format="PNG")
638
- logger.info(f"Converted {src_path} โ†’ {png_tmp.name}")
639
  return png_tmp.name
640
- except Exception as e:
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 AI image via API: {e}", exc_info=True)
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 image option"""
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
- # AI ์ด๋ฏธ์ง€(ํ‘œ์ง€ ๊ทธ๋ฆผ)
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 = int(title_top_position)
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(32)
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 = int(title_top_position + Inches(1.0))
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(18)
1140
  p2.font.color.rgb = RGBColor(255, 255, 255)
1141
  p2.alignment = PP_ALIGN.CENTER
1142
 
1143
- bodyPr2 = tf2._txBody.bodyPr
1144
- bodyPr2.set('vert', 'horz')
1145
-
1146
- slide.shapes._spTree.remove(subtitle_shape._element)
1147
- slide.shapes._spTree.append(subtitle_shape._element)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Add content based on layout - COMPLETELY FIXED VERSION
1196
- if layout_type == 'section_header':
1197
- # Section header content handling
1198
- content = slide_data.get('content', '')
1199
- if content:
1200
- logger.info(f"Adding content to section header slide {i+1}: {content[:50]}...")
1201
- textbox = slide.shapes.add_textbox(
1202
- Inches(1), Inches(3.5), Inches(8), Inches(1.5)
1203
- )
1204
- tf = textbox.text_frame
1205
- tf.clear()
1206
- tf.text = content
1207
- tf.word_wrap = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1208
 
1209
- for paragraph in tf.paragraphs:
1210
- paragraph.font.name = theme['fonts']['body']
1211
- paragraph.font.size = Pt(16)
1212
- paragraph.font.color.rgb = RGBColor(255, 255, 255)
1213
- paragraph.alignment = PP_ALIGN.CENTER
 
 
 
 
 
 
 
 
 
 
 
1214
 
1215
- # Add decorative line
1216
- line = slide.shapes.add_shape(
1217
- MSO_SHAPE.RECTANGLE, Inches(3), Inches(3.2), Inches(4), Pt(4)
1218
- )
1219
- line.fill.solid()
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
- # Left column
1231
- left_box = slide.shapes.add_textbox(
1232
- Inches(0.5), Inches(1.5), Inches(4.5), Inches(3.5)
1233
- )
1234
- left_tf = left_box.text_frame
1235
- left_tf.clear()
1236
- left_content = '\n'.join(content_lines[:mid_point])
1237
- if left_content:
1238
- left_tf.text = left_content
1239
- left_tf.word_wrap = True
1240
- force_font_size(left_tf, 14, theme)
1241
-
1242
- # Apply emoji bullets
1243
- for paragraph in left_tf.paragraphs:
1244
- text = paragraph.text.strip()
1245
- if text and text.startswith(('-', 'โ€ข', 'โ—')) and not has_emoji(text):
1246
- clean_text = text.lstrip('-โ€ขโ— ')
1247
- emoji = get_emoji_for_content(clean_text)
1248
- paragraph.text = f"{emoji} {clean_text}"
1249
- force_font_size(left_tf, 14, theme)
 
 
 
 
 
 
 
 
 
 
 
1250
 
1251
- # Right column
1252
- right_box = slide.shapes.add_textbox(
1253
- Inches(5), Inches(1.5), Inches(4.5), Inches(3.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1254
  )
1255
- right_tf = right_box.text_frame
1256
- right_tf.clear()
1257
- right_content = '\n'.join(content_lines[mid_point:])
1258
- if right_content:
1259
- right_tf.text = right_content
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
- # Regular content - MOST IMPORTANT PART - COMPLETELY FIXED
1274
- content = slide_data.get('content', '')
1275
-
1276
- # ๋””๋ฒ„๊น… ๋กœ๊ทธ
1277
- logger.info(f"Slide {i+1} - Content to add: '{content[:100]}...' (length: {len(content)})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1278
 
1279
- if include_charts and slide_data.get('chart_data'):
1280
- create_chart_slide(slide, slide_data['chart_data'], theme)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1281
 
1282
- # content๊ฐ€ ์žˆ์œผ๋ฉด ๋ฌด์กฐ๊ฑด ํ…์ŠคํŠธ๋ฐ•์Šค ์ถ”๊ฐ€
1283
- if content and content.strip():
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
- for p_idx, paragraph in enumerate(tf.paragraphs):
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
- logger.info(f"Successfully added content to slide {i+1}")
1336
- else:
1337
- logger.warning(f"Slide {i+1} has no content or empty content")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 API Image
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 API-based AI image"""
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
- # AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์š”์ฒญ๋˜์—ˆ์ง€๋งŒ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
1608
- if include_ai_image and not AI_IMAGE_ENABLED:
1609
- yield None, "๐Ÿ”„ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ API์— ์—ฐ๊ฒฐํ•˜๋Š” ์ค‘...", ""
1610
- if initialize_ai_image_api():
1611
- yield None, "โœ… AI ์ด๋ฏธ์ง€ API ์—ฐ๊ฒฐ ์„ฑ๊ณต!", ""
1612
  else:
1613
  include_ai_image = False
1614
- yield None, "โš ๏ธ AI ์ด๋ฏธ์ง€ API ์—ฐ๊ฒฐ ์‹คํŒจ. AI ์ด๋ฏธ์ง€ ์—†์ด ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if include_ai_image and AI_IMAGE_ENABLED:
1687
- yield None, f"๐Ÿ“ ์Šฌ๋ผ์ด๋“œ ์ƒ์„ฑ ์™„๋ฃŒ!\n\n๐ŸŽจ AI 3D ํ‘œ์ง€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘... (30์ดˆ ์ •๋„ ์†Œ์š”)", llm_response
 
 
 
 
 
 
 
 
1688
 
1689
  # Create PPT file with advanced design
1690
- ppt_path = create_advanced_ppt_from_content(slides_data, topic, design_theme, include_charts, include_ai_image)
 
 
 
 
 
 
 
 
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
- if include_ai_image and AI_IMAGE_ENABLED:
1698
- success_msg += f"๐Ÿ–ผ๏ธ AI ์ƒ์„ฑ 3D ์Šคํƒ€์ผ ํ‘œ์ง€ ์ด๋ฏธ์ง€ ํฌํ•จ\n"
 
 
 
 
 
 
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
- ๊ฐ ์Šฌ๋ผ์ด๋“œ๋งˆ๋‹ค 5๊ฐœ์˜ ํ•ต์‹ฌ ํฌ์ธํŠธ์™€ ๋ฐœํ‘œ์ž ๋…ธํŠธ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
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
- include_ai_image = gr.Checkbox(
1986
- label="๐Ÿ–ผ๏ธ AI 3D ํ‘œ์ง€ ์ด๋ฏธ์ง€",
1987
- value=False,
1988
- info="AI๋กœ ์ƒ์„ฑํ•œ 3D ์Šคํƒ€์ผ ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์ง€์— ์ถ”๊ฐ€ (์ƒ์„ฑ ์‹œ๊ฐ„ 30์ดˆ ์ถ”๊ฐ€)"
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. **AI 3D ์ด๋ฏธ์ง€**: ์ฃผ์ œ๋ฅผ ์ƒ์ง•ํ•˜๋Š” 3D ์Šคํƒ€์ผ ํ‘œ์ง€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
2035
- 6. **์ฐธ๊ณ  ์ž๋ฃŒ ์—…๋กœ๋“œ**: PDF, CSV, TXT ํŒŒ์ผ ์ง€์›
2036
- 7. **์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ**: AI๊ฐ€ ์ž๋™์œผ๋กœ PPT ์ƒ์„ฑ
2037
-
2038
- ### ๐ŸŽจ ๋””์ž์ธ ํ…Œ๋งˆ ํŠน์ง•
2039
- - **ํ”„๋กœํŽ˜์…”๋„**: ๋น„์ฆˆ๋‹ˆ์Šค ํ”„๋ ˆ์  ํ…Œ์ด์…˜์— ์ ํ•ฉํ•œ ๊น”๋”ํ•œ ๋””์ž์ธ
2040
- - **๋ชจ๋˜**: ํŠธ๋ Œ๋””ํ•˜๊ณ  ์„ธ๋ จ๋œ ์ƒ‰์ƒ ์กฐํ•ฉ
2041
- - **์ž์—ฐ**: ํŽธ์•ˆํ•˜๊ณ  ์นœ๊ทผํ•œ ๋А๋‚Œ์˜ ์ž์—ฐ ์ƒ‰์ƒ
2042
- - **ํฌ๋ฆฌ์—์ดํ‹ฐ๋ธŒ**: ๋Œ€๋‹ดํ•˜๊ณ  ํ™œ๊ธฐ์ฐฌ ์ƒ‰์ƒ์œผ๋กœ ์ฃผ๋ชฉ๋„ ๋†’์Œ
2043
- - **๋ฏธ๋‹ˆ๋ฉ€**: ๊น”๋”ํ•˜๊ณ  ๋‹จ์ˆœํ•œ ํ‘๋ฐฑ ๋””์ž์ธ
2044
-
2045
- ### โœจ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ
2046
- - **5๊ฐœ ํ•ต์‹ฌ ํฌ์ธํŠธ**: ๊ฐ ์Šฌ๋ผ์ด๋“œ๋งˆ๋‹ค 5๊ฐœ์˜ ํ•ต์‹ฌ ๋‚ด์šฉ
2047
- - **๋ฐœํ‘œ์ž ๋…ธํŠธ**: ๊ฐ ์Šฌ๋ผ์ด๋“œ๋งˆ๋‹ค ๋ฐœํ‘œ์ž๋ฅผ ์œ„ํ•œ ๋…ธํŠธ ์ž๋™ ์ƒ์„ฑ
2048
- - **ํ–ฅ์ƒ๋œ ํฐํŠธ ํฌ๊ธฐ**: ๋” ํฐ ํฐํŠธ๋กœ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ
2049
- - **AI 3D ์ด๋ฏธ์ง€**: API๋ฅผ ํ†ตํ•œ ๊ณ ํ’ˆ์งˆ 3D ์Šคํƒ€์ผ ํ‘œ์ง€ ์ด๋ฏธ์ง€ (ํ•œ๊ธ€ ํ”„๋กฌํ”„ํŠธ ์ง€์›)
2050
 
2051
  ### ๐Ÿ’ก ๊ณ ๊ธ‰ ํŒ
2052
- - CSV ํŒŒ์ผ ์—…๋กœ๋“œ ์‹œ '์ฐจํŠธ ํฌํ•จ' ์˜ต์…˜์œผ๋กœ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ๊ฐ€๋Šฅ
2053
- - ์›น ๊ฒ€์ƒ‰ ํ™œ์„ฑํ™”๋กœ ์ตœ์‹  ์ •๋ณด ๋ฐ˜์˜
2054
- - AI ์ด๋ฏธ์ง€๋Š” ์ฃผ์ œ์˜ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ž๋™ ์ƒ์„ฑ
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()