ginipick commited on
Commit
2de4d6d
·
verified ·
1 Parent(s): e393b2f

Delete app-backup3.py

Browse files
Files changed (1) hide show
  1. app-backup3.py +0 -863
app-backup3.py DELETED
@@ -1,863 +0,0 @@
1
- import gradio as gr
2
- import replicate
3
- import requests
4
- import os
5
- import json
6
- import asyncio
7
- import concurrent.futures
8
- from io import BytesIO
9
- from PIL import Image
10
- from typing import List, Tuple, Dict
11
- import zipfile
12
- from datetime import datetime
13
- import time
14
- import traceback
15
- import base64
16
-
17
- # 환경 변수에서 토큰 가져오기
18
- REPLICATE_API_TOKEN = os.getenv("RAPI_TOKEN")
19
- FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN")
20
-
21
- # 스타일 정의
22
- STYLE_TEMPLATES = {
23
- "3D Style (Pixar-like)": {
24
- "name": "3D Style",
25
- "description": "Pixar-esque 3D render with volumetric lighting",
26
- "use_case": "표지, 비전, 미래 컨셉",
27
- "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."
28
- },
29
- "Elegant SWOT Quadrant": {
30
- "name": "SWOT Analysis",
31
- "description": "Flat-design 4-grid layout with minimal shadows",
32
- "use_case": "현황 분석, 전략 평가",
33
- "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"
34
- },
35
- "Colorful Mind Map": {
36
- "name": "Mind Map",
37
- "description": "Hand-drawn educational style with vibrant colors",
38
- "use_case": "브레인스토밍, 아이디어 정리",
39
- "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"
40
- },
41
- "Business Workflow": {
42
- "name": "Business Process",
43
- "description": "End-to-end business workflow with clear phases",
44
- "use_case": "프로세스 설명, 단계별 진행",
45
- "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"
46
- },
47
- "Industrial Design": {
48
- "name": "Product Design",
49
- "description": "Sleek industrial design concept sketch",
50
- "use_case": "제품 소개, 컨셉 디자인",
51
- "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"
52
- },
53
- "3D Bubble Chart": {
54
- "name": "Bubble Chart",
55
- "description": "Clean 3D bubble visualization",
56
- "use_case": "비교 분석, 포지셔닝",
57
- "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"
58
- },
59
- "Timeline Ribbon": {
60
- "name": "Timeline",
61
- "description": "Horizontal ribbon timeline with cyber-futuristic vibe",
62
- "use_case": "일정, 로드맵, 마일스톤",
63
- "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"
64
- },
65
- "Risk Heat Map": {
66
- "name": "Heat Map",
67
- "description": "Risk assessment heat map with gradient colors",
68
- "use_case": "리스크 분석, 우선순위",
69
- "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"
70
- },
71
- "Pyramid/Funnel": {
72
- "name": "Funnel Chart",
73
- "description": "Multi-layer gradient funnel visualization",
74
- "use_case": "단계별 축소, 핵심 도출",
75
- "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"
76
- },
77
- "KPI Dashboard": {
78
- "name": "Dashboard",
79
- "description": "Dark-mode analytics dashboard with sci-fi interface",
80
- "use_case": "성과 지표, 실적 대시보드",
81
- "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"
82
- },
83
- "Value Chain": {
84
- "name": "Value Chain",
85
- "description": "Horizontal value chain with industrial look",
86
- "use_case": "가치 사슬, 비즈니스 모델",
87
- "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"
88
- },
89
- "Gantt Chart": {
90
- "name": "Gantt Chart",
91
- "description": "Hand-drawn style Gantt chart with playful colors",
92
- "use_case": "프로젝트 일정, 작업 관리",
93
- "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"
94
- },
95
- "Mobile App Mockup": {
96
- "name": "App Mockup",
97
- "description": "Clean wireframe for mobile app design",
98
- "use_case": "앱/웹 UI, 화면 설계",
99
- "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"
100
- },
101
- "Flowchart": {
102
- "name": "Flowchart",
103
- "description": "Vibrant flowchart with minimalistic icons",
104
- "use_case": "의사결정, 프로세스 흐름",
105
- "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"
106
- }
107
- }
108
-
109
- # PPT 템플릿 정의
110
- PPT_TEMPLATES = {
111
- "비즈니스 제안서": {
112
- "description": "투자 유치, 사업 제안용",
113
- "slides": [
114
- {"title": "표지", "style": "3D Style (Pixar-like)", "prompt_hint": "회사 비전과 미래"},
115
- {"title": "목차", "style": "Flowchart", "prompt_hint": "프레젠테이션 구조"},
116
- {"title": "문제 정의", "style": "Colorful Mind Map", "prompt_hint": "현재 시장의 문제점"},
117
- {"title": "현황 분석", "style": "Elegant SWOT Quadrant", "prompt_hint": "강점, 약점, 기회, 위협"},
118
- {"title": "솔루션", "style": "Industrial Design", "prompt_hint": "제품/서비스 컨셉"},
119
- {"title": "프로세스", "style": "Business Workflow", "prompt_hint": "실행 단계"},
120
- {"title": "일정", "style": "Timeline Ribbon", "prompt_hint": "주요 마일스톤"},
121
- {"title": "성과 예측", "style": "KPI Dashboard", "prompt_hint": "예상 성과 지표"},
122
- {"title": "투자 요청", "style": "Pyramid/Funnel", "prompt_hint": "투자 규모와 활용"}
123
- ]
124
- },
125
- "제품 소개": {
126
- "description": "신제품 런칭, 서비스 소개용",
127
- "slides": [
128
- {"title": "제품 컨셉", "style": "Industrial Design", "prompt_hint": "제품 디자인"},
129
- {"title": "사용자 니즈", "style": "Colorful Mind Map", "prompt_hint": "고객 페인포인트"},
130
- {"title": "기능 소개", "style": "Mobile App Mockup", "prompt_hint": "UI/UX 화면"},
131
- {"title": "작동 원리", "style": "Flowchart", "prompt_hint": "기능 플로우"},
132
- {"title": "시장 포지션", "style": "3D Bubble Chart", "prompt_hint": "경쟁사 비교"},
133
- {"title": "출시 일정", "style": "Timeline Ribbon", "prompt_hint": "런칭 로드맵"}
134
- ]
135
- },
136
- "프로젝트 보고": {
137
- "description": "진행 상황, 성과 보고용",
138
- "slides": [
139
- {"title": "프로젝트 개요", "style": "Business Workflow", "prompt_hint": "전체 프로세스"},
140
- {"title": "진행 현황", "style": "Gantt Chart", "prompt_hint": "작업 일정"},
141
- {"title": "리스크 관리", "style": "Risk Heat Map", "prompt_hint": "위험 요소"},
142
- {"title": "성과 지표", "style": "KPI Dashboard", "prompt_hint": "달성 실적"},
143
- {"title": "향후 계획", "style": "Timeline Ribbon", "prompt_hint": "다음 단계"}
144
- ]
145
- },
146
- "전략 기획": {
147
- "description": "중장기 전략, 비전 수립용",
148
- "slides": [
149
- {"title": "비전", "style": "3D Style (Pixar-like)", "prompt_hint": "미래 비전"},
150
- {"title": "환경 분석", "style": "Elegant SWOT Quadrant", "prompt_hint": "내외부 환경"},
151
- {"title": "전략 체계", "style": "Colorful Mind Map", "prompt_hint": "전략 구조"},
152
- {"title": "가치 사슬", "style": "Value Chain", "prompt_hint": "비즈니스 모델"},
153
- {"title": "실행 로드맵", "style": "Timeline Ribbon", "prompt_hint": "단계별 계획"},
154
- {"title": "목표 지표", "style": "KPI Dashboard", "prompt_hint": "KPI 목표"}
155
- ]
156
- },
157
- "사용자 정의": {
158
- "description": "직접 구성하기",
159
- "slides": []
160
- }
161
- }
162
-
163
- def generate_slide_content(topic: str, slide_title: str, slide_context: str) -> Dict[str, str]:
164
- """각 슬라이드의 텍스트 내용 생성"""
165
- print(f"[슬라이드 내용] {slide_title} 텍스트 생성 중...")
166
-
167
- url = "https://api.friendli.ai/dedicated/v1/chat/completions"
168
- headers = {
169
- "Authorization": f"Bearer {FRIENDLI_TOKEN}",
170
- "Content-Type": "application/json"
171
- }
172
-
173
- system_prompt = """You are a professional presentation content writer specializing in creating concise, impactful slide content.
174
-
175
- Your task is to create:
176
- 1. A compelling subtitle (max 10 words)
177
- 2. Exactly 5 bullet points, each being a complete, concise sentence
178
- 3. Each bullet point should be 10-15 words
179
-
180
- Guidelines:
181
- - Be specific and actionable
182
- - Use professional business language
183
- - Include relevant data points or metrics when appropriate
184
- - Ensure content aligns with the slide's purpose
185
- - Make each point distinct and valuable
186
- - Use active voice and strong verbs
187
-
188
- Output format:
189
- Subtitle: [subtitle here]
190
- • [Point 1]
191
- • [Point 2]
192
- • [Point 3]
193
- • [Point 4]
194
- • [Point 5]"""
195
-
196
- user_message = f"""Topic: {topic}
197
- Slide Title: {slide_title}
198
- Context: {slide_context}
199
-
200
- Create compelling content for this presentation slide."""
201
-
202
- payload = {
203
- "model": "dep89a2fld32mcm",
204
- "messages": [
205
- {
206
- "role": "system",
207
- "content": system_prompt
208
- },
209
- {
210
- "role": "user",
211
- "content": user_message
212
- }
213
- ],
214
- "max_tokens": 300,
215
- "top_p": 0.8,
216
- "temperature": 0.7,
217
- "stream": False
218
- }
219
-
220
- try:
221
- response = requests.post(url, json=payload, headers=headers, timeout=30)
222
- if response.status_code == 200:
223
- result = response.json()
224
- content = result['choices'][0]['message']['content'].strip()
225
-
226
- # Parse content
227
- lines = content.split('\n')
228
- subtitle = ""
229
- bullet_points = []
230
-
231
- for line in lines:
232
- if line.startswith("Subtitle:"):
233
- subtitle = line.replace("Subtitle:", "").strip()
234
- elif line.strip().startswith("•"):
235
- bullet_points.append(line.strip())
236
-
237
- # 한글로 번역이 필요한 경우
238
- if any(ord('가') <= ord(char) <= ord('힣') for char in topic):
239
- subtitle = translate_content_to_korean(subtitle)
240
- bullet_points = [translate_content_to_korean(point) for point in bullet_points]
241
-
242
- return {
243
- "subtitle": subtitle,
244
- "bullet_points": bullet_points[:5] # 최대 5개
245
- }
246
- else:
247
- return {
248
- "subtitle": slide_title,
249
- "bullet_points": ["내용을 생성할 수 없습니다."] * 5
250
- }
251
- except Exception as e:
252
- print(f"[슬라이드 내용] 오류: {str(e)}")
253
- return {
254
- "subtitle": slide_title,
255
- "bullet_points": ["내용을 생성할 수 없습니다."] * 5
256
- }
257
-
258
- def translate_content_to_korean(text: str) -> str:
259
- """영어 텍스트를 한글로 번역"""
260
- url = "https://api.friendli.ai/dedicated/v1/chat/completions"
261
- headers = {
262
- "Authorization": f"Bearer {FRIENDLI_TOKEN}",
263
- "Content-Type": "application/json"
264
- }
265
-
266
- payload = {
267
- "model": "dep89a2fld32mcm",
268
- "messages": [
269
- {
270
- "role": "system",
271
- "content": "You are a translator. Translate the given English text to Korean. Maintain professional business tone. Only return the translation without any explanation."
272
- },
273
- {
274
- "role": "user",
275
- "content": text
276
- }
277
- ],
278
- "max_tokens": 200,
279
- "top_p": 0.8,
280
- "stream": False
281
- }
282
-
283
- try:
284
- response = requests.post(url, json=payload, headers=headers, timeout=30)
285
- if response.status_code == 200:
286
- result = response.json()
287
- return result['choices'][0]['message']['content'].strip()
288
- else:
289
- return text
290
- except Exception as e:
291
- return text
292
-
293
- def generate_prompt_with_llm(topic: str, style_example: str = None, slide_context: str = None) -> str:
294
- """주제와 스타일 예제를 받아서 LLM을 사용해 이미지 프롬프트를 생성"""
295
- print(f"[LLM] 프롬프트 생성 시작: {slide_context}")
296
-
297
- url = "https://api.friendli.ai/dedicated/v1/chat/completions"
298
- headers = {
299
- "Authorization": f"Bearer {FRIENDLI_TOKEN}",
300
- "Content-Type": "application/json"
301
- }
302
-
303
- system_prompt = """You are an expert image prompt engineer specializing in creating prompts for professional presentation slides.
304
-
305
- Your task is to create prompts that:
306
- 1. Are highly specific and visual, perfect for PPT backgrounds or main visuals
307
- 2. Consider the slide's purpose and maintain consistency across a presentation
308
- 3. Include style references matching the given example
309
- 4. Focus on clean, professional visuals that won't distract from text overlays
310
- 5. Ensure high contrast areas for text readability when needed
311
- 6. Maintain brand consistency and professional aesthetics
312
-
313
- Important guidelines:
314
- - If given a style example, adapt the topic to match that specific visual style
315
- - Consider the slide context (e.g., "표지", "현황 분석") to create appropriate visuals
316
- - Always output ONLY the prompt without any explanation
317
- - Keep prompts between 50-150 words for optimal results
318
- - Ensure the visual supports rather than overwhelms the slide content"""
319
-
320
- user_message = f"Topic: {topic}"
321
- if style_example:
322
- user_message += f"\n\nStyle reference to follow:\n{style_example}"
323
- if slide_context:
324
- user_message += f"\n\nSlide context: {slide_context}"
325
-
326
- payload = {
327
- "model": "dep89a2fld32mcm",
328
- "messages": [
329
- {
330
- "role": "system",
331
- "content": system_prompt
332
- },
333
- {
334
- "role": "user",
335
- "content": user_message
336
- }
337
- ],
338
- "max_tokens": 300,
339
- "top_p": 0.8,
340
- "temperature": 0.7,
341
- "stream": False
342
- }
343
-
344
- try:
345
- response = requests.post(url, json=payload, headers=headers, timeout=30)
346
- if response.status_code == 200:
347
- result = response.json()
348
- prompt = result['choices'][0]['message']['content'].strip()
349
- print(f"[LLM] 프롬프트 생성 완료: {prompt[:50]}...")
350
- return prompt
351
- else:
352
- error_msg = f"프롬프트 생성 실패: {response.status_code}"
353
- print(f"[LLM] {error_msg}")
354
- return error_msg
355
- except Exception as e:
356
- error_msg = f"프롬프트 생성 중 오류 발생: {str(e)}"
357
- print(f"[LLM] {error_msg}")
358
- return error_msg
359
-
360
- def translate_to_english(text: str) -> str:
361
- """한글 텍스트를 영어로 번역 (LLM 사용)"""
362
- if not any(ord('가') <= ord(char) <= ord('힣') for char in text):
363
- return text
364
-
365
- print(f"[번역] 한글 감지, 영어로 번역 시작")
366
-
367
- url = "https://api.friendli.ai/dedicated/v1/chat/completions"
368
- headers = {
369
- "Authorization": f"Bearer {FRIENDLI_TOKEN}",
370
- "Content-Type": "application/json"
371
- }
372
-
373
- payload = {
374
- "model": "dep89a2fld32mcm",
375
- "messages": [
376
- {
377
- "role": "system",
378
- "content": "You are a translator. Translate the given Korean text to English. Only return the translation without any explanation."
379
- },
380
- {
381
- "role": "user",
382
- "content": text
383
- }
384
- ],
385
- "max_tokens": 500,
386
- "top_p": 0.8,
387
- "stream": False
388
- }
389
-
390
- try:
391
- response = requests.post(url, json=payload, headers=headers, timeout=30)
392
- if response.status_code == 200:
393
- result = response.json()
394
- translated = result['choices'][0]['message']['content'].strip()
395
- print(f"[번역] 완료")
396
- return translated
397
- else:
398
- print(f"[번역] 실패, 원본 사용")
399
- return text
400
- except Exception as e:
401
- print(f"[번역] 오류: {str(e)}, 원본 사용")
402
- return text
403
-
404
- def generate_image(prompt: str, seed: int = 10, slide_info: str = "") -> Tuple[Image.Image, str]:
405
- """Replicate API를 사용해 이미지 생성"""
406
- print(f"\n[이미지 생성] {slide_info}")
407
- print(f"[이미지 생성] 프롬프트: {prompt[:50]}...")
408
-
409
- try:
410
- english_prompt = translate_to_english(prompt)
411
-
412
- if not REPLICATE_API_TOKEN:
413
- error_msg = "RAPI_TOKEN 환경변수가 설정되지 않았습니다."
414
- print(f"[이미지 생성] 오류: {error_msg}")
415
- return None, error_msg
416
-
417
- print(f"[이미지 생성] Replicate API 호출 중...")
418
- client = replicate.Client(api_token=REPLICATE_API_TOKEN)
419
-
420
- input_params = {
421
- "seed": seed,
422
- "prompt": english_prompt,
423
- "speed_mode": "Extra Juiced 🚀 (even more speed)",
424
- "output_quality": 100
425
- }
426
-
427
- start_time = time.time()
428
- output = client.run(
429
- "prunaai/hidream-l1-fast:17c237d753218fed0ed477cb553902b6b75735f48c128537ab829096ef3d3645",
430
- input=input_params
431
- )
432
-
433
- elapsed = time.time() - start_time
434
- print(f"[이미지 생성] API 응답 받음 ({elapsed:.1f}초)")
435
-
436
- if output:
437
- if isinstance(output, str) and output.startswith('http'):
438
- print(f"[이미지 생성] URL에서 이미지 다운로드 중...")
439
- response = requests.get(output, timeout=30)
440
- img = Image.open(BytesIO(response.content))
441
- print(f"[이미지 생성] 완료!")
442
- return img, english_prompt
443
- else:
444
- print(f"[이미지 생성] 바이너리 데이터 처리 중...")
445
- img = Image.open(BytesIO(output.read()))
446
- print(f"[이미지 생성] 완료!")
447
- return img, english_prompt
448
- else:
449
- error_msg = "이미지 생성 실패 - 빈 응답"
450
- print(f"[이미지 생성] {error_msg}")
451
- return None, error_msg
452
-
453
- except Exception as e:
454
- error_msg = f"오류: {str(e)}"
455
- print(f"[이미지 생성] {error_msg}")
456
- print(f"[이미지 생성] 상세 오류:\n{traceback.format_exc()}")
457
- return None, error_msg
458
-
459
- def create_slide_preview_html(slide_data: Dict) -> str:
460
- """16:9 비율의 슬라이드 프리뷰 HTML 생성"""
461
-
462
- # 이미지를 base64로 인코딩
463
- img_base64 = ""
464
- if slide_data.get("image"):
465
- buffered = BytesIO()
466
- slide_data["image"].save(buffered, format="PNG")
467
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
468
-
469
- # 텍스트 내용 가져오기
470
- subtitle = slide_data.get("subtitle", "")
471
- bullet_points = slide_data.get("bullet_points", [])
472
-
473
- # HTML 생성
474
- html = f"""
475
- <div class="slide-container" style="
476
- width: 100%;
477
- max-width: 1200px;
478
- margin: 20px auto;
479
- background: white;
480
- border-radius: 8px;
481
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
482
- overflow: hidden;
483
- ">
484
- <div class="slide-header" style="
485
- background: #2c3e50;
486
- color: white;
487
- padding: 15px 30px;
488
- font-size: 18px;
489
- font-weight: bold;
490
- ">
491
- 슬라이드 {slide_data.get('slide_number', '')}: {slide_data.get('title', '')}
492
- </div>
493
-
494
- <div class="slide-content" style="
495
- display: flex;
496
- height: 0;
497
- padding-bottom: 56.25%; /* 16:9 비율 */
498
- position: relative;
499
- ">
500
- <div class="slide-inner" style="
501
- position: absolute;
502
- top: 0;
503
- left: 0;
504
- width: 100%;
505
- height: 100%;
506
- display: flex;
507
- ">
508
- <!-- 텍스트 영역 (좌측) -->
509
- <div class="text-area" style="
510
- flex: 1;
511
- padding: 40px;
512
- display: flex;
513
- flex-direction: column;
514
- justify-content: center;
515
- background: #f8f9fa;
516
- ">
517
- <h2 style="
518
- color: #2c3e50;
519
- font-size: 28px;
520
- margin-bottom: 30px;
521
- font-weight: 600;
522
- ">{subtitle}</h2>
523
-
524
- <ul style="
525
- list-style: none;
526
- padding: 0;
527
- margin: 0;
528
- ">
529
- """
530
-
531
- for point in bullet_points:
532
- html += f"""
533
- <li style="
534
- margin-bottom: 15px;
535
- padding-left: 25px;
536
- position: relative;
537
- color: #34495e;
538
- font-size: 16px;
539
- line-height: 1.6;
540
- ">
541
- <span style="
542
- position: absolute;
543
- left: 0;
544
- color: #3498db;
545
- ">▶</span>
546
- {point.replace('•', '').strip()}
547
- </li>
548
- """
549
-
550
- html += f"""
551
- </ul>
552
- </div>
553
-
554
- <!-- 이미지 영역 (우측) -->
555
- <div class="image-area" style="
556
- flex: 1;
557
- background: #e9ecef;
558
- display: flex;
559
- align-items: center;
560
- justify-content: center;
561
- padding: 20px;
562
- ">
563
- """
564
-
565
- if img_base64:
566
- html += f"""
567
- <img src="data:image/png;base64,{img_base64}" style="
568
- max-width: 100%;
569
- max-height: 100%;
570
- object-fit: contain;
571
- border-radius: 4px;
572
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
573
- " alt="Slide Image">
574
- """
575
- else:
576
- html += """
577
- <div style="
578
- color: #6c757d;
579
- text-align: center;
580
- ">
581
- <div style="font-size: 48px;">🖼️</div>
582
- <p>이미지 생성 중...</p>
583
- </div>
584
- """
585
-
586
- html += """
587
- </div>
588
- </div>
589
- </div>
590
- </div>
591
- """
592
-
593
- return html
594
-
595
- def generate_ppt_with_content(topic: str, template_name: str, custom_slides: List[Dict], seed: int, progress=gr.Progress()):
596
- """PPT 이미지와 텍스트 내용을 함께 생성"""
597
- results = []
598
- preview_html = ""
599
-
600
- # 템플릿 선택
601
- if template_name == "사용자 정의" and custom_slides:
602
- slides = custom_slides
603
- else:
604
- slides = PPT_TEMPLATES[template_name]["slides"]
605
-
606
- if not slides:
607
- yield "", "슬라이드가 정의되지 않았습니다."
608
- return
609
-
610
- total_slides = len(slides)
611
- print(f"\n[PPT 생성] 시작 - 총 {total_slides}개 슬라이드")
612
- print(f"[PPT 생성] 주제: {topic}")
613
- print(f"[PPT 생성] 템플릿: {template_name}")
614
-
615
- # CSS 스타일 추가
616
- preview_html = """
617
- <style>
618
- .slides-container {
619
- width: 100%;
620
- max-width: 1400px;
621
- margin: 0 auto;
622
- }
623
- </style>
624
- <div class="slides-container">
625
- """
626
-
627
- # 각 슬라이드 순차 처리
628
- for i, slide in enumerate(slides):
629
- progress((i + 1) / (total_slides + 1), f"슬라이드 {i+1}/{total_slides} 처리 중...")
630
-
631
- slide_info = f"슬라이드 {i+1}: {slide['title']}"
632
-
633
- # 텍스트 내용 생성
634
- slide_context = f"{slide['title']} - {slide.get('prompt_hint', '')}"
635
- content = generate_slide_content(topic, slide['title'], slide_context)
636
-
637
- # 프롬프트 생성 및 이미지 생성
638
- style_key = slide["style"]
639
- if style_key in STYLE_TEMPLATES:
640
- style_info = STYLE_TEMPLATES[style_key]
641
- prompt = generate_prompt_with_llm(topic, style_info["example"], slide_context)
642
-
643
- # 이미지 생성
644
- slide_seed = seed + i
645
- img, used_prompt = generate_image(prompt, slide_seed, slide_info)
646
-
647
- # 슬라이드 데이터 구성
648
- slide_data = {
649
- "slide_number": i + 1,
650
- "title": slide["title"],
651
- "subtitle": content["subtitle"],
652
- "bullet_points": content["bullet_points"],
653
- "image": img,
654
- "style": style_info["name"]
655
- }
656
-
657
- # 프리뷰 HTML 생성
658
- preview_html += create_slide_preview_html(slide_data)
659
-
660
- # 현재까지의 상태 업데이트
661
- yield preview_html + "</div>", f"### 🔄 {slide_info} 생성 중..."
662
-
663
- results.append({
664
- "slide_data": slide_data,
665
- "success": img is not None
666
- })
667
-
668
- # 최종 결과
669
- preview_html += "</div>"
670
- progress(1.0, "완료!")
671
- successful = sum(1 for r in results if r["success"])
672
- final_status = f"### 🎉 생성 완료! 총 {total_slides}개 슬라이드 중 {successful}개 성공"
673
-
674
- yield preview_html, final_status
675
-
676
- def create_custom_slides_ui():
677
- """사용자 정의 슬라이드 구성 UI"""
678
- slides = []
679
- for i in range(10):
680
- with gr.Row():
681
- with gr.Column(scale=2):
682
- title = gr.Textbox(
683
- label=f"슬라이드 {i+1} 제목",
684
- placeholder="예: 표지, 목차, 현황 분석...",
685
- visible=(i < 3)
686
- )
687
- with gr.Column(scale=3):
688
- style = gr.Dropdown(
689
- choices=list(STYLE_TEMPLATES.keys()),
690
- label=f"스타일 선택",
691
- visible=(i < 3)
692
- )
693
- with gr.Column(scale=3):
694
- hint = gr.Textbox(
695
- label=f"프롬프트 힌트",
696
- placeholder="이 슬라이드에서 표현하고 싶은 내용",
697
- visible=(i < 3)
698
- )
699
- slides.append({"title": title, "style": style, "hint": hint})
700
- return slides
701
-
702
- # Gradio 인터페이스 생성
703
- with gr.Blocks(title="PPT 이미지 생성기", theme=gr.themes.Soft(), css="""
704
- .preview-container { max-width: 1400px; margin: 0 auto; }
705
- """) as demo:
706
- gr.Markdown("""
707
- # 🎯 AI 기반 PPT 통합 생성기
708
-
709
- ### 텍스트와 이미지가 완벽하게 조화된 프레젠테이션을 자동으로 생성합니다!
710
- """)
711
-
712
- # API 토큰 상태 확인
713
- if not REPLICATE_API_TOKEN:
714
- gr.Markdown("⚠️ **경고**: RAPI_TOKEN 환경 변수가 설정되지 않았습니다.")
715
- if not FRIENDLI_TOKEN:
716
- gr.Markdown("⚠️ **경고**: FRIENDLI_TOKEN 환경 변수가 설정되지 않았습니다.")
717
-
718
- with gr.Row():
719
- with gr.Column(scale=1):
720
- # 기본 입력
721
- topic_input = gr.Textbox(
722
- label="프레젠테이션 주제",
723
- placeholder="예: AI 스타트업 투자 유치, 신제품 런칭, 디지털 전환 전략",
724
- lines=2
725
- )
726
-
727
- # PPT 템플릿 선택
728
- template_select = gr.Dropdown(
729
- choices=list(PPT_TEMPLATES.keys()),
730
- label="PPT 템플릿 선택",
731
- value="비즈니스 제안서",
732
- info="목적에 맞는 템플릿을 선택하세요"
733
- )
734
-
735
- # 템플릿 설명
736
- template_info = gr.Markdown()
737
-
738
- # 시드 값
739
- seed_input = gr.Slider(
740
- minimum=1,
741
- maximum=100,
742
- value=10,
743
- step=1,
744
- label="시드 값"
745
- )
746
-
747
- generate_btn = gr.Button("🚀 PPT 전체 생성 (텍스트 + 이미지)", variant="primary", size="lg")
748
-
749
- # 사용자 정의 섹션
750
- with gr.Accordion("📝 사용자 정의 슬라이드 구성", open=False) as custom_accordion:
751
- gr.Markdown("템플릿을 사용하지 않고 직접 슬라이드를 구성하세요.")
752
- custom_slides_components = create_custom_slides_ui()
753
-
754
- # 상태 표시
755
- status_output = gr.Markdown(
756
- value="### 👆 템플릿을 선택하고 생성 버튼을 클릭하세요!"
757
- )
758
-
759
- # 프리뷰 영역
760
- preview_output = gr.HTML(
761
- label="PPT 프리뷰 (16:9)",
762
- elem_classes="preview-container"
763
- )
764
-
765
- # 활용 팁
766
- gr.Markdown("""
767
- ---
768
- ### 💡 새로운 기능:
769
-
770
- 1. **자동 텍스트 생성**: 각 슬라이드마다 적절한 제목과 5개의 핵심 포인트 자동 생성
771
- 2. **16:9 프리뷰**: 실제 PPT와 동일한 비율로 미리보기
772
- 3. **좌우 레이아웃**: 좌측 텍스트, 우측 이미지로 깔끔한 구성
773
- 4. **실시간 업데이트**: 각 슬라이드가 생성될 때마다 즉시 확인
774
-
775
- ### 📌 활용 팁:
776
- - 생성된 내용을 복사하여 실제 PPT에 바로 사용 가능
777
- - 각 슬라이드의 텍스트와 이미지가 주제에 맞게 자동 조율
778
- - 한글 주제 입력 시 한글로 텍스트 생성
779
- """)
780
-
781
- # 이벤트 핸들러
782
- def update_template_info(template_name):
783
- if template_name in PPT_TEMPLATES:
784
- template = PPT_TEMPLATES[template_name]
785
- info = f"**{template['description']}**\n\n포함된 슬라이드:\n"
786
- for i, slide in enumerate(template['slides']):
787
- info += f"{i+1}. {slide['title']} - {STYLE_TEMPLATES[slide['style']]['use_case']}\n"
788
- return info
789
- return ""
790
-
791
- def generate_ppt_handler(topic, template_name, seed, progress=gr.Progress(), *custom_inputs):
792
- if not topic.strip():
793
- yield "", "❌ 주제를 입력해주세요."
794
- return
795
-
796
- # 사용자 정의 슬라이드 처리
797
- custom_slides = []
798
- if template_name == "사용자 정의":
799
- for i in range(0, len(custom_inputs), 3):
800
- title = custom_inputs[i]
801
- style = custom_inputs[i+1] if i+1 < len(custom_inputs) else None
802
- hint = custom_inputs[i+2] if i+2 < len(custom_inputs) else ""
803
-
804
- if title and style:
805
- custom_slides.append({
806
- "title": title,
807
- "style": style,
808
- "prompt_hint": hint
809
- })
810
-
811
- # PPT 생성
812
- for preview, status in generate_ppt_with_content(topic, template_name, custom_slides, seed, progress):
813
- yield preview, status
814
-
815
- # 이벤트 연결
816
- template_select.change(
817
- fn=update_template_info,
818
- inputs=[template_select],
819
- outputs=[template_info]
820
- )
821
-
822
- # 사용자 정의 입력 수집
823
- all_custom_inputs = []
824
- for slide_components in custom_slides_components:
825
- all_custom_inputs.extend([
826
- slide_components["title"],
827
- slide_components["style"],
828
- slide_components["hint"]
829
- ])
830
-
831
- generate_btn.click(
832
- fn=generate_ppt_handler,
833
- inputs=[topic_input, template_select, seed_input] + all_custom_inputs,
834
- outputs=[preview_output, status_output]
835
- )
836
-
837
- # 초기 템플릿 정보 표시
838
- demo.load(
839
- fn=update_template_info,
840
- inputs=[template_select],
841
- outputs=[template_info]
842
- )
843
-
844
- # 앱 실행
845
- if __name__ == "__main__":
846
- print("\n" + "="*50)
847
- print("🚀 PPT 통합 생성기 시작!")
848
- print("="*50)
849
-
850
- # 환경 변수 확인
851
- if not REPLICATE_API_TOKEN:
852
- print("⚠️ 경고: RAPI_TOKEN 환경 변수가 설정되지 않았습니다.")
853
- else:
854
- print("✅ RAPI_TOKEN 확인됨")
855
-
856
- if not FRIENDLI_TOKEN:
857
- print("⚠️ 경고: FRIENDLI_TOKEN 환경 변수가 설정되지 않았습니다.")
858
- else:
859
- print("✅ FRIENDLI_TOKEN 확인됨")
860
-
861
- print("="*50 + "\n")
862
-
863
- demo.launch()