openfree commited on
Commit
55d6d84
ยท
verified ยท
1 Parent(s): 396a304

Update app-BACKUP.py

Browse files
Files changed (1) hide show
  1. app-BACKUP.py +394 -188
app-BACKUP.py CHANGED
@@ -10,6 +10,7 @@ import tempfile
10
  import random
11
  from typing import Dict, List, Tuple, Optional
12
  import shutil
 
13
 
14
  import gradio as gr
15
  from loguru import logger
@@ -48,6 +49,28 @@ SERPHOUSE_API_KEY = os.getenv("SERPHOUSE_API_KEY", "")
48
  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 - FLUX API
53
  ##############################################################################
@@ -863,89 +886,169 @@ def pdf_to_markdown(pdf_path: str) -> str:
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,
@@ -964,7 +1067,6 @@ def generate_flux_image_via_api(prompt: str) -> Optional[str]:
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")
@@ -977,22 +1079,28 @@ def generate_flux_image_via_api(prompt: str) -> Optional[str]:
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
990
  ##############################################################################
991
  def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> list:
992
- """Parse LLM response to extract slide content - COMPLETELY FIXED VERSION"""
993
  slides = []
994
 
995
- # Debug: ์ „์ฒด ์‘๋‹ต ํ™•์ธ
996
  logger.info(f"Parsing LLM response, total length: {len(response)}")
997
  logger.debug(f"First 500 chars: {response[:500]}")
998
 
@@ -1005,24 +1113,45 @@ def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> l
1005
  except:
1006
  pass
1007
 
1008
- # Split by slide markers and process each section
1009
- # ์Šฌ๋ผ์ด๋“œ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋” ๊ฐ•๋ ฅํ•œ ์ •๊ทœ์‹
1010
- slide_pattern = r'(?:^|\n)(?:์Šฌ๋ผ์ด๋“œ|Slide)\s*\d+|(?:^|\n)\d+[\.)](?:\s|$)'
1011
-
1012
- # ์Šฌ๋ผ์ด๋“œ ์„น์…˜์œผ๋กœ ๋ถ„ํ• 
1013
- sections = re.split(slide_pattern, response, flags=re.MULTILINE | re.IGNORECASE)
1014
-
1015
- # ์ฒซ ๋ฒˆ์งธ ๋นˆ ์„น์…˜ ์ œ๊ฑฐ
1016
- if sections and not sections[0].strip():
1017
- sections = sections[1:]
1018
-
1019
- logger.info(f"Found {len(sections)} potential slide sections")
1020
-
1021
- for idx, section in enumerate(sections):
1022
- if not section.strip():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1023
  continue
1024
 
1025
- logger.debug(f"Processing section {idx}: {section[:100]}...")
1026
 
1027
  slide = {
1028
  'title': '',
@@ -1033,9 +1162,9 @@ def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> l
1033
  }
1034
 
1035
  # ์„น์…˜ ๋‚ด์—์„œ ์ œ๋ชฉ, ๋‚ด์šฉ, ๋…ธํŠธ ์ถ”์ถœ
1036
- lines = section.strip().split('\n')
1037
  current_part = None
1038
- title_lines = []
1039
  content_lines = []
1040
  notes_lines = []
1041
 
@@ -1045,11 +1174,11 @@ def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> l
1045
  continue
1046
 
1047
  # ์ œ๋ชฉ ์„น์…˜ ๊ฐ์ง€
1048
- if line.startswith('์ œ๋ชฉ:') or line.startswith('Title:'):
1049
  current_part = 'title'
1050
  title_text = line.split(':', 1)[1].strip() if ':' in line else ''
1051
- if title_text:
1052
- title_lines.append(title_text)
1053
  # ๋‚ด์šฉ ์„น์…˜ ๊ฐ์ง€
1054
  elif line.startswith('๋‚ด์šฉ:') or line.startswith('Content:'):
1055
  current_part = 'content'
@@ -1064,57 +1193,91 @@ def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> l
1064
  notes_lines.append(notes_text)
1065
  # ํ˜„์žฌ ์„น์…˜์— ๋”ฐ๋ผ ๋‚ด์šฉ ์ถ”๊ฐ€
1066
  else:
1067
- if current_part == 'title' and not title_lines:
1068
- title_lines.append(line)
1069
  elif current_part == 'content':
1070
  content_lines.append(line)
1071
  elif current_part == 'notes':
1072
  notes_lines.append(line)
1073
- elif not current_part and not title_lines:
1074
  # ์ฒซ ๋ฒˆ์งธ ์ค„์„ ์ œ๋ชฉ์œผ๋กœ
1075
- title_lines.append(line)
1076
- current_part = 'content' # ์ดํ›„ ์ค„๋“ค์€ content๋กœ
1077
- elif not current_part:
 
 
1078
  content_lines.append(line)
1079
 
1080
  # ์Šฌ๋ผ์ด๋“œ ๋ฐ์ดํ„ฐ ์„ค์ •
1081
- slide['title'] = ' '.join(title_lines).strip()
1082
  slide['content'] = '\n'.join(content_lines).strip()
1083
  slide['notes'] = ' '.join(notes_lines).strip()
1084
 
1085
- # ์ œ๋ชฉ ์ •๋ฆฌ
1086
- slide['title'] = re.sub(r'^(์Šฌ๋ผ์ด๋“œ|Slide)\s*\d+\s*[:๏ผš\-]?\s*', '', slide['title'], flags=re.IGNORECASE)
1087
- slide['title'] = re.sub(r'^(์ œ๋ชฉ|Title)\s*[:๏ผš]\s*', '', slide['title'], flags=re.IGNORECASE)
1088
-
1089
  # ๋‚ด์šฉ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ถ”๊ฐ€
1090
  if slide['title'] or slide['content']:
1091
  logger.info(f"Slide {len(slides)+1}: Title='{slide['title'][:30]}...', Content length={len(slide['content'])}")
