Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -623,97 +623,190 @@ def generate_diagram_locally(json_data: str, diagram_type: str, output_format: s
|
|
| 623 |
##############################################################################
|
| 624 |
# โโโโโโโโโโโโโโโโโโ Prompt Templates (6 Styles) โโโโโโโโโโโโโโโโโโ
|
| 625 |
EXAMPLE_PROMPTS: dict[str, str] = {
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
"Mindmap":
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
"Infographic": """A sophisticated flat-style infographic for a multinational corporation's annual report:
|
| 652 |
-
- Title: "Global Renewable Energy Trends 2025"
|
| 653 |
-
- Subtitle: "Market Share and Growth Analysis"
|
| 654 |
-
- Visual Elements:
|
| 655 |
-
- Multi-segmented bar charts comparing Solar, Wind, and Hydro energy production across regions
|
| 656 |
-
- Pie chart displaying overall energy distribution: Solar (45%), Wind (30%), Hydro (25%)
|
| 657 |
-
- Trend lines indicating year-over-year growth
|
| 658 |
-
- Icons: Sleek, minimalist representations of a sun, wind turbine, and water droplet
|
| 659 |
-
- Layout: Clean, grid-based design with ample white space and pastel accents for a modern corporate look
|
| 660 |
-
- Annotations: Brief, impactful data callouts highlighting key performance indicators and future forecasts""",
|
| 661 |
-
"Diagram": """A detailed hand-drawn diagram illustrating an end-to-end business workflow:
|
| 662 |
-
- Title: "Integrated Business Process Diagram"
|
| 663 |
-
- Components:
|
| 664 |
-
- Market Analysis โ Strategy Development โ Product Design โ Implementation โ Post-Launch Review
|
| 665 |
-
- Visual Elements:
|
| 666 |
-
- Directional arrows, magnifying glass, lightbulb, gear, checklist icons
|
| 667 |
-
- Style: Vibrant, educational yet professional
|
| 668 |
-
- Layout: Clear hierarchy, color-coded sections""",
|
| 669 |
-
"Flowchart": """A hand-drawn style flowchart, vibrant colors, minimalistic icons.
|
| 670 |
-
BUSINESS WORKFLOW
|
| 671 |
-
โโโ START [Green Button ~40px]
|
| 672 |
-
โ โโโ COLLECT REQUIREMENTS [Folder Icon]
|
| 673 |
-
โ โโโ ANALYZE DATA [Chart Icon]
|
| 674 |
-
โโโ IMPLEMENTATION [Coding Symbol ~50px]
|
| 675 |
-
โ โโโ FRONTEND [Browser Icon]
|
| 676 |
-
โ โโโ BACKEND [Server Icon]
|
| 677 |
-
โโโ TEST & INTEGRATION [Gear Icon ~45px]
|
| 678 |
-
โโโ DEPLOY โ END [Checkered Flag ~40px]"""
|
| 679 |
}
|
| 680 |
STYLE_KEYS = list(EXAMPLE_PROMPTS.keys())
|
| 681 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
def generate_flux_prompt(title: str, content: str, style_key: str) -> str:
|
| 683 |
"""
|
| 684 |
-
Build a FLUX image-generation prompt for one slide,
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
"""
|
| 689 |
-
# 1)
|
| 690 |
bullets = [
|
| 691 |
-
re.sub(r'^[
|
| 692 |
for line in content.splitlines()
|
| 693 |
-
if line.
|
| 694 |
][:8]
|
| 695 |
|
| 696 |
-
# Build node / list representation for templates that need it
|
| 697 |
if bullets:
|
| 698 |
-
|
| 699 |
-
tree_block = '\n'.join(
|
| 700 |
-
|
|
|
|
|
|
|
| 701 |
else:
|
| 702 |
-
|
| 703 |
-
tree_block =
|
| 704 |
|
| 705 |
-
# 2)
|
| 706 |
-
|
|
|
|
| 707 |
|
| 708 |
-
# 3)
|
| 709 |
-
|
|
|
|
|
|
|
|
|
|
| 710 |
|
| 711 |
-
|
| 712 |
-
style_tail = "Corporate palette, white background, high-resolution vector, clean composition."
|
| 713 |
|
| 714 |
-
# 5) Return full prompt (no markdown / commentary)
|
| 715 |
-
full_prompt = f"{prompt_body}\n\n{style_tail}"
|
| 716 |
-
return full_prompt.strip()
|
| 717 |
|
| 718 |
|
| 719 |
def generate_flux_image_via_api(prompt: str) -> Optional[str]:
|
|
|
|
| 623 |
##############################################################################
|
| 624 |
# โโโโโโโโโโโโโโโโโโ Prompt Templates (6 Styles) โโโโโโโโโโโโโโโโโโ
|
| 625 |
EXAMPLE_PROMPTS: dict[str, str] = {
|
| 626 |
+
|
| 627 |
+
"Product Design": (
|
| 628 |
+
"A sleek industrial product-design sketch.\n"
|
| 629 |
+
"{nodes}"
|
| 630 |
+
),
|
| 631 |
+
"Mindmap": (
|
| 632 |
+
"A hand-drawn colorful mind-map, educational style, clear hierarchy.\n"
|
| 633 |
+
"{nodes}"
|
| 634 |
+
),
|
| 635 |
+
"Mockup": (
|
| 636 |
+
"A clean hand-drawn wire-frame for a mobile banking app.\n"
|
| 637 |
+
"{nodes}"
|
| 638 |
+
),
|
| 639 |
+
"Infographic": (
|
| 640 |
+
"A flat corporate infographic โ โGlobal Renewable Energy Trends 2025โ.\n"
|
| 641 |
+
"{nodes}"
|
| 642 |
+
),
|
| 643 |
+
"Diagram": (
|
| 644 |
+
"A hand-drawn business process diagram.\n"
|
| 645 |
+
"{nodes}"
|
| 646 |
+
),
|
| 647 |
+
"Flowchart": (
|
| 648 |
+
"A vibrant hand-drawn flow-chart.\n"
|
| 649 |
+
"{nodes}"
|
| 650 |
+
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
}
|
| 652 |
STYLE_KEYS = list(EXAMPLE_PROMPTS.keys())
|
| 653 |
|
| 654 |
+
def create_advanced_ppt_from_content(
|
| 655 |
+
slides_data: list,
|
| 656 |
+
topic: str,
|
| 657 |
+
theme_name: str = "professional",
|
| 658 |
+
include_charts: bool = False,
|
| 659 |
+
include_ai_image: bool = False,
|
| 660 |
+
include_diagrams: bool = False,
|
| 661 |
+
include_flux_images: bool = False,
|
| 662 |
+
max_images_per_api: int = 3,
|
| 663 |
+
max_diagrams: int = 2,
|
| 664 |
+
) -> str:
|
| 665 |
+
"""
|
| 666 |
+
Build a fully-styled 16:9 PowerPoint file from parsed slide data.
|
| 667 |
+
|
| 668 |
+
โข ์ฌ๋ผ์ด๋ ํ
๋ง/ํฐํธ/์์์ DESIGN_THEMES์์ ๋์ด์จ๋ค.
|
| 669 |
+
โข AI ์ด๋ฏธ์ง: 3D-API์ FLUX-API ๊ฐ๊ฐ ์ต๋ `max_images_per_api` ์ฅ.
|
| 670 |
+
โข ๋ค์ด์ด๊ทธ๋จ: detect_diagram_type_with_score() ์ฐ์ ์์ ์์ `max_diagrams` ๊ฐ.
|
| 671 |
+
โข ํจ์๊ฐ ๋๋๋ฉด ์์ํ์ผ(.pptx) ๊ฒฝ๋ก๋ฅผ ๋๋ ค์ค๋ค.
|
| 672 |
+
"""
|
| 673 |
+
if not PPTX_AVAILABLE:
|
| 674 |
+
raise ImportError("python-pptx ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค (pip install python-pptx)")
|
| 675 |
+
|
| 676 |
+
prs = Presentation()
|
| 677 |
+
theme = DESIGN_THEMES.get(theme_name, DESIGN_THEMES["professional"])
|
| 678 |
+
|
| 679 |
+
# --- ์ฌ๋ผ์ด๋ ์บ๋ฒ์ค ํฌ๊ธฐ (16:9) ---
|
| 680 |
+
prs.slide_width = Inches(10)
|
| 681 |
+
prs.slide_height = Inches(5.625)
|
| 682 |
+
|
| 683 |
+
# ๋ด๋ถ ์นด์ดํฐ
|
| 684 |
+
img_cnt_3d, img_cnt_flux, dia_cnt = 0, 0, 0
|
| 685 |
+
|
| 686 |
+
# โธโธโธ 0. ๋ค์ด์ด๊ทธ๋จ ํ์ ์ฌ๋ผ์ด๋ ๋ฏธ๋ฆฌ ์ ์
|
| 687 |
+
diagram_targets: list[tuple[int, str]] = []
|
| 688 |
+
if include_diagrams:
|
| 689 |
+
scored = []
|
| 690 |
+
for idx, s in enumerate(slides_data):
|
| 691 |
+
d_type, score = detect_diagram_type_with_score(s["title"], s["content"])
|
| 692 |
+
if d_type:
|
| 693 |
+
scored.append((idx, d_type, score))
|
| 694 |
+
scored.sort(key=lambda x: x[2], reverse=True)
|
| 695 |
+
diagram_targets = [(i, t) for i, t, _ in scored[:max_diagrams]]
|
| 696 |
+
|
| 697 |
+
# โธโธโธ 1. ํ์ง ์ฌ๋ผ์ด๋ --------------------------------------------------
|
| 698 |
+
cover = prs.slides.add_slide(prs.slide_layouts[0])
|
| 699 |
+
add_gradient_background(cover, theme["colors"]["primary"], theme["colors"]["secondary"])
|
| 700 |
+
cover.shapes.title.text = topic
|
| 701 |
+
cover.shapes.title.text_frame.paragraphs[0].font.size = Pt(36)
|
| 702 |
+
cover.shapes.title.text_frame.paragraphs[0].font.bold = True
|
| 703 |
+
if include_ai_image and (AI_IMAGE_ENABLED or FLUX_API_ENABLED):
|
| 704 |
+
p3d, pph = generate_cover_image_prompts(topic, slides_data)
|
| 705 |
+
img3d, imgph = generate_images_parallel(p3d, pph)
|
| 706 |
+
pick = img3d if img3d and img_cnt_3d < max_images_per_api else imgph
|
| 707 |
+
if pick:
|
| 708 |
+
cover.shapes.add_picture(pick, Inches(6.2), Inches(2.0), width=Inches(3.3))
|
| 709 |
+
if pick == img3d: img_cnt_3d += 1
|
| 710 |
+
else: img_cnt_flux += 1
|
| 711 |
+
|
| 712 |
+
# โธโธโธ 2. ๋ณธ๋ฌธ ์ฌ๋ผ์ด๋ ---------------------------------------------------
|
| 713 |
+
for idx, s in enumerate(slides_data):
|
| 714 |
+
layout = prs.slide_layouts[1]
|
| 715 |
+
sl = prs.slides.add_slide(layout)
|
| 716 |
+
apply_theme_to_slide(sl, theme)
|
| 717 |
+
sl.shapes.title.text = s["title"]
|
| 718 |
+
|
| 719 |
+
# ์ผ์ชฝ ํ
์คํธ
|
| 720 |
+
left = sl.shapes.add_textbox(Inches(0.6), Inches(1.4), Inches(4.4), Inches(3.6))
|
| 721 |
+
tf = left.text_frame
|
| 722 |
+
tf.text = s["content"]
|
| 723 |
+
force_font_size(tf, 16, theme)
|
| 724 |
+
|
| 725 |
+
# ์ค๋ฅธ์ชฝ ์๊ฐ ์์
|
| 726 |
+
right_x, right_y = Inches(5.2), Inches(1.5)
|
| 727 |
+
visual_done = False
|
| 728 |
+
|
| 729 |
+
# ๋ค์ด์ด๊ทธ๋จ ์ฐ์
|
| 730 |
+
if (idx, _) := next(((i, t) for i, t in diagram_targets if i == idx), None):
|
| 731 |
+
if dia_cnt < max_diagrams:
|
| 732 |
+
j = generate_diagram_json(s["title"], s["content"], _)
|
| 733 |
+
if j:
|
| 734 |
+
p = generate_diagram_locally(j, _, "png")
|
| 735 |
+
if p:
|
| 736 |
+
sl.shapes.add_picture(p, right_x, right_y, width=Inches(4.0))
|
| 737 |
+
visual_done, dia_cnt = True, dia_cnt + 1
|
| 738 |
+
|
| 739 |
+
# ์ด๋ฏธ์ง (3D / FLUX)
|
| 740 |
+
if not visual_done and include_flux_images and (img_cnt_3d + img_cnt_flux) < max_images_per_api * 2:
|
| 741 |
+
p3d, pph = generate_diverse_prompt(s["title"], s["content"], idx)
|
| 742 |
+
pic = None
|
| 743 |
+
if img_cnt_3d < max_images_per_api:
|
| 744 |
+
pic = generate_ai_image_via_3d_api(p3d)
|
| 745 |
+
if pic: img_cnt_3d += 1
|
| 746 |
+
if not pic and img_cnt_flux < max_images_per_api:
|
| 747 |
+
pic = generate_flux_image_via_api(pph)
|
| 748 |
+
if pic: img_cnt_flux += 1
|
| 749 |
+
if pic:
|
| 750 |
+
sl.shapes.add_picture(pic, right_x, right_y, width=Inches(4.0))
|
| 751 |
+
|
| 752 |
+
# ์ฐจํธ
|
| 753 |
+
if include_charts and s.get("chart_data"):
|
| 754 |
+
create_chart_slide(sl, s["chart_data"], theme)
|
| 755 |
+
|
| 756 |
+
# ์ฌ๋ผ์ด๋ ๋ฒํธ
|
| 757 |
+
num_box = sl.shapes.add_textbox(Inches(9.0), Inches(5.1), Inches(1.0), Inches(0.3))
|
| 758 |
+
num_box.text_frame.text = f"{idx+1}/{len(slides_data)}"
|
| 759 |
+
num_box.text_frame.paragraphs[0].font.size = Pt(10)
|
| 760 |
+
|
| 761 |
+
# โธโธโธ 3. ๋ง๋ฌด๋ฆฌ ์ฌ๋ผ์ด๋ -------------------------------------------------
|
| 762 |
+
thanks = prs.slides.add_slide(prs.slide_layouts[5])
|
| 763 |
+
add_gradient_background(thanks, theme["colors"]["secondary"], theme["colors"]["primary"])
|
| 764 |
+
thanks.shapes.title.text = "๊ฐ์ฌํฉ๋๋ค"
|
| 765 |
+
|
| 766 |
+
# โธโธโธ 4. ์ ์ฅ -----------------------------------------------------------
|
| 767 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pptx")
|
| 768 |
+
prs.save(tmp.name)
|
| 769 |
+
return tmp.name
|
| 770 |
+
|
| 771 |
+
# ------------------------------------------------------------------
|
| 772 |
+
# 2) generate_flux_prompt ํจ์ ์ ์ฒด โ
์์ ์๋ฃ
|
| 773 |
+
# ------------------------------------------------------------------
|
| 774 |
def generate_flux_prompt(title: str, content: str, style_key: str) -> str:
|
| 775 |
"""
|
| 776 |
+
Build a FLUX image-generation prompt for one slide, using six pre-defined
|
| 777 |
+
visual styles (Product Design, Mindmap, Mockup, Infographic, Diagram,
|
| 778 |
+
Flowchart). `content` is the raw bullet-point block; โ+โ/โ-โ/โโขโ/โโโ ๋ฑ
|
| 779 |
+
๋ชจ๋ ๊ธ๋จธ๋ฆฌ ๊ธฐํธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
|
| 780 |
"""
|
| 781 |
+
# 1) clean bullet points (max 8)
|
| 782 |
bullets = [
|
| 783 |
+
re.sub(r'^[\+\-\โข\โ]\s*', '', line).strip()
|
| 784 |
for line in content.splitlines()
|
| 785 |
+
if line.lstrip().startswith(('+', '-', 'โข', 'โ'))
|
| 786 |
][:8]
|
| 787 |
|
|
|
|
| 788 |
if bullets:
|
| 789 |
+
nodes_block = '\n'.join(f"- {b}" for b in bullets)
|
| 790 |
+
tree_block = '\n'.join(
|
| 791 |
+
f"{'โโโ' if i < len(bullets)-1 else 'โโโ'} {b}"
|
| 792 |
+
for i, b in enumerate(bullets)
|
| 793 |
+
)
|
| 794 |
else:
|
| 795 |
+
nodes_block = "- (no explicit bullet points) -"
|
| 796 |
+
tree_block = nodes_block
|
| 797 |
|
| 798 |
+
# 2) choose template & inject nodes
|
| 799 |
+
template = EXAMPLE_PROMPTS.get(style_key, EXAMPLE_PROMPTS["Diagram"])
|
| 800 |
+
prompt_body = template.format(nodes=nodes_block, tree=tree_block)
|
| 801 |
|
| 802 |
+
# 3) stylistic tail (โค120 words total)
|
| 803 |
+
tail = (
|
| 804 |
+
"Corporate palette, white background, hand-drawn line style, "
|
| 805 |
+
"clean composition, high-resolution vector."
|
| 806 |
+
)
|
| 807 |
|
| 808 |
+
return f"{prompt_body}\n\n{tail}".strip()
|
|
|
|
| 809 |
|
|
|
|
|
|
|
|
|
|
| 810 |
|
| 811 |
|
| 812 |
def generate_flux_image_via_api(prompt: str) -> Optional[str]:
|