aliceblue11 commited on
Commit
b8b24ec
·
verified ·
1 Parent(s): 01bb4c0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +537 -0
app.py ADDED
@@ -0,0 +1,537 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 메인 그라디오 애플리케이션
3
+ 이벤트 공지사항 자동 생성기
4
+ """
5
+
6
+ import gradio as gr
7
+ import json
8
+ import tempfile
9
+ from datetime import datetime
10
+ from PIL import Image
11
+ from typing import Dict, List, Any, Optional
12
+
13
+ # 모듈 import
14
+ from modules.ai_analyzer import MonthlyConceptAnalyzer, EventNoticeGenerator
15
+ from modules.image_analyzer import ImageAnalyzer, format_image_analysis_result
16
+ from modules.design_guide import DesignGuideGenerator
17
+ from modules.utils import EventUtils, ValidationUtils, TextUtils
18
+
19
+ class EventGeneratorApp:
20
+ """이벤트 생성기 메인 애플리케이션"""
21
+
22
+ def __init__(self):
23
+ self.concept_analyzer = MonthlyConceptAnalyzer()
24
+ self.notice_generator = EventNoticeGenerator()
25
+ self.image_analyzer = ImageAnalyzer()
26
+ self.design_generator = DesignGuideGenerator()
27
+
28
+ # 애플리케이션 상태
29
+ self.current_concepts = []
30
+ self.selected_concept = None
31
+ self.current_image_analysis = None
32
+
33
+ def create_interface(self):
34
+ """그라디오 인터페이스 생성"""
35
+
36
+ with gr.Blocks(
37
+ theme=gr.themes.Soft(),
38
+ title="스마트 이벤트 공지사항 생성기",
39
+ css="""
40
+ .main-header {
41
+ text-align: center;
42
+ padding: 20px;
43
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
44
+ color: white;
45
+ border-radius: 10px;
46
+ margin-bottom: 20px;
47
+ }
48
+ .input-section {
49
+ background: #f8f9fa;
50
+ padding: 20px;
51
+ border-radius: 10px;
52
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
53
+ }
54
+ .output-section {
55
+ background: white;
56
+ padding: 20px;
57
+ border-radius: 10px;
58
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
59
+ }
60
+ .concept-card {
61
+ background: #e3f2fd;
62
+ padding: 15px;
63
+ border-radius: 8px;
64
+ margin: 10px 0;
65
+ border-left: 4px solid #2196f3;
66
+ }
67
+ .success-message {
68
+ background: #d4edda;
69
+ color: #155724;
70
+ padding: 10px;
71
+ border-radius: 5px;
72
+ border: 1px solid #c3e6cb;
73
+ }
74
+ .error-message {
75
+ background: #f8d7da;
76
+ color: #721c24;
77
+ padding: 10px;
78
+ border-radius: 5px;
79
+ border: 1px solid #f5c6cb;
80
+ }
81
+ """
82
+ ) as demo:
83
+
84
+ # 헤더
85
+ with gr.Column(elem_classes="main-header"):
86
+ gr.Markdown("""
87
+ # 🎉 AI 이벤트 공지사항 생성기
88
+ ### 실시간 트렌드 분석으로 완벽한 이벤트 기획을 도와드립니다
89
+ """)
90
+
91
+ with gr.Row():
92
+ # 좌측: 입력 영역
93
+ with gr.Column(scale=1, elem_classes="input-section"):
94
+
95
+ gr.Markdown("## ⚙️ 기본 설정")
96
+
97
+ year_input = gr.Number(
98
+ value=2025,
99
+ label="📅 연도",
100
+ minimum=2024,
101
+ maximum=2030,
102
+ step=1,
103
+ info="이벤트를 진행할 연도를 선택해주세요"
104
+ )
105
+
106
+ month_input = gr.Dropdown(
107
+ choices=[f"{i}월" for i in range(1, 13)],
108
+ label="📅 이벤트 월 선택",
109
+ value=f"{datetime.now().month}월",
110
+ info="이벤트를 진행할 월을 선택해주세요"
111
+ )
112
+
113
+ analyze_concepts_btn = gr.Button(
114
+ "🧠 AI 컨셉 분석 시작",
115
+ variant="secondary",
116
+ size="lg"
117
+ )
118
+
119
+ # 컨셉 선택 영역 (초기에는 숨김)
120
+ with gr.Group(visible=False) as concept_selection_group:
121
+ concept_selector = gr.Dropdown(
122
+ label="🎨 추천 컨셉 선택",
123
+ interactive=True,
124
+ info="AI가 분석한 컨셉 중 하나를 선택해주세요"
125
+ )
126
+
127
+ concept_preview = gr.Textbox(
128
+ label="📊 선택된 컨셉 미리보기",
129
+ lines=6,
130
+ interactive=False
131
+ )
132
+
133
+ gr.Markdown("## 🎯 이벤트 설정")
134
+
135
+ event_type_selector = gr.Dropdown(
136
+ choices=[
137
+ "댓글 달기 이벤트",
138
+ "게시글 작성 이벤트",
139
+ "좋아요/공감 이벤트",
140
+ "출석체크 이벤트",
141
+ "추천인 이벤트",
142
+ "리뷰/후기 이벤트",
143
+ "사진 업로드 이벤트",
144
+ "퀴즈/설문 이벤트",
145
+ "직접 입력"
146
+ ],
147
+ label="🎯 이벤트 유형 선택",
148
+ value="댓글 달기 이벤트",
149
+ info="진행하고 싶은 이벤트 유형을 선택해주세요"
150
+ )
151
+
152
+ custom_event_input = gr.Textbox(
153
+ label="✏️ 커스텀 이벤트 상세 설명",
154
+ placeholder="'직접 입력' 선택시 원하는 이벤트 내용을 자세히 설명해주세요",
155
+ visible=False,
156
+ lines=4
157
+ )
158
+
159
+ gr.Markdown("## 📸 레퍼런스 분석 (선택사항)")
160
+
161
+ reference_image_input = gr.Image(
162
+ label="레퍼런스 이미지 업로드",
163
+ type="pil",
164
+ height=250,
165
+ info="기존 디자인이나 참고하고 싶은 이미지를 업로드하면 분석해서 반영합니다"
166
+ )
167
+
168
+ # 생성 버튼 (초기에는 숨김)
169
+ generate_notice_btn = gr.Button(
170
+ "✨ 완성된 공지사항 생성하기",
171
+ variant="primary",
172
+ size="lg",
173
+ visible=False
174
+ )
175
+
176
+ # 우측: 결과 출력 영역
177
+ with gr.Column(scale=2, elem_classes="output-section"):
178
+
179
+ with gr.Tabs() as output_tabs:
180
+
181
+ with gr.TabItem("🧠 AI 컨셉 분석 결과"):
182
+ concept_analysis_output = gr.Markdown(
183
+ value="👆 먼저 좌측에서 연도와 월을 선택한 후 'AI 컨셉 분석 시작' 버튼을 클릭해주세요",
184
+ elem_classes="concept-card"
185
+ )
186
+
187
+ with gr.TabItem("📝 완성된 공지사항"):
188
+ final_notice_output = gr.Textbox(
189
+ label="생성된 이벤트 공지사항",
190
+ lines=30,
191
+ placeholder="컨셉 선택 후 '완성된 공지사항 생성하기' 버튼을 클릭하면 결과가 나타납니다",
192
+ show_copy_button=True,
193
+ max_lines=50
194
+ )
195
+
196
+ with gr.Row():
197
+ download_txt_btn = gr.DownloadButton(
198
+ "💾 텍스트 파일로 다운로드",
199
+ size="sm",
200
+ variant="secondary",
201
+ visible=False
202
+ )
203
+ regenerate_btn = gr.Button(
204
+ "🔄 다시 생성하기",
205
+ size="sm",
206
+ visible=False
207
+ )
208
+
209
+ # 텍스트 분석 정보
210
+ text_stats = gr.Textbox(
211
+ label="📊 공지사항 통계",
212
+ lines=3,
213
+ interactive=False,
214
+ visible=False
215
+ )
216
+
217
+ with gr.TabItem("🎨 디자인 가이드"):
218
+ design_guide_output = gr.Markdown(
219
+ value="컨셉 분석 완료 후 이 탭에서 디자인 가이드를 확인할 수 있습니다"
220
+ )
221
+
222
+ color_palette_display = gr.HTML(
223
+ label="추천 컬러 팔레트",
224
+ visible=False
225
+ )
226
+
227
+ with gr.TabItem("📊 레퍼런스 이미지 분석"):
228
+ image_analysis_output = gr.Textbox(
229
+ label="업로드된 이미지 분석 결과",
230
+ lines=15,
231
+ placeholder="레퍼런스 이미지를 업로드하면 자동으로 분석 결과가 나타납니다",
232
+ interactive=False
233
+ )
234
+
235
+ extracted_elements_output = gr.JSON(
236
+ label="추출된 디자인 요소들",
237
+ visible=False
238
+ )
239
+
240
+ # 상태 관리용 변수들
241
+ concepts_state = gr.State([])
242
+ selected_concept_state = gr.State(None)
243
+ image_analysis_state = gr.State(None)
244
+
245
+ # 이벤트 핸들러 함수들
246
+ def handle_concept_analysis(year, month):
247
+ """AI 컨셉 분석 처리"""
248
+ try:
249
+ # 월 번호 추출
250
+ month_num = int(month.replace('월', ''))
251
+
252
+ # AI 컨셉 분석 실행
253
+ concepts = self.concept_analyzer.analyze_monthly_concepts(month_num, year)
254
+ self.current_concepts = concepts
255
+
256
+ # 컨셉 선택 드롭다운 업데이트
257
+ concept_choices = [f"{concept['name']} - {concept['theme']}" for concept in concepts]
258
+
259
+ # 분석 결과 마크다운 생성
260
+ analysis_md = f"# 🎯 {year}년 {month} AI 분석 결과\n\n"
261
+ analysis_md += f"**분석 완료 시간:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
262
+
263
+ for i, concept in enumerate(concepts, 1):
264
+ colors_preview = " ".join([f"<span style='background:{color}; padding:2px 8px; border-radius:3px; color:white; font-size:10px;'>{color}</span>" for color in concept['colors'][:3]])
265
+
266
+ analysis_md += f"""
267
+ ## {i}. {concept['name']}
268
+ **테마:** {concept['theme']}
269
+ **어필 포인트:** {concept['target_appeal']}
270
+ **예상 참여도:** ⭐ {concept['participation_score']}/10점
271
+ **추천 이벤트:** {concept['event_style']}
272
+ **색상 팔레트:** {colors_preview}
273
+ **선정 이유:** {concept['reason']}
274
+
275
+ ---
276
+ """
277
+
278
+ return (
279
+ gr.update(visible=True), # concept_selection_group
280
+ gr.update(choices=concept_choices, value=concept_choices[0] if concept_choices else None), # concept_selector
281
+ gr.update(visible=True), # generate_notice_btn
282
+ analysis_md, # concept_analysis_output
283
+ concepts # concepts_state
284
+ )
285
+
286
+ except Exception as e:
287
+ error_message = f"❌ 컨셉 분석 중 오류가 발생했습니다: {str(e)}"
288
+ return (
289
+ gr.update(visible=False),
290
+ gr.update(choices=[], value=None),
291
+ gr.update(visible=False),
292
+ error_message,
293
+ []
294
+ )
295
+
296
+ def handle_concept_selection(selected_concept_name, concepts_data):
297
+ """컨셉 선택 처리"""
298
+ try:
299
+ if selected_concept_name and concepts_data:
300
+ # 선택된 컨셉 찾기
301
+ selected_idx = next(i for i, c in enumerate(concepts_data)
302
+ if f"{c['name']} - {c['theme']}" == selected_concept_name)
303
+ selected_concept = concepts_data[selected_idx]
304
+ self.selected_concept = selected_concept
305
+
306
+ # 미리보기 텍스트 생성
307
+ preview_text = f"""🎨 컨셉: {selected_concept['name']}
308
+ 🏷️ 테마: {selected_concept['theme']}
309
+ 💬 캐치프레이즈: {selected_concept['catchphrase']}
310
+ 🎯 타겟 어필: {selected_concept['target_appeal']}
311
+ ⭐ 예상 참여도: {selected_concept['participation_score']}/10점
312
+ 🎪 추천 이벤트: {selected_concept['event_style']}
313
+ 🎨 색상: {', '.join(selected_concept['colors'][:3])}
314
+ 💡 차별화: {selected_concept['competitive_edge']}"""
315
+
316
+ return preview_text, selected_concept
317
+ return "", None
318
+ except Exception as e:
319
+ return f"❌ 컨셉 선택 오류: {str(e)}", None
320
+
321
+ def handle_event_type_change(event_type):
322
+ """이벤트 유형 변경 처리"""
323
+ return gr.update(visible=(event_type == "직접 입력"))
324
+
325
+ def handle_image_upload(image):
326
+ """이미지 업로드 처리"""
327
+ try:
328
+ if image:
329
+ analysis = self.image_analyzer.analyze_reference_image(image)
330
+ self.current_image_analysis = analysis
331
+ formatted_result = format_image_analysis_result(analysis)
332
+
333
+ return (
334
+ formatted_result, # image_analysis_output
335
+ gr.update(visible=True, value=analysis), # extracted_elements_output
336
+ analysis # image_analysis_state
337
+ )
338
+ return "", gr.update(visible=False), None
339
+ except Exception as e:
340
+ error_msg = f"❌ 이미지 분석 오류: {str(e)}"
341
+ return error_msg, gr.update(visible=False), None
342
+
343
+ def handle_final_generation(year, month, selected_concept, event_type, custom_event, image_analysis):
344
+ """최종 공지사항 생성 처리"""
345
+ try:
346
+ if not selected_concept:
347
+ return (
348
+ "❌ 먼저 컨셉을 선택해주세요!",
349
+ "",
350
+ gr.update(visible=False),
351
+ gr.update(visible=False),
352
+ gr.update(visible=False),
353
+ ""
354
+ )
355
+
356
+ # 최종 공지사항 생성
357
+ notice = self.notice_generator.generate_event_notice(
358
+ concept_data=selected_concept,
359
+ event_type=event_type,
360
+ custom_event=custom_event,
361
+ reference_analysis=image_analysis
362
+ )
363
+
364
+ # 디자인 가이드 생성
365
+ design_guide = self.design_generator.generate_design_guidelines(
366
+ selected_concept, image_analysis
367
+ )
368
+
369
+ # 색상 팔레트 HTML 생성
370
+ color_html = self.design_generator.generate_color_palette_html(
371
+ selected_concept.get('colors', [])
372
+ )
373
+
374
+ # 텍스트 통계 생성
375
+ stats = TextUtils.count_characters(notice)
376
+ stats_text = f"총 글자수: {stats['total_chars']:,}자 | 단어수: {stats['words']:,}개 | 줄수: {stats['lines']:,}줄"
377
+
378
+ # 파일 다운로드 준비
379
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
380
+ filename = f"event_notice_{timestamp}.txt"
381
+
382
+ return (
383
+ notice, # final_notice_output
384
+ design_guide, # design_guide_output
385
+ gr.update(visible=True, value=color_html), # color_palette_display
386
+ gr.update(visible=True), # download_txt_btn
387
+ gr.update(visible=True), # regenerate_btn
388
+ stats_text # text_stats
389
+ )
390
+
391
+ except Exception as e:
392
+ error_message = f"❌ 공지사항 생성 중 오류가 발생했습니다: {str(e)}"
393
+ return (
394
+ error_message,
395
+ "",
396
+ gr.update(visible=False),
397
+ gr.update(visible=False),
398
+ gr.update(visible=False),
399
+ ""
400
+ )
401
+
402
+ def handle_download_preparation(notice_content):
403
+ """다운로드 파일 준비"""
404
+ try:
405
+ if notice_content and notice_content.strip():
406
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
407
+ filename = f"event_notice_{timestamp}.txt"
408
+
409
+ # 임시 파일 생성
410
+ temp_file = tempfile.NamedTemporaryFile(
411
+ mode='w',
412
+ suffix='.txt',
413
+ delete=False,
414
+ encoding='utf-8'
415
+ )
416
+ temp_file.write(notice_content)
417
+ temp_file.close()
418
+
419
+ return temp_file.name
420
+ return None
421
+ except Exception as e:
422
+ print(f"파일 준비 오류: {e}")
423
+ return None
424
+
425
+ def handle_regeneration(year, month, selected_concept, event_type, custom_event, image_analysis):
426
+ """공지사항 재생성"""
427
+ return handle_final_generation(year, month, selected_concept, event_type, custom_event, image_analysis)
428
+
429
+ # 이벤트 바인딩
430
+ analyze_concepts_btn.click(
431
+ handle_concept_analysis,
432
+ inputs=[year_input, month_input],
433
+ outputs=[
434
+ concept_selection_group,
435
+ concept_selector,
436
+ generate_notice_btn,
437
+ concept_analysis_output,
438
+ concepts_state
439
+ ]
440
+ )
441
+
442
+ concept_selector.change(
443
+ handle_concept_selection,
444
+ inputs=[concept_selector, concepts_state],
445
+ outputs=[concept_preview, selected_concept_state]
446
+ )
447
+
448
+ event_type_selector.change(
449
+ handle_event_type_change,
450
+ inputs=[event_type_selector],
451
+ outputs=[custom_event_input]
452
+ )
453
+
454
+ reference_image_input.change(
455
+ handle_image_upload,
456
+ inputs=[reference_image_input],
457
+ outputs=[image_analysis_output, extracted_elements_output, image_analysis_state]
458
+ )
459
+
460
+ generate_notice_btn.click(
461
+ handle_final_generation,
462
+ inputs=[
463
+ year_input,
464
+ month_input,
465
+ selected_concept_state,
466
+ event_type_selector,
467
+ custom_event_input,
468
+ image_analysis_state
469
+ ],
470
+ outputs=[
471
+ final_notice_output,
472
+ design_guide_output,
473
+ color_palette_display,
474
+ download_txt_btn,
475
+ regenerate_btn,
476
+ text_stats
477
+ ]
478
+ )
479
+
480
+ regenerate_btn.click(
481
+ handle_regeneration,
482
+ inputs=[
483
+ year_input,
484
+ month_input,
485
+ selected_concept_state,
486
+ event_type_selector,
487
+ custom_event_input,
488
+ image_analysis_state
489
+ ],
490
+ outputs=[
491
+ final_notice_output,
492
+ design_guide_output,
493
+ color_palette_display,
494
+ download_txt_btn,
495
+ regenerate_btn,
496
+ text_stats
497
+ ]
498
+ )
499
+
500
+ # 다운로드 버튼 클릭 이벤트
501
+ download_txt_btn.click(
502
+ handle_download_preparation,
503
+ inputs=[final_notice_output],
504
+ outputs=[]
505
+ )
506
+
507
+ return demo
508
+
509
+ def main():
510
+ """메인 실행 함수"""
511
+ print("🎉 이벤트 공지사항 생성기를 시작합니다...")
512
+
513
+ try:
514
+ # 애플리케이션 초기화
515
+ app = EventGeneratorApp()
516
+
517
+ # 그라디오 인터페이스 생성
518
+ demo = app.create_interface()
519
+
520
+ # 서버 실행
521
+ print("🚀 서버를 시작합니다...")
522
+ demo.launch(
523
+ server_name="0.0.0.0",
524
+ server_port=7860,
525
+ share=True,
526
+ debug=True,
527
+ show_tips=True,
528
+ show_error=True
529
+ )
530
+
531
+ except Exception as e:
532
+ print(f"❌ 애플리케이션 시작 중 오류 발생: {e}")
533
+ import traceback
534
+ traceback.print_exc()
535
+
536
+ if __name__ == "__main__":
537
+ main()