1092
  slides.append(slide)
1093
 
1094
- # ๋งŒ์•ฝ ์œ„ ๋ฐฉ๋ฒ•์œผ๋กœ ํŒŒ์‹ฑ์ด ์•ˆ ๋˜์—ˆ๋‹ค๋ฉด, ๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ• ์‹œ๋„
1095
- if not slides:
1096
- logger.warning("Primary parsing failed, trying fallback method...")
 
 
 
 
1097
 
1098
- # ๋”๋ธ” ๋‰ด๋ผ์ธ์œผ๋กœ ๊ตฌ๋ถ„
1099
- sections = response.split('\n\n')
1100
  for section in sections:
1101
- lines = section.strip().split('\n')
1102
- if len(lines) >= 2: # ์ตœ์†Œ ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์ด ์žˆ์–ด์•ผ ํ•จ
1103
- slide = {
1104
- 'title': lines[0].strip(),
1105
- 'content': '\n'.join(lines[1:]).strip(),
1106
- 'notes': '',
1107
- 'layout': 'title_content',
1108
- 'chart_data': None
1109
- }
1110
 
1111
- # ์ œ๋ชฉ ์ •๋ฆฌ
1112
- slide['title'] = re.sub(r'^(์Šฌ๋ผ์ด๋“œ|Slide)\s*\d+\s*[:๏ผš\-]?\s*', '', slide['title'], flags=re.IGNORECASE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1113
 
1114
- if slide['title'] and slide['content']:
1115
- slides.append(slide)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1116
 
1117
  logger.info(f"Total slides parsed: {len(slides)}")
 
 
 
 
 
 
1118
  return slides
1119
 
1120
  def force_font_size(text_frame, font_size_pt: int, theme: Dict):
@@ -1380,14 +1543,14 @@ def create_advanced_ppt_from_content(
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
@@ -1403,7 +1566,7 @@ def create_advanced_ppt_from_content(
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
@@ -1415,18 +1578,27 @@ def create_advanced_ppt_from_content(
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
@@ -1436,9 +1608,9 @@ def create_advanced_ppt_from_content(
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
@@ -1447,9 +1619,9 @@ def create_advanced_ppt_from_content(
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
@@ -1459,10 +1631,14 @@ def create_advanced_ppt_from_content(
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
 
@@ -1525,18 +1701,18 @@ def create_advanced_ppt_from_content(
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']:
@@ -1584,17 +1760,19 @@ def create_advanced_ppt_from_content(
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
  )
@@ -1611,31 +1789,45 @@ def create_advanced_ppt_from_content(
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:
@@ -2052,14 +2244,25 @@ def generate_ppt(
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:
@@ -2070,15 +2273,6 @@ def generate_ppt(
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 = ""
2084
  chart_data = None
@@ -2540,16 +2734,17 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
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
 
@@ -2588,10 +2783,21 @@ with gr.Blocks(css=css, title="AI PPT Generator Pro") as demo:
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()
 
10
  import random
11
  from typing import Dict, List, Tuple, Optional
12
  import shutil
13
+ import concurrent.futures
14
 
15
  import gradio as gr
16
  from loguru import logger
 
49
  if not SERPHOUSE_API_KEY:
50
  logger.warning("SERPHOUSE_API_KEY not set. Web search functionality will be limited.")
51
 
52
+ ##############################################################################
53
+ # AI Image Generation API Configuration - 3D Style
54
+ ##############################################################################
55
+ AI_IMAGE_API_URL = "http://211.233.58.201:7971/"
56
+ AI_IMAGE_ENABLED = False
57
+ ai_image_client = None
58
+
59
+ def initialize_ai_image_api():
60
+ """AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ API ์ดˆ๊ธฐํ™” (3D ์Šคํƒ€์ผ)"""
61
+ global AI_IMAGE_ENABLED, ai_image_client
62
+
63
+ try:
64
+ logger.info("Connecting to AI image generation API (3D style)...")
65
+ ai_image_client = Client(AI_IMAGE_API_URL)
66
+ AI_IMAGE_ENABLED = True
67
+ logger.info("AI image generation API (3D style) connected successfully")
68
+ return True
69
+ except Exception as e:
70
+ logger.error(f"Failed to connect to AI image API: {e}")
71
+ AI_IMAGE_ENABLED = False
72
+ return False
73
+
74
  ##############################################################################
75
  # AI Image Generation API Configuration - FLUX API
76
  ##############################################################################
 
886
  return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
887
 
888
  ##############################################################################
889
+ # AI Image Generation Functions using Multiple APIs
890
  ##############################################################################
891
+ def generate_diverse_prompt(title: str, content: str, slide_index: int) -> Tuple[str, str]:
892
+ """์Šฌ๋ผ์ด๋“œ๋ณ„๋กœ ๋‹ค์–‘ํ•œ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ - 3D์™€ ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ๋ฒ„์ „"""
893
 
894
  # ์ฃผ์š” ํ‚ค์›Œ๋“œ ์ถ”์ถœ
895
+ keywords = extract_keywords(f"{title} {content}", top_k=5).split()
896
 
897
+ # ์Šฌ๋ผ์ด๋“œ ์ธ๋ฑ์Šค์— ๋”ฐ๋ผ ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ ์ ์šฉ
898
+ styles_3d = [
899
+ "isometric 3D illustration",
900
+ "low poly 3D art",
901
+ "3D cartoon style",
902
+ "3D glass morphism",
903
+ "3D neon glow effect",
904
+ "3D paper cut art",
905
+ "3D clay render",
906
+ "3D geometric abstract"
907
+ ]
908
 
909
+ styles_photo = [
910
+ "professional photography",
911
+ "cinematic shot",
912
+ "minimalist photography",
913
+ "aerial view photograph",
914
+ "macro photography",
915
+ "dramatic lighting photo",
916
+ "architectural photography",
917
+ "lifestyle photography"
918
+ ]
 
 
919
 
920
+ # ๋‚ด์šฉ ๊ธฐ๋ฐ˜ ์‹œ๊ฐ ๋ฉ”ํƒ€ํฌ ์„ ํƒ
921
+ visual_metaphors = []
922
+ content_lower = (title + " " + content).lower()
923
 
924
+ if any(word in content_lower for word in ['์„ฑ์žฅ', 'growth', '์ฆ๊ฐ€', 'increase']):
925
+ visual_metaphors = ["ascending stairs", "growing tree", "rocket launch", "mountain peak", "rising graph"]
926
+ elif any(word in content_lower for word in ['ํ˜์‹ ', 'innovation', '์ฐฝ์˜', 'creative']):
927
+ visual_metaphors = ["lightbulb moment", "puzzle pieces connecting", "spark of genius", "breaking boundaries", "colorful explosion"]
928
+ elif any(word in content_lower for word in ['ํ˜‘์—…', 'collaboration', 'ํŒ€', 'team']):
929
+ visual_metaphors = ["hands joining together", "connected network", "team huddle", "bridge building", "interlocking gears"]
930
+ elif any(word in content_lower for word in ['๋ฐ์ดํ„ฐ', 'data', '๋ถ„์„', 'analysis']):
931
+ visual_metaphors = ["data visualization", "digital dashboard", "flowing data streams", "analytical charts", "information network"]
932
+ elif any(word in content_lower for word in ['๋ฏธ๋ž˜', 'future', '์ „๋ง', 'vision']):
933
+ visual_metaphors = ["horizon view", "crystal ball", "futuristic cityscape", "pathway to tomorrow", "digital transformation"]
934
+ elif any(word in content_lower for word in ['ํ”„๋กœ์„ธ์Šค', 'process', '๋‹จ๊ณ„', 'step']):
935
+ visual_metaphors = ["flowing river", "assembly line", "domino effect", "clockwork mechanism", "journey path"]
936
+ elif any(word in content_lower for word in ['๋ชฉํ‘œ', 'goal', '์„ฑ๊ณต', 'success']):
937
+ visual_metaphors = ["target with arrow", "trophy on pedestal", "finish line", "mountain summit", "golden key"]
938
+ else:
939
+ visual_metaphors = ["abstract shapes", "dynamic composition", "symbolic representation", "conceptual art", "modern design"]
940
+
941
+ # ์Šคํƒ€์ผ๊ณผ ๋ฉ”ํƒ€ํฌ ์„ ํƒ
942
+ style_3d = styles_3d[slide_index % len(styles_3d)]
943
+ style_photo = styles_photo[slide_index % len(styles_photo)]
944
+ metaphor = random.choice(visual_metaphors)
945
+
946
+ # ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ ๋‹ค์–‘ํ™”
947
+ color_palettes = [
948
+ "vibrant blue and orange",
949
+ "elegant purple and gold",
950
+ "fresh green and white",
951
+ "bold red and black",
952
+ "soft pastel tones",
953
+ "monochromatic blue",
954
+ "warm sunset colors",
955
+ "cool ocean palette"
956
+ ]
957
+ colors = color_palettes[slide_index % len(color_palettes)]
958
 
959
+ # 3D ์Šคํƒ€์ผ ํ”„๋กฌํ”„ํŠธ (ํ•œ๊ธ€)
960
+ prompt_3d = f"wbgmsst, {style_3d}, {metaphor} representing {' '.join(keywords[:3])}, {colors}, professional presentation slide, high quality, white background"
961
+
962
+ # ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)
963
+ prompt_photo = f"{style_photo} of {metaphor} symbolizing {' '.join(keywords[:3])}, {colors} color scheme, professional business context, clean composition, high resolution"
964
+
965
+ return prompt_3d, prompt_photo
966
+
967
+ def generate_cover_image_prompts(topic: str, slides_data: list) -> Tuple[str, str]:
968
+ """ํ‘œ์ง€์šฉ 3D์™€ ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
969
 
970
+ keywords = extract_keywords(topic, top_k=3).split()
 
 
971
 
972
+ # ์ฃผ์ œ๋ณ„ ํŠนํ™”๋œ ์‹œ๊ฐ ์š”์†Œ
973
+ topic_lower = topic.lower()
974
+ if any(word in topic_lower for word in ['๊ธฐ์ˆ ', 'tech', 'ai', '์ธ๊ณต์ง€๋Šฅ']):
975
+ visual_3d = "futuristic 3D holographic interface"
976
+ visual_photo = "modern technology workspace with holographic displays"
977
+ elif any(word in topic_lower for word in ['๋น„์ฆˆ๋‹ˆ์Šค', 'business', '๊ฒฝ์˜']):
978
+ visual_3d = "3D corporate building with glass architecture"
979
+ visual_photo = "professional business meeting in modern office"
980
+ elif any(word in topic_lower for word in ['๊ต์œก', 'education', 'ํ•™์Šต']):
981
+ visual_3d = "3D books transforming into knowledge symbols"
982
+ visual_photo = "inspiring educational environment with digital elements"
983
+ elif any(word in topic_lower for word in ['ํ™˜๊ฒฝ', 'environment', '์ž์—ฐ']):
984
+ visual_3d = "3D earth with renewable energy icons"
985
+ visual_photo = "pristine nature landscape with sustainable elements"
986
  else:
987
+ visual_3d = "abstract 3D geometric composition"
988
+ visual_photo = "professional abstract photography"
989
 
990
+ prompt_3d = f"wbgmsst, {visual_3d}, {' '.join(keywords)} theme, premium 3D render, elegant composition, gradient background"
991
+ prompt_photo = f"{visual_photo} featuring {' '.join(keywords)}, cinematic lighting, professional presentation cover, high-end photography"
992
 
993
+ return prompt_3d, prompt_photo
994
 
995
+ def generate_conclusion_image_prompts(title: str, content: str) -> Tuple[str, str]:
996
+ """๊ฒฐ๋ก  ์Šฌ๋ผ์ด๋“œ์šฉ ํŠน๋ณ„ํ•œ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
997
 
 
998
  keywords = extract_keywords(f"{title} {content}", top_k=4).split()
999
 
1000
+ # ๊ฒฐ๋ก  ์Šคํƒ€์ผ ๋น„์ฃผ์–ผ
1001
+ prompt_3d = f"wbgmsst, 3D trophy or achievement symbol, {' '.join(keywords[:2])} success visualization, golden lighting, celebration mood, premium quality"
1002
+ prompt_photo = f"inspirational sunrise or horizon view symbolizing {' '.join(keywords[:2])}, bright future ahead, professional photography, uplifting atmosphere"
 
 
 
 
 
 
 
 
 
1003
 
1004
+ return prompt_3d, prompt_photo
1005
+
1006
+ def generate_ai_image_via_3d_api(prompt: str) -> Optional[str]:
1007
+ """3D ์Šคํƒ€์ผ API๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
1008
+ if not AI_IMAGE_ENABLED or not ai_image_client:
1009
+ return None
1010
 
1011
+ try:
1012
+ logger.info(f"Generating 3D style image with prompt: {prompt[:100]}...")
1013
+
1014
+ result = ai_image_client.predict(
1015
+ height=1024.0,
1016
+ width=1024.0,
1017
+ steps=8.0,
1018
+ scales=3.5,
1019
+ prompt=prompt,
1020
+ seed=float(random.randint(0, 1000000)),
1021
+ api_name="/process_and_save_image"
1022
+ )
1023
+
1024
+ # ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
1025
+ image_path = None
1026
+ if isinstance(result, dict):
1027
+ image_path = result.get("path")
1028
+ elif isinstance(result, str):
1029
+ image_path = result
1030
+
1031
+ if image_path and os.path.exists(image_path):
1032
+ # PNG๋กœ ๋ณ€ํ™˜
1033
+ with Image.open(image_path) as img:
1034
+ png_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
1035
+ img.save(png_tmp.name, format="PNG")
1036
+ logger.info(f"3D image generated and saved to {png_tmp.name}")
1037
+ return png_tmp.name
1038
+
1039
+ return None
1040
+
1041
+ except Exception as e:
1042
+ logger.error(f"Failed to generate 3D image: {e}")
1043
+ return None
1044
 
1045
  def generate_flux_image_via_api(prompt: str) -> Optional[str]:
1046
+ """FLUX API๋ฅผ ํ†ตํ•ด ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
1047
  if not FLUX_API_ENABLED or not flux_api_client:
 
1048
  return None
1049
 
1050
  try:
1051
+ logger.info(f"Generating FLUX photorealistic image with prompt: {prompt[:100]}...")
1052
 
1053
  result = flux_api_client.predict(
1054
  prompt=prompt,
 
1067
  if isinstance(result, tuple) and len(result) > 0:
1068
  image_path = result[0]
1069
  if image_path and os.path.exists(image_path):
 
1070
  with Image.open(image_path) as img:
1071
  png_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
1072
  img.save(png_tmp.name, format="PNG")
 
1079
  logger.error(f"Failed to generate FLUX image: {e}")
1080
  return None
1081
 
1082
+ def generate_images_parallel(prompt_3d: str, prompt_photo: str) -> Tuple[Optional[str], Optional[str]]:
1083
+ """๋‘ API๋ฅผ ๋ณ‘๋ ฌ๋กœ ํ˜ธ์ถœํ•˜์—ฌ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
1084
+ import concurrent.futures
1085
+
1086
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
1087
+ # ๋‘ API๋ฅผ ๋™์‹œ์— ํ˜ธ์ถœ
1088
+ future_3d = executor.submit(generate_ai_image_via_3d_api, prompt_3d)
1089
+ future_photo = executor.submit(generate_flux_image_via_api, prompt_photo)
1090
+
1091
+ # ๊ฒฐ๊ณผ ๋Œ€๊ธฐ
1092
+ image_3d = future_3d.result()
1093
+ image_photo = future_photo.result()
1094
+
1095
+ return image_3d, image_photo
1096
 
1097
  ##############################################################################
1098
  # PPT Generation Functions - FIXED VERSION
1099
  ##############################################################################
1100
  def parse_llm_ppt_response(response: str, layout_style: str = "consistent") -> list:
1101
+ """Parse LLM response to extract slide content - FIXED VERSION"""
1102
  slides = []
1103
 
 
1104
  logger.info(f"Parsing LLM response, total length: {len(response)}")
1105
  logger.debug(f"First 500 chars: {response[:500]}")
1106
 
 
1113
  except:
1114
  pass
1115
 
1116
+ # ๋” ์ •ํ™•ํ•œ ์Šฌ๋ผ์ด๋“œ ๊ตฌ๋ถ„ ํŒจํ„ด
1117
+ # "์Šฌ๋ผ์ด๋“œ 1", "์Šฌ๋ผ์ด๋“œ 2" ๋˜๋Š” "Slide 1", "Slide 2" ํ˜•์‹์„ ์ฐพ์Œ
1118
+ slide_markers = []
1119
+
1120
+ # ์Šฌ๋ผ์ด๋“œ ๋งˆ์ปค์˜ ์œ„์น˜๋ฅผ ๋จผ์ € ์ฐพ์Œ
1121
+ for match in re.finditer(r'^(?:์Šฌ๋ผ์ด๋“œ|Slide)\s*(\d+)\s*$', response, re.MULTILINE):
1122
+ slide_markers.append({
1123
+ 'index': int(match.group(1)),
1124
+ 'start': match.start(),
1125
+ 'end': match.end()
1126
+ })
1127
+
1128
+ logger.info(f"Found {len(slide_markers)} slide markers")
1129
+
1130
+ # ์Šฌ๋ผ์ด๋“œ ๋งˆ์ปค๊ฐ€ ์—†์œผ๋ฉด ๋‹ค๋ฅธ ํŒจํ„ด ์‹œ๋„
1131
+ if not slide_markers:
1132
+ # ์ˆซ์ž๋งŒ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํŒจํ„ด๋„ ์ฐพ๊ธฐ (์˜ˆ: "1.", "2." ๋“ฑ)
1133
+ for match in re.finditer(r'^(\d+)[.)]\s*$', response, re.MULTILINE):
1134
+ slide_markers.append({
1135
+ 'index': int(match.group(1)),
1136
+ 'start': match.start(),
1137
+ 'end': match.end()
1138
+ })
1139
+
1140
+ # ๊ฐ ์Šฌ๋ผ์ด๋“œ ๋งˆ์ปค ์‚ฌ์ด์˜ ๋‚ด์šฉ์„ ์ถ”์ถœ
1141
+ for i, marker in enumerate(slide_markers):
1142
+ # ํ˜„์žฌ ์Šฌ๋ผ์ด๋“œ์˜ ์‹œ์ž‘๊ณผ ๋ ์œ„์น˜
1143
+ start = marker['end']
1144
+ if i < len(slide_markers) - 1:
1145
+ end = slide_markers[i + 1]['start']
1146
+ else:
1147
+ end = len(response)
1148
+
1149
+ section = response[start:end].strip()
1150
+
1151
+ if not section:
1152
  continue
1153
 
1154
+ logger.debug(f"Processing slide {marker['index']}: {section[:100]}...")
1155
 
1156
  slide = {
1157
  'title': '',
 
1162
  }
1163
 
1164
  # ์„น์…˜ ๋‚ด์—์„œ ์ œ๋ชฉ, ๋‚ด์šฉ, ๋…ธํŠธ ์ถ”์ถœ
1165
+ lines = section.split('\n')
1166
  current_part = None
1167
+ title_found = False
1168
  content_lines = []
1169
  notes_lines = []
1170
 
 
1174
  continue
1175
 
1176
  # ์ œ๋ชฉ ์„น์…˜ ๊ฐ์ง€
1177
+ if (line.startswith('์ œ๋ชฉ:') or line.startswith('Title:')) and not title_found:
1178
  current_part = 'title'
1179
  title_text = line.split(':', 1)[1].strip() if ':' in line else ''
1180
+ slide['title'] = title_text
1181
+ title_found = True
1182
  # ๋‚ด์šฉ ์„น์…˜ ๊ฐ์ง€
1183
  elif line.startswith('๋‚ด์šฉ:') or line.startswith('Content:'):
1184
  current_part = 'content'
 
1193
  notes_lines.append(notes_text)
1194
  # ํ˜„์žฌ ์„น์…˜์— ๋”ฐ๋ผ ๋‚ด์šฉ ์ถ”๊ฐ€
1195
  else:
1196
+ if current_part == 'title' and not slide['title']:
1197
+ slide['title'] = line
1198
  elif current_part == 'content':
1199
  content_lines.append(line)
1200
  elif current_part == 'notes':
1201
  notes_lines.append(line)
1202
+ elif not title_found and not slide['title']:
1203
  # ์ฒซ ๋ฒˆ์งธ ์ค„์„ ์ œ๋ชฉ์œผ๋กœ
1204
+ slide['title'] = line
1205
+ title_found = True
1206
+ current_part = 'content'
1207
+ elif current_part is None and title_found:
1208
+ current_part = 'content'
1209
  content_lines.append(line)
1210
 
1211
  # ์Šฌ๋ผ์ด๋“œ ๋ฐ์ดํ„ฐ ์„ค์ •
 
1212
  slide['content'] = '\n'.join(content_lines).strip()
1213
  slide['notes'] = ' '.join(notes_lines).strip()
1214
 
 
 
 
 
1215
  # ๋‚ด์šฉ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ถ”๊ฐ€
1216
  if slide['title'] or slide['content']:
1217
  logger.info(f"Slide {len(slides)+1}: Title='{slide['title'][:30]}...', Content length={len(slide['content'])}")
1218
  slides.append(slide)
1219
 
1220
+ # ๋งŒ์•ฝ ์œ„ ๋ฐฉ๋ฒ•์œผ๋กœ ํŒŒ์‹ฑ์ด ์•ˆ ๋˜์—ˆ๋‹ค๋ฉด, ๋” ์œ ์—ฐํ•œ ๋ฐฉ๋ฒ• ์‹œ๋„
1221
+ if not slides or len(slides) < 3:
1222
+ logger.warning(f"Primary parsing resulted in only {len(slides)} slides, trying alternative method...")
1223
+ slides = []
1224
+
1225
+ # "์ œ๋ชฉ:" ํŒจํ„ด์œผ๋กœ ์Šฌ๋ผ์ด๋“œ ๊ตฌ๋ถ„ ์‹œ๋„
1226
+ sections = re.split(r'\n(?=์ œ๋ชฉ:|Title:)', response)
1227
 
 
 
1228
  for section in sections:
1229
+ if not section.strip():
1230
+ continue
 
 
 
 
 
 
 
1231
 
1232
+ slide = {
1233
+ 'title': '',
1234
+ 'content': '',
1235
+ 'notes': '',
1236
+ 'layout': 'title_content',
1237
+ 'chart_data': None
1238
+ }
1239
+
1240
+ lines = section.strip().split('\n')
1241
+ current_part = None
1242
+ content_lines = []
1243
+ notes_lines = []
1244
+
1245
+ for line in lines:
1246
+ line = line.strip()
1247
+ if not line:
1248
+ continue
1249
 
1250
+ if line.startswith('์ œ๋ชฉ:') or line.startswith('Title:'):
1251
+ slide['title'] = line.split(':', 1)[1].strip() if ':' in line else ''
1252
+ current_part = 'content'
1253
+ elif line.startswith('๋‚ด์šฉ:') or line.startswith('Content:'):
1254
+ current_part = 'content'
1255
+ elif line.startswith('๋…ธํŠธ:') or line.startswith('Notes:'):
1256
+ current_part = 'notes'
1257
+ notes_text = line.split(':', 1)[1].strip() if ':' in line else ''
1258
+ if notes_text:
1259
+ notes_lines.append(notes_text)
1260
+ elif current_part == 'content':
1261
+ content_lines.append(line)
1262
+ elif current_part == 'notes':
1263
+ notes_lines.append(line)
1264
+
1265
+ slide['content'] = '\n'.join(content_lines).strip()
1266
+ slide['notes'] = ' '.join(notes_lines).strip()
1267
+
1268
+ # ์Šฌ๋ผ์ด๋“œ ๋ฒˆํ˜ธ ์ œ๊ฑฐ
1269
+ slide['title'] = re.sub(r'^(์Šฌ๋ผ์ด๋“œ|Slide)\s*\d+\s*[:๏ผš\-]?\s*', '', slide['title'], flags=re.IGNORECASE)
1270
+
1271
+ if slide['title'] or slide['content']:
1272
+ slides.append(slide)
1273
 
1274
  logger.info(f"Total slides parsed: {len(slides)}")
1275
+
1276
+ # ํŒŒ์‹ฑ ๊ฒฐ๊ณผ ๊ฒ€์ฆ
1277
+ if len(slides) < 3:
1278
+ logger.error("Parsing resulted in too few slides. Raw response preview:")
1279
+ logger.error(response[:1000])
1280
+
1281
  return slides
1282
 
1283
  def force_font_size(text_frame, font_size_pt: int, theme: Dict):
 
1543
  # ๋ฐฐ๊ฒฝ ๊ทธ๋ผ๋””์–ธํŠธ
1544
  add_gradient_background(slide, theme['colors']['primary'], theme['colors']['secondary'])
1545
 
1546
+ # ์ œ๋ชฉ๊ณผ ๋ถ€์ œ๋ชฉ์„ ์ค‘์•™ ์ƒ๋‹จ์— ๋ฐฐ์น˜
1547
  title_shape = slide.shapes.title
1548
  subtitle_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else None
1549
 
1550
  if title_shape:
1551
  title_shape.left = Inches(0.5)
1552
  title_shape.width = prs.slide_width - Inches(1)
1553
+ title_shape.top = Inches(1.0) # ์ƒ๋‹จ์— ๋ฐฐ์น˜
1554
  title_shape.height = Inches(1.2)
1555
 
1556
  tf = title_shape.text_frame
 
1566
  if subtitle_shape:
1567
  subtitle_shape.left = Inches(0.5)
1568
  subtitle_shape.width = prs.slide_width - Inches(1)
1569
+ subtitle_shape.top = Inches(2.2) # ์ œ๋ชฉ ์•„๋ž˜์— ๋ฐฐ์น˜
1570
  subtitle_shape.height = Inches(0.9)
1571
 
1572
  tf2 = subtitle_shape.text_frame
 
1578
  p2.font.color.rgb = RGBColor(255, 255, 255)
1579
  p2.alignment = PP_ALIGN.CENTER
1580
 
1581
+ # AI ์ด๋ฏธ์ง€๋ฅผ ์šฐ์ธก ํ•˜๋‹จ์— ๋ฐฐ์น˜ (๋‘ API ๋ณ‘๋ ฌ ์‚ฌ์šฉ)
1582
+ if include_ai_image and (AI_IMAGE_ENABLED or FLUX_API_ENABLED):
1583
+ logger.info("Generating AI cover images via parallel APIs...")
1584
+
1585
+ # ๋‘ ๊ฐ€์ง€ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
1586
+ prompt_3d, prompt_photo = generate_cover_image_prompts(topic, slides_data)
1587
+
1588
+ # ๋ณ‘๋ ฌ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
1589
+ image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
1590
+
1591
+ # ์„ฑ๊ณตํ•œ ์ด๋ฏธ์ง€ ์ค‘ ํ•˜๋‚˜ ์„ ํƒ (3D ์šฐ์„ )
1592
+ ai_image_path = image_3d or image_photo
1593
+
1594
  if ai_image_path and os.path.exists(ai_image_path):
1595
  try:
1596
  img = Image.open(ai_image_path)
1597
  img_width, img_height = img.size
1598
 
1599
+ # ์ด๋ฏธ์ง€๋ฅผ ์šฐ์ธก ํ•˜๋‹จ์— ๋ฐฐ์น˜
1600
+ max_width = Inches(3.5)
1601
+ max_height = Inches(2.5)
1602
 
1603
  ratio = img_height / img_width
1604
  img_w = max_width
 
1608
  img_h = max_height
1609
  img_w = max_height / ratio
1610
 
1611
+ # ์šฐ์ธก ํ•˜๋‹จ ๋ฐฐ์น˜
1612
  left = prs.slide_width - img_w - Inches(0.5)
1613
+ top = prs.slide_height - img_h - Inches(0.8)
1614
 
1615
  pic = slide.shapes.add_picture(ai_image_path, left, top, width=img_w, height=img_h)
1616
  pic.shadow.inherit = False
 
1619
  pic.shadow.distance = Pt(8)
1620
  pic.shadow.angle = 45
1621
 
1622
+ # ์ด๋ฏธ์ง€ ์œ„์— ์ž‘์€ ์บก์…˜ ์ถ”๊ฐ€
1623
  caption_box = slide.shapes.add_textbox(
1624
+ left, top - Inches(0.3),
1625
  img_w, Inches(0.3)
1626
  )
1627
  caption_tf = caption_box.text_frame
 
1631
  caption_p.font.color.rgb = RGBColor(255, 255, 255)
1632
  caption_p.alignment = PP_ALIGN.CENTER
1633
 
1634
+ # ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
1635
+ for temp_path in [image_3d, image_photo]:
1636
+ if temp_path and os.path.exists(temp_path):
1637
+ try:
1638
+ os.unlink(temp_path)
1639
+ except:
1640
+ pass
1641
+
1642
  except Exception as e:
1643
  logger.error(f"Failed to add cover image: {e}")
1644
 
 
1701
  should_add_visual = False
1702
  visual_type = None
1703
 
1704
+ # ๊ฒฐ๋ก  ์Šฌ๋ผ์ด๋“œ๋Š” ํ•ญ์ƒ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
1705
+ if is_conclusion_slide and include_flux_images:
1706
  should_add_visual = True
1707
+ visual_type = ('conclusion_images', None)
1708
  elif include_diagrams:
1709
  diagram_type = detect_diagram_type(slide_title, slide_content)
1710
  if diagram_type:
1711
  should_add_visual = True
1712
  visual_type = ('diagram', diagram_type)
1713
+ elif not should_add_visual and include_flux_images and i % 2 == 0: # ๋งค 2๋ฒˆ์งธ ์Šฌ๋ผ์ด๋“œ์— ์ด๋ฏธ์ง€
1714
  should_add_visual = True
1715
+ visual_type = ('diverse_images', None)
1716
 
1717
  # ์‹œ๊ฐ์  ์š”์†Œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ขŒ-์šฐ ๋ ˆ์ด์•„์›ƒ ์ ์šฉ
1718
  if should_add_visual and layout_type not in ['section_header']:
 
1760
  except Exception as e:
1761
  logger.error(f"Failed to add diagram: {e}")
1762
 
1763
+ elif visual_type[0] == 'conclusion_images':
1764
+ # ๊ฒฐ๋ก  ์Šฌ๋ผ์ด๋“œ์šฉ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (๋‘ API ๋ณ‘๋ ฌ)
1765
+ logger.info(f"Generating conclusion images for slide {i+1}")
1766
+ prompt_3d, prompt_photo = generate_conclusion_image_prompts(slide_title, slide_content)
1767
+ image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
1768
+
1769
+ # ์„ฑ๊ณตํ•œ ์ด๋ฏธ์ง€ ์ค‘ ํ•˜๋‚˜ ์„ ํƒ
1770
+ selected_image = image_photo or image_3d # ๊ฒฐ๋ก ์€ ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ์šฐ์„ 
1771
 
1772
+ if selected_image and os.path.exists(selected_image):
1773
  try:
 
1774
  pic = slide.shapes.add_picture(
1775
+ selected_image,
1776
  Inches(5.2), Inches(1.5),
1777
  width=Inches(4.3), height=Inches(3.0)
1778
  )
 
1789
  caption_p.font.color.rgb = theme['colors']['secondary']
1790
  caption_p.alignment = PP_ALIGN.CENTER
1791
 
1792
+ # ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
1793
+ for temp_path in [image_3d, image_photo]:
1794
+ if temp_path and os.path.exists(temp_path):
1795
+ try:
1796
+ os.unlink(temp_path)
1797
+ except:
1798
+ pass
1799
  except Exception as e:
1800
+ logger.error(f"Failed to add conclusion image: {e}")
1801
 
1802
+ elif visual_type[0] == 'diverse_images':
1803
+ # ๋‹ค์–‘ํ•œ ์Šฌ๋ผ์ด๋“œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (๋‘ API ๋ณ‘๋ ฌ)
1804
+ logger.info(f"Generating diverse images for slide {i+1}")
1805
+ prompt_3d, prompt_photo = generate_diverse_prompt(slide_title, slide_content, i)
1806
+ image_3d, image_photo = generate_images_parallel(prompt_3d, prompt_photo)
1807
+
1808
+ # ์Šฌ๋ผ์ด๋“œ ์ธ๋ฑ์Šค์— ๋”ฐ๋ผ ๋ฒˆ๊ฐˆ์•„ ์„ ํƒ
1809
+ selected_image = image_3d if i % 2 == 0 else image_photo
1810
+ if not selected_image: # ์‹คํŒจ์‹œ ๋‹ค๋ฅธ ๊ฒƒ ์„ ํƒ
1811
+ selected_image = image_photo if i % 2 == 0 else image_3d
1812
 
1813
+ if selected_image and os.path.exists(selected_image):
1814
  try:
 
1815
  pic = slide.shapes.add_picture(
1816
+ selected_image,
1817
  Inches(5.2), Inches(1.5),
1818
  width=Inches(4.3), height=Inches(3.0)
1819
  )
1820
  visual_added = True
1821
 
1822
+ # ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
1823
+ for temp_path in [image_3d, image_photo]:
1824
+ if temp_path and os.path.exists(temp_path):
1825
+ try:
1826
+ os.unlink(temp_path)
1827
+ except:
1828
+ pass
1829
  except Exception as e:
1830
+ logger.error(f"Failed to add slide image: {e}")
1831
 
1832
  # ์‹œ๊ฐ์  ์š”์†Œ๊ฐ€ ์ถ”๊ฐ€๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ถ”๊ฐ€
1833
  if not visual_added:
 
2244
  return None, "โŒ ์Šฌ๋ผ์ด๋“œ ์ˆ˜๋Š” 3์žฅ ์ด์ƒ 20์žฅ ์ดํ•˜๋กœ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.", ""
2245
 
2246
  try:
2247
+ # 3D ์Šคํƒ€์ผ API ์ดˆ๊ธฐํ™” (ํ‘œ์ง€ ์ด๋ฏธ์ง€์šฉ)
2248
+ if include_ai_image and not AI_IMAGE_ENABLED:
2249
+ yield None, "๐Ÿ”„ 3D ์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ API์— ์—ฐ๊ฒฐํ•˜๋Š” ์ค‘...", ""
2250
+ if initialize_ai_image_api():
2251
+ yield None, "โœ… 3D ์Šคํƒ€์ผ API ์—ฐ๊ฒฐ ์„ฑ๊ณต!", ""
2252
+ else:
2253
+ include_ai_image = False
2254
+ yield None, "โš ๏ธ 3D ์Šคํƒ€์ผ API ์—ฐ๊ฒฐ ์‹คํŒจ. AI ์ด๋ฏธ์ง€ ์—†์ด ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.", ""
2255
+
2256
+ # FLUX API ์ดˆ๊ธฐํ™” (ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ์ด๋ฏธ์ง€์šฉ)
2257
+ if (include_ai_image or include_flux_images) and not FLUX_API_ENABLED:
2258
+ yield None, "๐Ÿ”„ FLUX ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ API์— ์—ฐ๊ฒฐํ•˜๋Š” ์ค‘...", ""
2259
  if initialize_flux_api():
2260
  yield None, "โœ… FLUX API ์—ฐ๊ฒฐ ์„ฑ๊ณต!", ""
2261
  else:
2262
+ if include_ai_image and not AI_IMAGE_ENABLED:
2263
+ include_ai_image = False
2264
+ include_flux_images = False
2265
+ yield None, "โš ๏ธ FLUX API ์—ฐ๊ฒฐ ์‹คํŒจ. ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ์ด๋ฏธ์ง€ ์—†์ด ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.", ""
2266
 
2267
  # ๋‹ค์ด์–ด๊ทธ๋žจ API ์ดˆ๊ธฐํ™”
2268
  if include_diagrams and not DIAGRAM_API_ENABLED:
 
2273
  include_diagrams = False
2274
  yield None, "โš ๏ธ ๋‹ค์ด์–ด๊ทธ๋žจ API ์—ฐ๊ฒฐ ์‹คํŒจ. ๋‹ค์ด์–ด๊ทธ๋žจ ์—†์ด ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.", ""
2275
 
 
 
 
 
 
 
 
 
 
2276
  # Process reference files if provided
2277
  additional_context = ""
2278
  chart_data = None
 
2734
  6. **์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ**: AI๊ฐ€ ์ž๋™์œผ๋กœ PPT ์ƒ์„ฑ
2735
 
2736
  ### ๐ŸŽจ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ
2737
+ - **๋ณ‘๋ ฌ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ**: 3D ์Šคํƒ€์ผ๊ณผ ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ API๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑ ์‹œ๊ฐ„ ๋‹จ์ถ•
2738
+ - **๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€ ์Šคํƒ€์ผ**: ๊ฐ ์Šฌ๋ผ์ด๋“œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์Šคํƒ€์ผ๊ณผ ๋ฉ”ํƒ€ํฌ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
2739
+ - **AI ํ‘œ์ง€ ์ด๋ฏธ์ง€**: ์šฐ์ธก ํ•˜๋‹จ ๋ฐฐ์น˜๋กœ ํ…์ŠคํŠธ์™€ ๊ฒน์น˜์ง€ ์•Š์Œ
2740
+ - **๊ฒฐ๋ก  ์Šฌ๋ผ์ด๋“œ ๊ฐ•์กฐ**: ๊ฒฐ๋ก /์š”์•ฝ ์Šฌ๋ผ์ด๋“œ์— ํŠน๋ณ„ํ•œ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
2741
  - **์ขŒ-์šฐ ๋ ˆ์ด์•„์›ƒ**: ํ…์ŠคํŠธ๋Š” ์ขŒ์ธก, ์‹œ๊ฐ์  ์š”์†Œ๋Š” ์šฐ์ธก ๋ฐฐ์น˜
2742
 
2743
  ### ๐Ÿ’ก ๊ณ ๊ธ‰ ํŒ
2744
+ - ํ‘œ์ง€ ์ด๋ฏธ์ง€๋Š” ์šฐ์ธก ํ•˜๋‹จ์— ๋ฐฐ์น˜๋˜์–ด ์ œ๋ชฉ/๋ถ€์ œ์™€ ๊ฒน์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
2745
+ - 3D ์Šคํƒ€์ผ๊ณผ ํฌํ† ๋ฆฌ์–ผ๋ฆฌ์Šคํ‹ฑ ์ด๋ฏธ์ง€๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์ƒ์„ฑ๋˜์–ด ์‹œ๊ฐ„์ด ์ ˆ์•ฝ๋ฉ๋‹ˆ๋‹ค
2746
+ - ๊ฐ ์Šฌ๋ผ์ด๋“œ๋Š” ๋‚ด์šฉ์— ๋งž๋Š” ๊ณ ์œ ํ•œ ์‹œ๊ฐ ๋ฉ”ํƒ€ํฌ์™€ ์Šคํƒ€์ผ๋กœ ์ด๋ฏธ์ง€๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค
2747
  - ๊ฒฐ๋ก /์š”์•ฝ ์Šฌ๋ผ์ด๋“œ๋Š” ์ž๋™์œผ๋กœ ๊ฐ์ง€๋˜์–ด ํŠน๋ณ„ํ•œ ์ด๋ฏธ์ง€๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค
 
2748
  """
2749
  )
2750
 
 
2783
 
2784
  # Initialize APIs on startup
2785
  if __name__ == "__main__":
2786
+ # Try to initialize APIs in parallel
2787
+ with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
2788
+ futures = []
2789
+ if AI_IMAGE_API_URL:
2790
+ futures.append(executor.submit(initialize_ai_image_api))
2791
+ if FLUX_API_URL:
2792
+ futures.append(executor.submit(initialize_flux_api))
2793
+ if DIAGRAM_API_URL:
2794
+ futures.append(executor.submit(initialize_diagram_api))
2795
+
2796
+ # Wait for all to complete
2797
+ for future in concurrent.futures.as_completed(futures):
2798
+ try:
2799
+ future.result()
2800
+ except Exception as e:
2801
+ logger.error(f"API initialization failed: {e}")
2802
 
2803
  demo.launch()