|
import gradio as gr |
|
import replicate |
|
import requests |
|
import os |
|
import json |
|
import asyncio |
|
import concurrent.futures |
|
from io import BytesIO |
|
from PIL import Image |
|
from typing import List, Tuple, Dict |
|
import zipfile |
|
from datetime import datetime |
|
import time |
|
import traceback |
|
|
|
|
|
REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN") |
|
FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN") |
|
|
|
|
|
STYLE_TEMPLATES = { |
|
"3D Style (Pixar-like)": { |
|
"name": "3D Style", |
|
"description": "Pixar-esque 3D render with volumetric lighting", |
|
"use_case": "νμ§, λΉμ , λ―Έλ 컨μ
", |
|
"example": "A fluffy ginger cat wearing a tiny spacesuit, floating amidst a vibrant nebula in a 3D render. The cat is gazing curiously at a swirling planet with rings made of candy. Background is filled with sparkling stars and colorful gas clouds, lit with soft, volumetric lighting. Style: Pixar-esque, highly detailed, playful. Colors: Deep blues, purples, oranges, and pinks. Rendered in Octane, 8k resolution." |
|
}, |
|
"Elegant SWOT Quadrant": { |
|
"name": "SWOT Analysis", |
|
"description": "Flat-design 4-grid layout with minimal shadows", |
|
"use_case": "νν© λΆμ, μ λ΅ νκ°", |
|
"example": "Elegant SWOT quadrant: flat-design 4-grid on matte-white backdrop, thin pastel separators, top-left 'Strengths' panel shows glowing shield icon and subtle motif, top-right 'Weaknesses' panel with cracked chain icon in soft crimson, bottom-left 'Opportunities' panel with sunrise-over-horizon icon in optimistic teal, bottom-right 'Threats' panel with storm-cloud & lightning icon in deep indigo, minimal shadows, no text, no watermark, 16:9, 4K" |
|
}, |
|
"Colorful Mind Map": { |
|
"name": "Mind Map", |
|
"description": "Hand-drawn educational style with vibrant colors", |
|
"use_case": "λΈλ μΈμ€ν λ°, μμ΄λμ΄ μ 리", |
|
"example": "A handrawn colorful mind map diagram: educational style, vibrant colors, clear hierarchy, golden ratio layout. Central concept with branching sub-topics, each branch with unique color coding, organic flowing connections, doodle-style icons for each node" |
|
}, |
|
"Business Workflow": { |
|
"name": "Business Process", |
|
"description": "End-to-end business workflow with clear phases", |
|
"use_case": "νλ‘μΈμ€ μ€λͺ
, λ¨κ³λ³ μ§ν", |
|
"example": "A detailed hand-drawn diagram illustrating an end-to-end business workflow with Market Analysis, Strategy Development, Product Design, Implementation, and Post-Launch Review phases. Clear directional arrows, iconography for each component, vibrant educational yet professional style" |
|
}, |
|
"Industrial Design": { |
|
"name": "Product Design", |
|
"description": "Sleek industrial design concept sketch", |
|
"use_case": "μ ν μκ°, 컨μ
λμμΈ", |
|
"example": "A sleek industrial design concept: Curved metallic body with minimal bezel, Touchscreen panel for settings, Modern matte black finish, Hand-drawn concept sketch style with annotations and dimension lines" |
|
}, |
|
"3D Bubble Chart": { |
|
"name": "Bubble Chart", |
|
"description": "Clean 3D bubble visualization", |
|
"use_case": "λΉκ΅ λΆμ, ν¬μ§μ
λ", |
|
"example": "3-D bubble chart on clean white 2Γ2 grid, quadrant titles hidden, four translucent spheres in lime, azure, amber, magenta, gentle depth-of-field, modern consulting aesthetic, no text, 4K" |
|
}, |
|
"Timeline Ribbon": { |
|
"name": "Timeline", |
|
"description": "Horizontal ribbon timeline with cyber-futuristic vibe", |
|
"use_case": "μΌμ , λ‘λλ§΅, λ§μΌμ€ν€", |
|
"example": "Horizontal ribbon timeline, milestone pins glowing hot pink on charcoal, year markers as circles, faint motion streaks, cyber-futuristic vibe, no text, 1920Γ1080" |
|
}, |
|
"Risk Heat Map": { |
|
"name": "Heat Map", |
|
"description": "Risk assessment heat map with gradient colors", |
|
"use_case": "리μ€ν¬ λΆμ, μ°μ μμ", |
|
"example": "Risk Heat Map: square grid, smooth gradient from mint to fire-red, cells beveled, simple legend strip hidden, long subtle shadow, sterile white frame, no text" |
|
}, |
|
"Pyramid/Funnel": { |
|
"name": "Funnel Chart", |
|
"description": "Multi-layer gradient funnel visualization", |
|
"use_case": "λ¨κ³λ³ μΆμ, ν΅μ¬ λμΆ", |
|
"example": "Pyramid / Funnel: 5-layer gradient funnel narrowing downwards, top vivid sky-blue, mid mint-green, bottom sunset-orange, glass reflection, minimal background, no text" |
|
}, |
|
"KPI Dashboard": { |
|
"name": "Dashboard", |
|
"description": "Dark-mode analytics dashboard with sci-fi interface", |
|
"use_case": "μ±κ³Ό μ§ν, μ€μ λμ보λ", |
|
"example": "KPI Dashboard: Dark-mode analytic dashboard, three glass speedometers glowing neon lime, two sparkline charts under, black glass background, sci-fi interface, no text, 4K" |
|
}, |
|
"Value Chain": { |
|
"name": "Value Chain", |
|
"description": "Horizontal value chain with industrial look", |
|
"use_case": "κ°μΉ μ¬μ¬, λΉμ¦λμ€ λͺ¨λΈ", |
|
"example": "Value Chain Diagram: Horizontal value chain blocks, steel-blue gradient bars with subtle bevel, small gear icons above each segment, sleek industrial look, shadow cast, no text" |
|
}, |
|
"Gantt Chart": { |
|
"name": "Gantt Chart", |
|
"description": "Hand-drawn style Gantt chart with playful colors", |
|
"use_case": "νλ‘μ νΈ μΌμ , μμ
κ΄λ¦¬", |
|
"example": "Gantt Chart: Hand-drawn style Gantt bars sketched with vibrant markers on dotted grid notebook page, sticky-note color palette, playful yet organized, perspective tilt, no text" |
|
}, |
|
"Mobile App Mockup": { |
|
"name": "App Mockup", |
|
"description": "Clean wireframe for mobile app design", |
|
"use_case": "μ±/μΉ UI, νλ©΄ μ€κ³", |
|
"example": "MOCKUP DESIGN: A clean hand-drawn style wireframe for a mobile app with Title screen, Login screen, Dashboard with sections, Bottom navigation bar, minimalist design with annotations" |
|
}, |
|
"Flowchart": { |
|
"name": "Flowchart", |
|
"description": "Vibrant flowchart with minimalistic icons", |
|
"use_case": "μμ¬κ²°μ , νλ‘μΈμ€ νλ¦", |
|
"example": "FLOWCHART DESIGN: A hand-drawn style flowchart, vibrant colors, minimalistic icons showing process flow from START to END with decision points, branches, and clear directional arrows" |
|
} |
|
} |
|
|
|
|
|
PPT_TEMPLATES = { |
|
"λΉμ¦λμ€ μ μμ": { |
|
"description": "ν¬μ μ μΉ, μ¬μ
μ μμ©", |
|
"slides": [ |
|
{"title": "νμ§", "style": "3D Style (Pixar-like)", "prompt_hint": "νμ¬ λΉμ κ³Ό λ―Έλ"}, |
|
{"title": "λͺ©μ°¨", "style": "Flowchart", "prompt_hint": "νλ μ ν
μ΄μ
ꡬ쑰"}, |
|
{"title": "λ¬Έμ μ μ", "style": "Colorful Mind Map", "prompt_hint": "νμ¬ μμ₯μ λ¬Έμ μ "}, |
|
{"title": "νν© λΆμ", "style": "Elegant SWOT Quadrant", "prompt_hint": "κ°μ , μ½μ , κΈ°ν, μν"}, |
|
{"title": "μ루μ
", "style": "Industrial Design", "prompt_hint": "μ ν/μλΉμ€ 컨μ
"}, |
|
{"title": "νλ‘μΈμ€", "style": "Business Workflow", "prompt_hint": "μ€ν λ¨κ³"}, |
|
{"title": "μΌμ ", "style": "Timeline Ribbon", "prompt_hint": "μ£Όμ λ§μΌμ€ν€"}, |
|
{"title": "μ±κ³Ό μμΈ‘", "style": "KPI Dashboard", "prompt_hint": "μμ μ±κ³Ό μ§ν"}, |
|
{"title": "ν¬μ μμ²", "style": "Pyramid/Funnel", "prompt_hint": "ν¬μ κ·λͺ¨μ νμ©"} |
|
] |
|
}, |
|
"μ ν μκ°": { |
|
"description": "μ μ ν λ°μΉ, μλΉμ€ μκ°μ©", |
|
"slides": [ |
|
{"title": "μ ν 컨μ
", "style": "Industrial Design", "prompt_hint": "μ ν λμμΈ"}, |
|
{"title": "μ¬μ©μ λμ¦", "style": "Colorful Mind Map", "prompt_hint": "κ³ κ° νμΈν¬μΈνΈ"}, |
|
{"title": "κΈ°λ₯ μκ°", "style": "Mobile App Mockup", "prompt_hint": "UI/UX νλ©΄"}, |
|
{"title": "μλ μ리", "style": "Flowchart", "prompt_hint": "κΈ°λ₯ νλ‘μ°"}, |
|
{"title": "μμ₯ ν¬μ§μ
", "style": "3D Bubble Chart", "prompt_hint": "κ²½μμ¬ λΉκ΅"}, |
|
{"title": "μΆμ μΌμ ", "style": "Timeline Ribbon", "prompt_hint": "λ°μΉ λ‘λλ§΅"} |
|
] |
|
}, |
|
"νλ‘μ νΈ λ³΄κ³ ": { |
|
"description": "μ§ν μν©, μ±κ³Ό λ³΄κ³ μ©", |
|
"slides": [ |
|
{"title": "νλ‘μ νΈ κ°μ", "style": "Business Workflow", "prompt_hint": "μ 체 νλ‘μΈμ€"}, |
|
{"title": "μ§ν νν©", "style": "Gantt Chart", "prompt_hint": "μμ
μΌμ "}, |
|
{"title": "리μ€ν¬ κ΄λ¦¬", "style": "Risk Heat Map", "prompt_hint": "μν μμ"}, |
|
{"title": "μ±κ³Ό μ§ν", "style": "KPI Dashboard", "prompt_hint": "λ¬μ± μ€μ "}, |
|
{"title": "ν₯ν κ³ν", "style": "Timeline Ribbon", "prompt_hint": "λ€μ λ¨κ³"} |
|
] |
|
}, |
|
"μ λ΅ κΈ°ν": { |
|
"description": "μ€μ₯κΈ° μ λ΅, λΉμ μ립μ©", |
|
"slides": [ |
|
{"title": "λΉμ ", "style": "3D Style (Pixar-like)", "prompt_hint": "λ―Έλ λΉμ "}, |
|
{"title": "νκ²½ λΆμ", "style": "Elegant SWOT Quadrant", "prompt_hint": "λ΄μΈλΆ νκ²½"}, |
|
{"title": "μ λ΅ μ²΄κ³", "style": "Colorful Mind Map", "prompt_hint": "μ λ΅ κ΅¬μ‘°"}, |
|
{"title": "κ°μΉ μ¬μ¬", "style": "Value Chain", "prompt_hint": "λΉμ¦λμ€ λͺ¨λΈ"}, |
|
{"title": "μ€ν λ‘λλ§΅", "style": "Timeline Ribbon", "prompt_hint": "λ¨κ³λ³ κ³ν"}, |
|
{"title": "λͺ©ν μ§ν", "style": "KPI Dashboard", "prompt_hint": "KPI λͺ©ν"} |
|
] |
|
}, |
|
"μ¬μ©μ μ μ": { |
|
"description": "μ§μ ꡬμ±νκΈ°", |
|
"slides": [] |
|
} |
|
} |
|
|
|
def generate_prompt_with_llm(topic: str, style_example: str = None, slide_context: str = None) -> str: |
|
"""μ£Όμ μ μ€νμΌ μμ λ₯Ό λ°μμ LLMμ μ¬μ©ν΄ μ΄λ―Έμ§ ν둬ννΈλ₯Ό μμ±""" |
|
print(f"[LLM] ν둬ννΈ μμ± μμ: {slide_context}") |
|
|
|
url = "https://api.friendli.ai/dedicated/v1/chat/completions" |
|
headers = { |
|
"Authorization": f"Bearer {FRIENDLI_TOKEN}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
system_prompt = """You are an expert image prompt engineer specializing in creating prompts for professional presentation slides. |
|
|
|
Your task is to create prompts that: |
|
1. Are highly specific and visual, perfect for PPT backgrounds or main visuals |
|
2. Consider the slide's purpose and maintain consistency across a presentation |
|
3. Include style references matching the given example |
|
4. Focus on clean, professional visuals that won't distract from text overlays |
|
5. Ensure high contrast areas for text readability when needed |
|
6. Maintain brand consistency and professional aesthetics |
|
|
|
Important guidelines: |
|
- If given a style example, adapt the topic to match that specific visual style |
|
- Consider the slide context (e.g., "νμ§", "νν© λΆμ") to create appropriate visuals |
|
- Always output ONLY the prompt without any explanation |
|
- Keep prompts between 50-150 words for optimal results |
|
- Ensure the visual supports rather than overwhelms the slide content""" |
|
|
|
user_message = f"Topic: {topic}" |
|
if style_example: |
|
user_message += f"\n\nStyle reference to follow:\n{style_example}" |
|
if slide_context: |
|
user_message += f"\n\nSlide context: {slide_context}" |
|
|
|
payload = { |
|
"model": "dep89a2fld32mcm", |
|
"messages": [ |
|
{ |
|
"role": "system", |
|
"content": system_prompt |
|
}, |
|
{ |
|
"role": "user", |
|
"content": user_message |
|
} |
|
], |
|
"max_tokens": 300, |
|
"top_p": 0.8, |
|
"temperature": 0.7, |
|
"stream": False |
|
} |
|
|
|
try: |
|
response = requests.post(url, json=payload, headers=headers, timeout=30) |
|
if response.status_code == 200: |
|
result = response.json() |
|
prompt = result['choices'][0]['message']['content'].strip() |
|
print(f"[LLM] ν둬ννΈ μμ± μλ£: {prompt[:50]}...") |
|
return prompt |
|
else: |
|
error_msg = f"ν둬ννΈ μμ± μ€ν¨: {response.status_code}" |
|
print(f"[LLM] {error_msg}") |
|
return error_msg |
|
except Exception as e: |
|
error_msg = f"ν둬ννΈ μμ± μ€ μ€λ₯ λ°μ: {str(e)}" |
|
print(f"[LLM] {error_msg}") |
|
return error_msg |
|
|
|
def translate_to_english(text: str) -> str: |
|
"""νκΈ ν
μ€νΈλ₯Ό μμ΄λ‘ λ²μ (LLM μ¬μ©)""" |
|
if not any(ord('κ°') <= ord(char) <= ord('ν£') for char in text): |
|
return text |
|
|
|
print(f"[λ²μ] νκΈ κ°μ§, μμ΄λ‘ λ²μ μμ") |
|
|
|
url = "https://api.friendli.ai/dedicated/v1/chat/completions" |
|
headers = { |
|
"Authorization": f"Bearer {FRIENDLI_TOKEN}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
payload = { |
|
"model": "dep89a2fld32mcm", |
|
"messages": [ |
|
{ |
|
"role": "system", |
|
"content": "You are a translator. Translate the given Korean text to English. Only return the translation without any explanation." |
|
}, |
|
{ |
|
"role": "user", |
|
"content": text |
|
} |
|
], |
|
"max_tokens": 500, |
|
"top_p": 0.8, |
|
"stream": False |
|
} |
|
|
|
try: |
|
response = requests.post(url, json=payload, headers=headers, timeout=30) |
|
if response.status_code == 200: |
|
result = response.json() |
|
translated = result['choices'][0]['message']['content'].strip() |
|
print(f"[λ²μ] μλ£") |
|
return translated |
|
else: |
|
print(f"[λ²μ] μ€ν¨, μλ³Έ μ¬μ©") |
|
return text |
|
except Exception as e: |
|
print(f"[λ²μ] μ€λ₯: {str(e)}, μλ³Έ μ¬μ©") |
|
return text |
|
|
|
def generate_image(prompt: str, seed: int = 10, slide_info: str = "") -> Tuple[Image.Image, str]: |
|
"""Replicate APIλ₯Ό μ¬μ©ν΄ μ΄λ―Έμ§ μμ±""" |
|
print(f"\n[μ΄λ―Έμ§ μμ±] {slide_info}") |
|
print(f"[μ΄λ―Έμ§ μμ±] ν둬ννΈ: {prompt[:50]}...") |
|
|
|
try: |
|
english_prompt = translate_to_english(prompt) |
|
|
|
if not REPLICATE_API_TOKEN: |
|
error_msg = "RAPI_TOKEN νκ²½λ³μκ° μ€μ λμ§ μμμ΅λλ€." |
|
print(f"[μ΄λ―Έμ§ μμ±] μ€λ₯: {error_msg}") |
|
return None, error_msg |
|
|
|
print(f"[μ΄λ―Έμ§ μμ±] Replicate API νΈμΆ μ€...") |
|
client = replicate.Client(api_token=REPLICATE_API_TOKEN) |
|
|
|
input_params = { |
|
"seed": seed, |
|
"prompt": english_prompt, |
|
"speed_mode": "Extra Juiced π (even more speed)", |
|
"output_quality": 100 |
|
} |
|
|
|
start_time = time.time() |
|
output = client.run( |
|
"prunaai/hidream-l1-fast:17c237d753218fed0ed477cb553902b6b75735f48c128537ab829096ef3d3645", |
|
input=input_params |
|
) |
|
|
|
elapsed = time.time() - start_time |
|
print(f"[μ΄λ―Έμ§ μμ±] API μλ΅ λ°μ ({elapsed:.1f}μ΄)") |
|
|
|
if output: |
|
if isinstance(output, str) and output.startswith('http'): |
|
print(f"[μ΄λ―Έμ§ μμ±] URLμμ μ΄λ―Έμ§ λ€μ΄λ‘λ μ€...") |
|
response = requests.get(output, timeout=30) |
|
img = Image.open(BytesIO(response.content)) |
|
print(f"[μ΄λ―Έμ§ μμ±] μλ£!") |
|
return img, english_prompt |
|
else: |
|
print(f"[μ΄λ―Έμ§ μμ±] λ°μ΄λ리 λ°μ΄ν° μ²λ¦¬ μ€...") |
|
img = Image.open(BytesIO(output.read())) |
|
print(f"[μ΄λ―Έμ§ μμ±] μλ£!") |
|
return img, english_prompt |
|
else: |
|
error_msg = "μ΄λ―Έμ§ μμ± μ€ν¨ - λΉ μλ΅" |
|
print(f"[μ΄λ―Έμ§ μμ±] {error_msg}") |
|
return None, error_msg |
|
|
|
except Exception as e: |
|
error_msg = f"μ€λ₯: {str(e)}" |
|
print(f"[μ΄λ―Έμ§ μμ±] {error_msg}") |
|
print(f"[μ΄λ―Έμ§ μμ±] μμΈ μ€λ₯:\n{traceback.format_exc()}") |
|
return None, error_msg |
|
|
|
def generate_ppt_images_sequential(topic: str, template_name: str, custom_slides: List[Dict], seed: int, progress=gr.Progress()): |
|
"""PPT ν
νλ¦Ώμ λ°λΌ μ΄λ―Έμ§λ₯Ό μμ°¨μ μΌλ‘ μμ±νλ©° μ§ν μν© νμ""" |
|
results = [] |
|
images_for_gallery = [] |
|
|
|
|
|
if template_name == "μ¬μ©μ μ μ" and custom_slides: |
|
slides = custom_slides |
|
else: |
|
slides = PPT_TEMPLATES[template_name]["slides"] |
|
|
|
if not slides: |
|
yield [], "μ¬λΌμ΄λκ° μ μλμ§ μμμ΅λλ€." |
|
return |
|
|
|
total_slides = len(slides) |
|
print(f"\n[PPT μμ±] μμ - μ΄ {total_slides}κ° μ¬λΌμ΄λ") |
|
print(f"[PPT μμ±] μ£Όμ : {topic}") |
|
print(f"[PPT μμ±] ν
νλ¦Ώ: {template_name}") |
|
|
|
|
|
for i, slide in enumerate(slides): |
|
progress((i + 1) / (total_slides + 1), f"μ¬λΌμ΄λ {i+1}/{total_slides} μ²λ¦¬ μ€...") |
|
|
|
slide_info = f"μ¬λΌμ΄λ {i+1}: {slide['title']}" |
|
status_msg = f"\n### π {slide_info} μμ± μ€...\n" |
|
|
|
|
|
yield images_for_gallery, status_msg + format_results_status(results) |
|
|
|
style_key = slide["style"] |
|
if style_key in STYLE_TEMPLATES: |
|
style_info = STYLE_TEMPLATES[style_key] |
|
slide_context = f"{slide['title']} - {slide.get('prompt_hint', '')}" |
|
|
|
|
|
prompt = generate_prompt_with_llm(topic, style_info["example"], slide_context) |
|
|
|
|
|
slide_seed = seed + i |
|
img, used_prompt = generate_image(prompt, slide_seed, slide_info) |
|
|
|
|
|
result = { |
|
"slide_title": slide["title"], |
|
"style": slide["style"], |
|
"image": img, |
|
"prompt": prompt, |
|
"used_prompt": used_prompt, |
|
"success": img is not None |
|
} |
|
results.append(result) |
|
|
|
|
|
if img is not None: |
|
caption = f"{i+1}. {slide['title']} ({style_info['name']})" |
|
images_for_gallery.append((img, caption)) |
|
|
|
status_msg = f"\n### β
{slide_info} μλ£!\n" |
|
else: |
|
status_msg = f"\n### β {slide_info} μ€ν¨: {used_prompt}\n" |
|
|
|
|
|
yield images_for_gallery, status_msg + format_results_status(results) |
|
|
|
|
|
progress(1.0, "μλ£!") |
|
successful = sum(1 for r in results if r["success"]) |
|
final_status = f"\n## π μμ± μλ£!\nμ΄ {total_slides}κ° μ¬λΌμ΄λ μ€ {successful}κ° μ±κ³΅\n\n" |
|
final_status += format_results_status(results) |
|
|
|
yield images_for_gallery, final_status |
|
|
|
def format_results_status(results: List[Dict]) -> str: |
|
"""κ²°κ³Όλ₯Ό μν λ©μμ§λ‘ ν¬λ§·""" |
|
if not results: |
|
return "" |
|
|
|
status_lines = ["### π μμ± κ²°κ³Ό:\n"] |
|
for i, result in enumerate(results): |
|
if result["success"]: |
|
status_lines.append(f"β
**μ¬λΌμ΄λ {i+1}: {result['slide_title']}**") |
|
status_lines.append(f" - μ€νμΌ: {result['style'].split('(')[0].strip()}") |
|
status_lines.append(f" - ν둬ννΈ: {result['prompt'][:60]}...\n") |
|
else: |
|
status_lines.append(f"β **μ¬λΌμ΄λ {i+1}: {result['slide_title']}** - μ€ν¨") |
|
status_lines.append(f" - μ€λ₯: {result['used_prompt']}\n") |
|
|
|
return "\n".join(status_lines) |
|
|
|
def create_custom_slides_ui(): |
|
"""μ¬μ©μ μ μ μ¬λΌμ΄λ κ΅¬μ± UI""" |
|
slides = [] |
|
for i in range(10): |
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
title = gr.Textbox( |
|
label=f"μ¬λΌμ΄λ {i+1} μ λͺ©", |
|
placeholder="μ: νμ§, λͺ©μ°¨, νν© λΆμ...", |
|
visible=(i < 3) |
|
) |
|
with gr.Column(scale=3): |
|
style = gr.Dropdown( |
|
choices=list(STYLE_TEMPLATES.keys()), |
|
label=f"μ€νμΌ μ ν", |
|
visible=(i < 3) |
|
) |
|
with gr.Column(scale=3): |
|
hint = gr.Textbox( |
|
label=f"ν둬ννΈ ννΈ", |
|
placeholder="μ΄ μ¬λΌμ΄λμμ νννκ³ μΆμ λ΄μ©", |
|
visible=(i < 3) |
|
) |
|
slides.append({"title": title, "style": style, "hint": hint}) |
|
return slides |
|
|
|
|
|
with gr.Blocks(title="PPT μ΄λ―Έμ§ μμ±κΈ°", theme=gr.themes.Soft()) as demo: |
|
gr.Markdown(""" |
|
# π― AI κΈ°λ° PPT μ΄λ―Έμ§ μμ±κΈ° |
|
|
|
### μ λ¬Έμ μΈ νλ μ ν
μ΄μ
μ μν λ§μΆ€ν μ΄λ―Έμ§λ₯Ό ν λ²μ μμ±νμΈμ! |
|
""") |
|
|
|
|
|
if not REPLICATE_API_TOKEN: |
|
gr.Markdown("β οΈ **κ²½κ³ **: RAPI_TOKEN νκ²½ λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
|
if not FRIENDLI_TOKEN: |
|
gr.Markdown("β οΈ **κ²½κ³ **: FRIENDLI_TOKEN νκ²½ λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
|
|
topic_input = gr.Textbox( |
|
label="νλ μ ν
μ΄μ
μ£Όμ ", |
|
placeholder="μ: AI μ€ννΈμ
ν¬μ μ μΉ, μ μ ν λ°μΉ, λμ§νΈ μ ν μ λ΅", |
|
lines=2 |
|
) |
|
|
|
|
|
template_select = gr.Dropdown( |
|
choices=list(PPT_TEMPLATES.keys()), |
|
label="PPT ν
νλ¦Ώ μ ν", |
|
value="λΉμ¦λμ€ μ μμ", |
|
info="λͺ©μ μ λ§λ ν
νλ¦Ώμ μ ννμΈμ" |
|
) |
|
|
|
|
|
template_info = gr.Markdown() |
|
|
|
|
|
seed_input = gr.Slider( |
|
minimum=1, |
|
maximum=100, |
|
value=10, |
|
step=1, |
|
label="μλ κ°" |
|
) |
|
|
|
generate_btn = gr.Button("π PPT μ΄λ―Έμ§ μΈνΈ μμ±", variant="primary", size="lg") |
|
|
|
|
|
with gr.Accordion("π μ¬μ©μ μ μ μ¬λΌμ΄λ ꡬμ±", open=False) as custom_accordion: |
|
gr.Markdown("ν
νλ¦Ώμ μ¬μ©νμ§ μκ³ μ§μ μ¬λΌμ΄λλ₯Ό ꡬμ±νμΈμ.") |
|
custom_slides_components = create_custom_slides_ui() |
|
|
|
with gr.Column(scale=2): |
|
|
|
status_output = gr.Markdown( |
|
value="### π ν
νλ¦Ώμ μ ννκ³ μμ± λ²νΌμ ν΄λ¦νμΈμ!\n\nμμ± μ§ν μν©μ΄ μ€μκ°μΌλ‘ νμλ©λλ€." |
|
) |
|
|
|
|
|
output_gallery = gr.Gallery( |
|
label="μμ±λ PPT μ΄λ―Έμ§", |
|
show_label=True, |
|
elem_id="gallery", |
|
columns=3, |
|
rows=3, |
|
object_fit="contain", |
|
height="auto" |
|
) |
|
|
|
|
|
with gr.Accordion("π μ€νμΌλ³ νμ© κ°μ΄λ", open=False): |
|
style_guide = "### PPT μ μμ μν μ€νμΌ κ°μ΄λ\n\n" |
|
for style_name, style_info in STYLE_TEMPLATES.items(): |
|
style_guide += f"**{style_name}**\n" |
|
style_guide += f"- μ©λ: {style_info['use_case']}\n" |
|
style_guide += f"- νΉμ§: {style_info['description']}\n\n" |
|
gr.Markdown(style_guide) |
|
|
|
|
|
gr.Markdown(""" |
|
--- |
|
### π‘ PPT μ μ ν: |
|
|
|
1. **ν
νλ¦Ώ νμ©**: λͺ©μ μ λ§λ ν
νλ¦Ώμ μ ννλ©΄ μ΅μ νλ μ¬λΌμ΄λ ꡬμ±μ μ 곡ν©λλ€ |
|
2. **μΌκ΄μ±**: νλμ μ£Όμ λ‘ μ 체 μ¬λΌμ΄λλ₯Ό μμ±νμ¬ μκ°μ μΌκ΄μ±μ μ μ§ν©λλ€ |
|
3. **μ€μκ° νμΈ**: κ° μ¬λΌμ΄λκ° μμ±λ λλ§λ€ μ§ν μν©μ νμΈν μ μμ΅λλ€ |
|
4. **κ³ νμ§**: λͺ¨λ μ΄λ―Έμ§λ νλ μ ν
μ΄μ
μ μ ν©ν κ³ ν΄μλλ‘ μμ±λ©λλ€ |
|
""") |
|
|
|
|
|
def update_template_info(template_name): |
|
if template_name in PPT_TEMPLATES: |
|
template = PPT_TEMPLATES[template_name] |
|
info = f"**{template['description']}**\n\nν¬ν¨λ μ¬λΌμ΄λ:\n" |
|
for i, slide in enumerate(template['slides']): |
|
info += f"{i+1}. {slide['title']} - {STYLE_TEMPLATES[slide['style']]['use_case']}\n" |
|
return info |
|
return "" |
|
|
|
def generate_ppt_images_handler(topic, template_name, seed, progress=gr.Progress(), *custom_inputs): |
|
if not topic.strip(): |
|
yield [], "β μ£Όμ λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ." |
|
return |
|
|
|
|
|
custom_slides = [] |
|
if template_name == "μ¬μ©μ μ μ": |
|
for i in range(0, len(custom_inputs), 3): |
|
title = custom_inputs[i] |
|
style = custom_inputs[i+1] if i+1 < len(custom_inputs) else None |
|
hint = custom_inputs[i+2] if i+2 < len(custom_inputs) else "" |
|
|
|
if title and style: |
|
custom_slides.append({ |
|
"title": title, |
|
"style": style, |
|
"prompt_hint": hint |
|
}) |
|
|
|
|
|
for gallery, status in generate_ppt_images_sequential(topic, template_name, custom_slides, seed, progress): |
|
yield gallery, status |
|
|
|
|
|
template_select.change( |
|
fn=update_template_info, |
|
inputs=[template_select], |
|
outputs=[template_info] |
|
) |
|
|
|
|
|
all_custom_inputs = [] |
|
for slide_components in custom_slides_components: |
|
all_custom_inputs.extend([ |
|
slide_components["title"], |
|
slide_components["style"], |
|
slide_components["hint"] |
|
]) |
|
|
|
generate_btn.click( |
|
fn=generate_ppt_images_handler, |
|
inputs=[topic_input, template_select, seed_input] + all_custom_inputs, |
|
outputs=[output_gallery, status_output] |
|
) |
|
|
|
|
|
demo.load( |
|
fn=update_template_info, |
|
inputs=[template_select], |
|
outputs=[template_info] |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
print("\n" + "="*50) |
|
print("π PPT μ΄λ―Έμ§ μμ±κΈ° μμ!") |
|
print("="*50) |
|
|
|
|
|
if not REPLICATE_API_TOKEN: |
|
print("β οΈ κ²½κ³ : RAPI_TOKEN νκ²½ λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
|
else: |
|
print("β
RAPI_TOKEN νμΈλ¨") |
|
|
|
if not FRIENDLI_TOKEN: |
|
print("β οΈ κ²½κ³ : FRIENDLI_TOKEN νκ²½ λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
|
else: |
|
print("β
FRIENDLI_TOKEN νμΈλ¨") |
|
|
|
print("="*50 + "\n") |
|
|
|
demo.launch() |