aliceblue11 commited on
Commit
c21e695
·
verified ·
1 Parent(s): 06f68dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +870 -475
app.py CHANGED
@@ -1,539 +1,694 @@
1
  """
2
- 메인 그라디오 애플리케이션
3
- 이벤트 공지사항 자동 생성기
4
  """
5
 
6
  import gradio as gr
 
 
7
  import json
8
  import tempfile
9
- import sys
10
  import os
11
- from datetime import datetime
12
- from PIL import Image
13
- from typing import Dict, List, Any, Optional
14
-
15
- # 현재 디렉토리를 Python 경로에 추가
16
- current_dir = os.path.dirname(os.path.abspath(__file__))
17
- sys.path.append(current_dir)
18
-
19
- # 모듈 import (절대 경로로 수정)
20
- try:
21
- from modules.ai_analyzer import MonthlyConceptAnalyzer, EventNoticeGenerator
22
- from modules.image_analyzer import ImageAnalyzer, format_image_analysis_result
23
- from modules.design_guide import DesignGuideGenerator
24
- from modules.utils import EventUtils, ValidationUtils, TextUtils
25
- except ImportError:
26
- # modules 폴더가 없는 경우 직접 정의된 클래스들 사용
27
- import warnings
28
- warnings.warn("modules 폴더를 찾을 수 없습니다. 내장 클래스를 사용합니다.")
29
-
30
- # 필요한 클래스들을 여기에 직접 정의
31
- from datetime import datetime
32
- import random
33
- import cv2
34
- import numpy as np
35
- from sklearn.cluster import KMeans
36
-
37
- class EventGeneratorApp:
38
- """이벤트 생성기 메인 애플리케이션"""
39
 
40
  def __init__(self):
41
- self.concept_analyzer = MonthlyConceptAnalyzer()
42
- self.notice_generator = EventNoticeGenerator()
43
- self.image_analyzer = ImageAnalyzer()
44
- self.design_generator = DesignGuideGenerator()
45
-
46
- # 애플리케이션 상태
47
- self.current_concepts = []
48
- self.selected_concept = None
49
- self.current_image_analysis = None
50
-
51
- def create_interface(self):
52
- """그라디오 인터페이스 생성"""
53
-
54
- with gr.Blocks(
55
- theme=gr.themes.Soft(),
56
- title="스마트 이벤트 공지사항 생성기",
57
- css="""
58
- .main-header {
59
- text-align: center;
60
- padding: 20px;
61
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
62
- color: white;
63
- border-radius: 10px;
64
- margin-bottom: 20px;
65
- }
66
- .input-section {
67
- background: #f8f9fa;
68
- padding: 20px;
69
- border-radius: 10px;
70
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
71
- }
72
- .output-section {
73
- background: white;
74
- padding: 20px;
75
- border-radius: 10px;
76
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
77
- }
78
- .concept-card {
79
- background: #e3f2fd;
80
- padding: 15px;
81
- border-radius: 8px;
82
- margin: 10px 0;
83
- border-left: 4px solid #2196f3;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
- .success-message {
86
- background: #d4edda;
87
- color: #155724;
88
- padding: 10px;
89
- border-radius: 5px;
90
- border: 1px solid #c3e6cb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
- .error-message {
93
- background: #f8d7da;
94
- color: #721c24;
95
- padding: 10px;
96
- border-radius: 5px;
97
- border: 1px solid #f5c6cb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  }
99
- """
100
- ) as demo:
101
-
102
- # 헤더
103
- with gr.Column(elem_classes="main-header"):
104
- gr.Markdown("""
105
- # 🎉 AI 이벤트 공지사항 생성기
106
- ### 실시간 트렌드 분석으로 완벽한 이벤트 기획을 도와드립니다
107
- """)
108
-
109
- with gr.Row():
110
- # 좌측: 입력 영역
111
- with gr.Column(scale=1, elem_classes="input-section"):
112
-
113
- gr.Markdown("## ⚙️ 기본 설정")
114
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  year_input = gr.Number(
116
  value=2025,
117
  label="📅 연도",
118
  minimum=2024,
119
  maximum=2030,
120
- step=1,
121
- info="이벤트를 진행할 연도를 선택해주세요"
122
  )
123
 
124
  month_input = gr.Dropdown(
125
  choices=[f"{i}월" for i in range(1, 13)],
126
  label="📅 이벤트 월 선택",
127
- value=f"{datetime.now().month}월",
128
- info="이벤트를 진행할 월을 선택해주세요"
129
- )
130
-
131
- analyze_concepts_btn = gr.Button(
132
- "🧠 AI 컨셉 분석 시작",
133
- variant="secondary",
134
- size="lg"
135
- )
136
-
137
- # 컨셉 선택 영역 (초기에는 숨김)
138
- with gr.Group(visible=False) as concept_selection_group:
139
- concept_selector = gr.Dropdown(
140
- label="🎨 추천 컨셉 선택",
141
- interactive=True,
142
- info="AI가 분석한 컨셉 중 하나를 선택해주세요"
143
- )
144
-
145
- concept_preview = gr.Textbox(
146
- label="📊 선택된 컨셉 미리보기",
147
- lines=6,
148
- interactive=False
149
- )
150
-
151
- gr.Markdown("## 🎯 이벤트 설정")
152
-
153
- event_type_selector = gr.Dropdown(
154
- choices=[
155
- "댓글 달기 이벤트",
156
- "게시글 작성 이벤트",
157
- "좋아요/공감 이벤트",
158
- "출석체크 이벤트",
159
- "추천인 이벤트",
160
- "리뷰/후기 이벤트",
161
- "사진 업로드 이벤트",
162
- "퀴즈/설문 이벤트",
163
- "직접 입력"
164
- ],
165
- label="🎯 이벤트 유형 선택",
166
- value="댓글 달기 이벤트",
167
- info="진행하고 싶은 이벤트 유형을 선택해주세요"
168
- )
169
-
170
- custom_event_input = gr.Textbox(
171
- label="✏️ 커스텀 이벤트 상세 설명",
172
- placeholder="'직접 입력' 선택시 원하는 이벤트 내용을 자세히 설명해주세요",
173
- visible=False,
174
- lines=4
175
  )
 
 
 
 
 
 
 
 
 
 
176
 
177
- gr.Markdown("## 📸 레퍼런스 분석 (선택사항)")
178
-
179
- reference_image_input = gr.Image(
180
- label="레퍼런스 이미지 업로드",
181
- type="pil",
182
- height=250,
183
- info="기존 디자인이나 참고하고 싶은 이미지를 업로드하면 분석해서 반영합니다"
184
  )
185
 
186
- # 생성 버튼 (초기에는 숨김)
187
- generate_notice_btn = gr.Button(
188
- "✨ 완성된 공지사항 생성하기",
189
- variant="primary",
190
- size="lg",
191
- visible=False
192
  )
193
 
194
- # 우측: 결과 출력 영역
195
- with gr.Column(scale=2, elem_classes="output-section"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- with gr.Tabs() as output_tabs:
198
-
199
- with gr.TabItem("🧠 AI 컨셉 분석 결과"):
200
- concept_analysis_output = gr.Markdown(
201
- value="👆 먼저 좌측에서 연도와 월을 선택한 후 'AI 컨셉 분석 시작' 버튼을 클릭해주세요",
202
- elem_classes="concept-card"
203
- )
 
 
 
 
 
 
 
204
 
205
- with gr.TabItem("📝 완성된 공지사항"):
206
- final_notice_output = gr.Textbox(
207
- label="생성된 이벤트 공지사항",
208
- lines=30,
209
- placeholder="컨셉 선택 후 '완성된 공지사항 생성하기' 버튼을 클릭하면 결과가 나타납니다",
210
- show_copy_button=True,
211
- max_lines=50
212
- )
213
-
214
- with gr.Row():
215
- download_txt_btn = gr.DownloadButton(
216
- "💾 텍스트 파일로 다운로드",
217
- size="sm",
218
- variant="secondary",
219
- visible=False
220
- )
221
- regenerate_btn = gr.Button(
222
- "🔄 다시 생성하기",
223
- size="sm",
224
- visible=False
225
- )
226
-
227
- # 텍스트 분석 정보
228
- text_stats = gr.Textbox(
229
- label="📊 공지사항 통계",
230
- lines=3,
231
  interactive=False,
232
- visible=False
 
233
  )
234
 
235
- with gr.TabItem("🎨 디자인 가이드"):
236
- design_guide_output = gr.Markdown(
237
- value="컨셉 분석 완료 후 이 탭에서 디자인 가이드를 확인할 수 있습니다"
238
- )
239
-
240
- color_palette_display = gr.HTML(
241
- label="추천 컬러 팔레트",
242
  visible=False
243
  )
244
-
245
- with gr.TabItem("📊 레퍼런스 이미지 분석"):
246
- image_analysis_output = gr.Textbox(
247
- label="업로드된 이미지 분석 결과",
248
- lines=15,
249
- placeholder="레퍼런스 이미지를 업로드하면 자동으로 분석 결과가 나타납니다",
250
- interactive=False
251
- )
252
-
253
- extracted_elements_output = gr.JSON(
254
- label="추출된 디자인 요소들",
255
  visible=False
256
  )
257
-
258
- # 상태 관리용 변수들
259
- concepts_state = gr.State([])
260
- selected_concept_state = gr.State(None)
261
- image_analysis_state = gr.State(None)
262
-
263
- # 이벤트 핸들러 함수들
264
- def handle_concept_analysis(year, month):
265
- """AI 컨셉 분석 처리"""
266
- try:
267
- # 월 번호 추출
268
- month_num = int(month.replace('월', ''))
269
-
270
- # AI 컨셉 분석 실행
271
- concepts = self.concept_analyzer.analyze_monthly_concepts(month_num, year)
272
- self.current_concepts = concepts
273
-
274
- # 컨셉 선택 드롭다운 업데이트
275
- concept_choices = [f"{concept['name']} - {concept['theme']}" for concept in concepts]
276
 
277
- # 분석 결과 마크다운 생성
278
- analysis_md = f"# 🎯 {year}년 {month} AI 분석 결과\n\n"
279
- analysis_md += f"**분석 완료 시간:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
- for i, concept in enumerate(concepts, 1):
282
- 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]])
283
-
284
- analysis_md += f"""
285
  ## {i}. {concept['name']}
286
- **테마:** {concept['theme']}
287
- **어필 포인트:** {concept['target_appeal']}
288
- **예상 참여도:** {concept['participation_score']}/10점
289
- **추천 이벤트:** {concept['event_style']}
290
- **색상 팔레트:** {colors_preview}
291
- **선정 이유:** {concept['reason']}
292
 
293
  ---
294
  """
295
-
296
- return (
297
- gr.update(visible=True), # concept_selection_group
298
- gr.update(choices=concept_choices, value=concept_choices[0] if concept_choices else None), # concept_selector
299
- gr.update(visible=True), # generate_notice_btn
300
- analysis_md, # concept_analysis_output
301
- concepts # concepts_state
302
- )
303
 
304
- except Exception as e:
305
- error_message = f"❌ 컨셉 분석 중 오류가 발생했습니다: {str(e)}"
306
- return (
307
- gr.update(visible=False),
308
- gr.update(choices=[], value=None),
309
- gr.update(visible=False),
310
- error_message,
311
- []
312
- )
313
 
314
- def handle_concept_selection(selected_concept_name, concepts_data):
315
- """컨셉 선택 처리"""
316
- try:
317
- if selected_concept_name and concepts_data:
318
- # 선택된 컨셉 찾기
319
- selected_idx = next(i for i, c in enumerate(concepts_data)
320
- if f"{c['name']} - {c['theme']}" == selected_concept_name)
321
- selected_concept = concepts_data[selected_idx]
322
- self.selected_concept = selected_concept
323
-
324
- # 미리보기 텍스트 생성
325
- preview_text = f"""🎨 컨셉: {selected_concept['name']}
326
- 🏷️ 테마: {selected_concept['theme']}
 
 
 
 
 
 
 
 
 
 
327
  💬 캐치프레이즈: {selected_concept['catchphrase']}
 
328
  🎯 타겟 어필: {selected_concept['target_appeal']}
 
329
  ⭐ 예상 참여도: {selected_concept['participation_score']}/10점
 
 
330
  🎪 추천 이벤트: {selected_concept['event_style']}
331
- 🎨 색상: {', '.join(selected_concept['colors'][:3])}
332
- 💡 차별화: {selected_concept['competitive_edge']}"""
333
-
334
- return preview_text, selected_concept
335
- return "", None
336
- except Exception as e:
337
- return f"❌ 컨셉 선택 오류: {str(e)}", None
338
-
339
- def handle_event_type_change(event_type):
340
- """이벤트 유형 변경 처리"""
341
- return gr.update(visible=(event_type == "직접 입력"))
342
-
343
- def handle_image_upload(image):
344
- """이미지 업로드 처리"""
345
- try:
346
- if image:
347
- analysis = self.image_analyzer.analyze_reference_image(image)
348
- self.current_image_analysis = analysis
349
- formatted_result = format_image_analysis_result(analysis)
350
-
351
- return (
352
- formatted_result, # image_analysis_output
353
- gr.update(visible=True, value=analysis), # extracted_elements_output
354
- analysis # image_analysis_state
355
- )
356
- return "", gr.update(visible=False), None
357
- except Exception as e:
358
- error_msg = f"❌ 이미지 분석 오류: {str(e)}"
359
- return error_msg, gr.update(visible=False), None
360
-
361
- def handle_final_generation(year, month, selected_concept, event_type, custom_event, image_analysis):
362
- """최종 공지사항 생성 처리"""
363
- try:
364
- if not selected_concept:
365
- return (
366
- "❌ 먼저 컨셉을 선택해주세요!",
367
- "",
368
- gr.update(visible=False),
369
- gr.update(visible=False),
370
- gr.update(visible=False),
371
- ""
372
- )
373
-
374
- # 최종 공지사항 생성
375
- notice = self.notice_generator.generate_event_notice(
376
- concept_data=selected_concept,
377
- event_type=event_type,
378
- custom_event=custom_event,
379
- reference_analysis=image_analysis
380
- )
381
-
382
- # 디자인 가이드 생성
383
- design_guide = self.design_generator.generate_design_guidelines(
384
- selected_concept, image_analysis
385
- )
386
-
387
- # 색상 팔레트 HTML 생성
388
- color_html = self.design_generator.generate_color_palette_html(
389
- selected_concept.get('colors', [])
390
- )
391
-
392
- # 텍스트 통계 생성
393
- stats = TextUtils.count_characters(notice)
394
- stats_text = f"총 글자수: {stats['total_chars']:,}자 | 단어수: {stats['words']:,}개 | 줄수: {stats['lines']:,}줄"
395
-
396
- # 파일 다운로드 준비
397
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
398
- filename = f"event_notice_{timestamp}.txt"
399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  return (
401
- notice, # final_notice_output
402
- design_guide, # design_guide_output
403
- gr.update(visible=True, value=color_html), # color_palette_display
404
- gr.update(visible=True), # download_txt_btn
405
- gr.update(visible=True), # regenerate_btn
406
- stats_text # text_stats
407
- )
408
-
409
- except Exception as e:
410
- error_message = f"❌ 공지사항 생성 중 오류가 발생했습니다: {str(e)}"
411
- return (
412
- error_message,
413
- "",
414
  gr.update(visible=False),
415
  gr.update(visible=False),
416
  gr.update(visible=False),
417
  ""
418
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
- def handle_download_preparation(notice_content):
421
- """다운로드 파일 준비"""
422
- try:
423
- if notice_content and notice_content.strip():
424
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
425
- filename = f"event_notice_{timestamp}.txt"
426
-
427
- # 임시 파일 생성
428
- temp_file = tempfile.NamedTemporaryFile(
429
- mode='w',
430
- suffix='.txt',
431
- delete=False,
432
- encoding='utf-8'
433
- )
434
- temp_file.write(notice_content)
435
- temp_file.close()
436
-
437
- return temp_file.name
438
- return None
439
- except Exception as e:
440
- print(f"파일 준비 오류: {e}")
441
- return None
442
-
443
- def handle_regeneration(year, month, selected_concept, event_type, custom_event, image_analysis):
444
- """공지사항 재생성"""
445
- return handle_final_generation(year, month, selected_concept, event_type, custom_event, image_analysis)
446
-
447
- # 이벤트 바인딩
448
- analyze_concepts_btn.click(
449
- handle_concept_analysis,
450
- inputs=[year_input, month_input],
451
- outputs=[
452
- concept_selection_group,
453
- concept_selector,
454
- generate_notice_btn,
455
- concept_analysis_output,
456
- concepts_state
457
- ]
458
- )
459
-
460
- concept_selector.change(
461
- handle_concept_selection,
462
- inputs=[concept_selector, concepts_state],
463
- outputs=[concept_preview, selected_concept_state]
464
- )
465
-
466
- event_type_selector.change(
467
- handle_event_type_change,
468
- inputs=[event_type_selector],
469
- outputs=[custom_event_input]
470
- )
471
-
472
- reference_image_input.change(
473
- handle_image_upload,
474
- inputs=[reference_image_input],
475
- outputs=[image_analysis_output, extracted_elements_output, image_analysis_state]
476
- )
477
-
478
- generate_notice_btn.click(
479
- handle_final_generation,
480
- inputs=[
481
- year_input,
482
- month_input,
483
- selected_concept_state,
484
- event_type_selector,
485
- custom_event_input,
486
- image_analysis_state
487
- ],
488
- outputs=[
489
- final_notice_output,
490
- design_guide_output,
491
- color_palette_display,
492
- download_txt_btn,
493
- regenerate_btn,
494
- text_stats
495
- ]
496
- )
497
-
498
- regenerate_btn.click(
499
- handle_regeneration,
500
- inputs=[
501
- year_input,
502
- month_input,
503
- selected_concept_state,
504
- event_type_selector,
505
- custom_event_input,
506
- image_analysis_state
507
- ],
508
- outputs=[
509
- final_notice_output,
510
- design_guide_output,
511
- color_palette_display,
512
- download_txt_btn,
513
- regenerate_btn,
514
- text_stats
515
- ]
516
- )
517
-
518
- # 다운로드 버튼 클릭 이벤트
519
- download_txt_btn.click(
520
- handle_download_preparation,
521
- inputs=[final_notice_output],
522
- outputs=[]
523
- )
524
-
525
- return demo
526
 
527
  def main():
528
  """메인 실행 함수"""
529
  print("🎉 이벤트 공지사항 생성기를 시작합니다...")
 
530
 
531
  try:
532
- # 애플리케이션 초기화
533
- app = EventGeneratorApp()
534
-
535
- # 그라디오 인터페이스 생성
536
- demo = app.create_interface()
537
 
538
  # 서버 실행
539
  print("🚀 서버를 시작합니다...")
@@ -541,7 +696,6 @@ def main():
541
  server_name="0.0.0.0",
542
  server_port=7860,
543
  share=True,
544
- debug=True,
545
  show_tips=True,
546
  show_error=True
547
  )
@@ -552,4 +706,245 @@ def main():
552
  traceback.print_exc()
553
 
554
  if __name__ == "__main__":
555
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ 이벤트 공지사항 생성기 - 완전 버전
3
+ 그라디오 최신 버전 지원
4
  """
5
 
6
  import gradio as gr
7
+ from datetime import datetime, timedelta
8
+ from typing import List, Dict, Any
9
  import json
10
  import tempfile
 
11
  import os
12
+
13
+ class EventGenerator:
14
+ """이벤트 생성기 메인 클래스"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  def __init__(self):
17
+ self.monthly_data = {
18
+ 1: {
19
+ "holidays": ["신정", "설날", "대보름"],
20
+ "special_days": ["새해", "신년", "다이어트의 달"],
21
+ "seasons": ["겨울", "새해맞이", "신년다짐"],
22
+ "trends": ["새해 계획", "다이어트", "정리정돈", "미니멀라이프"],
23
+ "colors": ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7"]
24
+ },
25
+ 2: {
26
+ "holidays": ["밸런타인데이", "정월대보름"],
27
+ "special_days": ["초콜릿데이", "로즈데이"],
28
+ "seasons": ["겨울", "입춘", "매화꽃"],
29
+ "trends": ["러브", "로맨스", "셀프케어", "따뜻함"],
30
+ "colors": ["#FF69B4", "#FFB6C1", "#DC143C", "#FFC0CB", "#8B0000"]
31
+ },
32
+ 3: {
33
+ "holidays": ["삼일절", "화이트데이"],
34
+ "special_days": ["여성의 날", "화이트데이"],
35
+ "seasons": ["봄", "벚꽃", "춘분", "개화"],
36
+ "trends": ["봄맞이", "새학기", "벚꽃놀이", "봄나들이"],
37
+ "colors": ["#FFB6C1", "#98FB98", "#87CEEB", "#F0E68C", "#DDA0DD"]
38
+ },
39
+ 4: {
40
+ "holidays": ["만우절", "식목일"],
41
+ "special_days": ["블랙데이", "킹데이"],
42
+ "seasons": ["봄", "벚꽃만개", "꽃구경"],
43
+ "trends": ["벚꽃축제", "봄피크닉", "새학기적응", "아웃도어"],
44
+ "colors": ["#FFB6C1", "#98FB98", "#F0E68C", "#DDA0DD", "#87CEEB"]
45
+ },
46
+ 5: {
47
+ "holidays": ["어린이날", "어버이날", "스승의날", "부처님오신날"],
48
+ "special_days": ["가정의 달", "로즈데이", "감사데이"],
49
+ "seasons": ["봄", "신록", "야외활동"],
50
+ "trends": ["가족사랑", "감사", "나들이", "어린이날선물"],
51
+ "colors": ["#32CD32", "#FFB6C1", "#87CEEB", "#F0E68C", "#DDA0DD"]
52
+ },
53
+ 6: {
54
+ "holidays": ["현충일", "단오"],
55
+ "special_days": ["키스데이", "패밀리데이"],
56
+ "seasons": ["초여름", "장마준비", "여름나기"],
57
+ "trends": ["여름준비", "다이어트", "워터파크", "바캉스준비"],
58
+ "colors": ["#00CED1", "#FFD700", "#FF6347", "#32CD32", "#FF69B4"]
59
+ },
60
+ 7: {
61
+ "holidays": ["제헌절", "초복", "중복"],
62
+ "special_days": ["실버데이", "썸머데이"],
63
+ "seasons": ["여름", "장마", "휴가철"],
64
+ "trends": ["여름휴가", "워터액티비티", "시원한음식", "휴가패션"],
65
+ "colors": ["#00BFFF", "#FFD700", "#FF6347", "#32CD32", "#FF69B4"]
66
+ },
67
+ 8: {
68
+ "holidays": ["광복절", "말복"],
69
+ "special_days": ["그린데이", "썸머바캉스"],
70
+ "seasons": ["여름", "휴가철", "더위절정"],
71
+ "trends": ["여름휴가절정", "바다여행", "축제", "여름추억"],
72
+ "colors": ["#00BFFF", "#FF6347", "#FFD700", "#32CD32", "#FF69B4"]
73
+ },
74
+ 9: {
75
+ "holidays": ["추석", "추분"],
76
+ "special_days": ["포토데이", "뮤직데이"],
77
+ "seasons": ["가을", "추석연휴", "선선함"],
78
+ "trends": ["추석준비", "가을패션", "독서의계절", "문화생활"],
79
+ "colors": ["#FF8C00", "#DC143C", "#B8860B", "#CD853F", "#D2691E"]
80
+ },
81
+ 10: {
82
+ "holidays": ["개천절", "한글날"],
83
+ "special_days": ["와인데이", "커피데이"],
84
+ "seasons": ["가을", "단풍", "쌀쌀함"],
85
+ "trends": ["가을단풍", "독서", "카페문화", "가을나들이"],
86
+ "colors": ["#FF8C00", "#DC143C", "#B8860B", "#8B4513", "#A0522D"]
87
+ },
88
+ 11: {
89
+ "holidays": ["빼빼로데이", "수능"],
90
+ "special_days": ["무비데이", "오렌지데이"],
91
+ "seasons": ["늦가을", "쌀쌀함", "겨울준비"],
92
+ "trends": ["빼빼로데이", "수능응원", "겨울준비", "연말준비"],
93
+ "colors": ["#8B4513", "#A0522D", "#CD853F", "#D2691E", "#FF8C00"]
94
+ },
95
+ 12: {
96
+ "holidays": ["크리스마스", "성탄절", "동지"],
97
+ "special_days": ["허그데이", "키스데이"],
98
+ "seasons": ["겨울", "연말", "크리스마스"],
99
+ "trends": ["크리스마스", "연말파티", "선물", "한해마무리"],
100
+ "colors": ["#DC143C", "#228B22", "#FFD700", "#800080", "#FF69B4"]
101
  }
102
+ }
103
+
104
+ self.event_templates = {
105
+ "댓글 달기 이벤트": {
106
+ "steps": ["해당 게시글 찾아가기", "감성적인 댓글 작성하기", "댓글 등록 완료"],
107
+ "description": "지정된 게시글에 댓글을 달아주세요"
108
+ },
109
+ "게시글 작성 이벤트": {
110
+ "steps": ["이벤트 주제 확인하기", "창의적인 게시글 작성하기", "해시태그 포함하여 업로드"],
111
+ "description": "주제에 맞는 게시글을 작성해주세요"
112
+ },
113
+ "좋아요/공감 이벤트": {
114
+ "steps": ["이벤트 게시글 확인하기", "좋아요 버튼 클릭하기", "추가 액션 완료하기"],
115
+ "description": "마음에 드는 게시글에 좋아요를 눌러주세요"
116
+ },
117
+ "출석체크 이벤트": {
118
+ "steps": ["매일 커뮤니티 접속하기", "출석체크 버튼 클릭하기", "연속 출석 달성하기"],
119
+ "description": "매일 커뮤니티에 방문하여 출석체크를 해주세요"
120
+ },
121
+ "추천인 이벤트": {
122
+ "steps": ["초대 링크 생성하기", "친구에게 링크 공유하기", "친구 가입 완료 확인하기"],
123
+ "description": "친구를 초대하고 함께 이벤트에 참여해주세요"
124
+ },
125
+ "사진 업로드 이벤트": {
126
+ "steps": ["테마에 맞는 사진 촬영하기", "사진 업로드하기", "설명글과 함께 공유하기"],
127
+ "description": "테마에 맞는 사진을 업로드해주세요"
128
+ },
129
+ "퀴즈/설문 이벤트": {
130
+ "steps": ["퀴즈 문제 확인하기", "정답 또는 의견 제출하기", "제출 완료 확인하기"],
131
+ "description": "퀴즈를 풀거나 설문에 참여해주세요"
132
  }
133
+ }
134
+
135
+ def analyze_monthly_concepts(self, month: int, year: int = 2025) -> List[Dict[str, Any]]:
136
+ """월별 컨셉 분석"""
137
+
138
+ month_data = self.monthly_data.get(month, {})
139
+ concepts = []
140
+
141
+ # 컨셉 1: 주요 기념일 기반
142
+ if month_data.get("holidays"):
143
+ main_holiday = month_data["holidays"][0]
144
+ concepts.append({
145
+ "name": f"{main_holiday} 특별 이벤트",
146
+ "theme": f"{main_holiday}를 테마로 한 감성 이벤트",
147
+ "catchphrase": f"💕 {main_holiday}과 함께 특별한 추억을 만들어요!",
148
+ "reason": f"{month}월의 대표 기념일인 {main_holiday}로 높은 관심과 참여도 예상",
149
+ "colors": month_data.get("colors", ["#FF69B4"])[:3],
150
+ "participation_score": 8.5,
151
+ "participation_reason": "주요 기념일로 인한 높은 관심도와 시즌성",
152
+ "event_style": "댓글 이벤트 + 인증샷 업로드",
153
+ "competitive_edge": "시즌 특화 컨텐츠로 차별화",
154
+ "target_appeal": "기념일 감성과 공감대 형성"
155
+ })
156
+
157
+ # 컨셉 2: 계절/트렌드 기반
158
+ if month_data.get("trends"):
159
+ trend_theme = month_data["trends"][0]
160
+ concepts.append({
161
+ "name": f"{trend_theme} 챌린지",
162
+ "theme": f"최신 {trend_theme} 트렌드 참여형 이벤트",
163
+ "catchphrase": f"✨ 지금 핫한 {trend_theme}! 함께 도전해요!",
164
+ "reason": f"현재 인기 트렌드인 {trend_theme}로 젊은 층 어필",
165
+ "colors": month_data.get("colors", ["#4ECDC4"])[1:4],
166
+ "participation_score": 8.0,
167
+ "participation_reason": "최신 트렌드로 인한 화제성과 참여욕구",
168
+ "event_style": "챌린지 참여 + SNS 공유",
169
+ "competitive_edge": "트렌드 선도적 이벤트",
170
+ "target_appeal": "트렌드 민감층과 SNS 활용"
171
+ })
172
+
173
+ # 컨셉 3: 계절감 기반
174
+ if month_data.get("seasons"):
175
+ season_theme = month_data["seasons"][0]
176
+ concepts.append({
177
+ "name": f"{season_theme} 라이프 이벤트",
178
+ "theme": f"{season_theme} 계절 맞이 라이프스타일 이벤트",
179
+ "catchphrase": f"🌸 {season_theme}과 함께하는 일상의 소소한 행복!",
180
+ "reason": f"{season_theme} 계절감으로 자연스러운 참여 유도",
181
+ "colors": month_data.get("colors", ["#98FB98"])[2:5],
182
+ "participation_score": 7.5,
183
+ "participation_reason": "계절적 공감대와 일상 연관성",
184
+ "event_style": "포토 챌린지 + 후기 공유",
185
+ "competitive_edge": "계절별 맞춤 컨텐츠",
186
+ "target_appeal": "일상 속 계절감과 감성"
187
+ })
188
+
189
+ # 컨셉 4: 커뮤니티 소통 기반
190
+ concepts.append({
191
+ "name": f"{month}월 우리들의 이야기",
192
+ "theme": "커뮤니티 멤버 간 소통과 공감 이벤트",
193
+ "catchphrase": f"💬 {month}월, 우리의 특별한 이야기를 들려주세요!",
194
+ "reason": "커뮤니티 결속력 강화와 지속적 참여 유도",
195
+ "colors": month_data.get("colors", ["#87CEEB"])[-3:],
196
+ "participation_score": 7.0,
197
+ "participation_reason": "커뮤니티 애착도와 소속감 기반 참여",
198
+ "event_style": "스토리텔링 + 공감 이벤트",
199
+ "competitive_edge": "진정성 있는 커뮤니티 소통",
200
+ "target_appeal": "소속감과 공감대 형성"
201
+ })
202
+
203
+ # 컨셉 5: 스페셜 데이 기반
204
+ if month_data.get("special_days"):
205
+ special_day = month_data["special_days"][0]
206
+ concepts.append({
207
+ "name": f"{special_day} 스페셜 위크",
208
+ "theme": f"{special_day} 맞이 특별 혜택 이벤트",
209
+ "catchphrase": f"🎁 {special_day} 특별한 선물이 기다려요!",
210
+ "reason": f"{special_day}의 특별함을 활용한 프리미엄 이벤트",
211
+ "colors": month_data.get("colors", ["#FFB6C1"])[:3],
212
+ "participation_score": 7.8,
213
+ "participation_reason": "특별한 날에 대한 기대감과 혜택 관심",
214
+ "event_style": "미션 완료 + 럭키박스",
215
+ "competitive_edge": "특별 혜택과 깜짝 이벤트",
216
+ "target_appeal": "특별함과 혜택에 대한 기대"
217
+ })
218
+
219
+ return concepts
220
+
221
+ def generate_event_notice(self, concept_data: Dict, event_type: str, custom_event: str = None) -> str:
222
+ """완성된 이벤트 공지사항 생성"""
223
+
224
+ concept_name = concept_data.get("name", "특별 이벤트")
225
+ catchphrase = concept_data.get("catchphrase", "✨ 특별한 이벤트에 참여하세요!")
226
+ theme = concept_data.get("theme", "커뮤니티 이벤트")
227
+
228
+ # 이벤트 기간 설정
229
+ now = datetime.now()
230
+ start_date = now.strftime("%Y.%m.%d")
231
+ end_date = (now + timedelta(days=7)).strftime("%Y.%m.%d")
232
+
233
+ # 템플릿 선택
234
+ if custom_event and event_type == "직접 입력":
235
+ template = {
236
+ "description": custom_event,
237
+ "steps": ["이벤트 내용 확인하기", "요구사항에 맞게 참여하기", "참여 완료 확인하기"]
238
  }
239
+ else:
240
+ template = self.event_templates.get(event_type, self.event_templates["댓글 달기 이벤트"])
241
+
242
+ # 공지사항 생성
243
+ notice = f"""{catchphrase}
244
+
245
+ 🎉 {concept_name} 🎉
246
+
247
+ 📅 이벤트 기간: {start_date} ~ {end_date} 23:59
248
+
249
+ ========================
250
+ EVENT
251
+ {theme}을 테마로 한 특별한 이벤트에 참여하세요!
252
+ 많은 분들의 적극적인 참여를 기다리고 있어요 💕
253
+
254
+ 📅 이벤트 기간: {start_date} ~ {end_date}
255
+ ========================
256
+
257
+ 🎯 STEP 1
258
+ {template["steps"][0]}
259
+ * 이벤트 게시글을 꼼꼼히 확인해주세요!
260
+
261
+ 🎯 STEP 2
262
+ {template["steps"][1]}
263
+ * 진심이 담긴 따뜻한 마음으로 참여해주세요
264
+
265
+ 🎯 STEP 3
266
+ {template["steps"][2]}
267
+ * 모든 과정을 완료하시면 참여 완료입니다!
268
+
269
+ =================
270
+ 🎁 당첨혜택
271
+ ✨ 1등 (1명): 스타벅스 5만원 기프트카드
272
+ 🎉 2등 (3명): 베스킨라빈스 아이스크림 쿠폰
273
+ 💝 3등 (10명): 편의점 5천원 상품권
274
+ 🌟 참가상 (50명): 모바일 치킨 쿠폰
275
+
276
+ 📋 추첨 방식: 참여자 중 무작위 추첨
277
+ ========================
278
+
279
+ 👥 이벤트 대상
280
+ ✅ 커뮤니티 정회원 (가입 후 7일 경과)
281
+ ✅ 만 18세 이상 성인
282
+ ✅ 이벤트 기간 내 정상 참여자
283
+
284
+ (*중복 참여, 어뷰징 계정 제외)
285
+ ========================
286
+
287
+ 📮 수령방법
288
+ 1️⃣ 당첨자 발표: 이벤트 종료 후 3일 이내
289
+ 2️⃣ 개별 메시지를 통한 당첨 안내
290
+ 3️⃣ 본인 확인 후 7일 이내 회신 필수
291
+ 4️⃣ 모바일 쿠폰/기프트카드 발송
292
+
293
+ 📞 문의처: 커뮤니티 고객센터
294
+ 🕐 수령 기한: 당첨 안내 후 30일 이내
295
+ =========================
296
+
297
+ ⚠️ 꼭 확인하세요!
298
+ ※ 부적절한 내용의 댓글/게시글은 삭제될 수 있습니다
299
+ ※ 중복 계정이나 어뷰징으로 의심되는 참여는 제외됩니다
300
+ ※ 당첨자는 본인 확인을 위해 추가 정보 요청이 있을 수 있습니다
301
+ ※ 상품은 현금으로 교환되지 않으며, 재판매가 불가합니다
302
+ ※ 이벤트 내용은 운영진의 판단에 따라 변경될 수 있습니다
303
+ ※ 문의사항은 고객센터를 통해 연락해주세요
304
+
305
+ ===========================
306
+ 📞 고객센터: 1234-5678
307
+ 🕐 운영시간: 평일 09:30~18:00 (점심시간 12:00~13:00)
308
+ 💬 카카오톡: @event_community
309
+ ※ 주말 및 공휴일 휴무
310
+
311
+ 💝 많은 분들의 참여 부탁드려요! 감사합니다 💕"""
312
+
313
+ return notice.strip()
314
+
315
+ def count_text_stats(self, text: str) -> str:
316
+ """텍스트 통계 계산"""
317
+ total_chars = len(text)
318
+ chars_no_space = len(text.replace(" ", "").replace("\n", ""))
319
+ words = len(text.split())
320
+ lines = len(text.split("\n"))
321
+
322
+ return f"총 글자수: {total_chars:,}자 | 공백제외: {chars_no_space:,}자 | 단어수: {words:,}개 | 줄수: {lines:,}줄"
323
+
324
+ def create_app():
325
+ """그라디오 앱 생성"""
326
+
327
+ generator = EventGenerator()
328
+
329
+ # 커스텀 CSS
330
+ custom_css = """
331
+ .main-header {
332
+ text-align: center;
333
+ padding: 30px;
334
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
335
+ color: white;
336
+ border-radius: 15px;
337
+ margin-bottom: 25px;
338
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
339
+ }
340
+ .input-section {
341
+ background: #f8f9fa;
342
+ padding: 25px;
343
+ border-radius: 15px;
344
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
345
+ margin-bottom: 20px;
346
+ }
347
+ .output-section {
348
+ background: white;
349
+ padding: 25px;
350
+ border-radius: 15px;
351
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
352
+ }
353
+ .concept-card {
354
+ background: linear-gradient(45deg, #e3f2fd, #f1f8e9);
355
+ padding: 20px;
356
+ border-radius: 12px;
357
+ margin: 15px 0;
358
+ border-left: 5px solid #2196f3;
359
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
360
+ }
361
+ .stats-box {
362
+ background: #fff3cd;
363
+ padding: 10px;
364
+ border-radius: 8px;
365
+ border: 1px solid #ffeaa7;
366
+ margin-top: 10px;
367
+ }
368
+ """
369
+
370
+ with gr.Blocks(
371
+ title="🎉 AI 이벤트 공지사항 생성기",
372
+ theme=gr.themes.Soft(),
373
+ css=custom_css
374
+ ) as demo:
375
+
376
+ # 헤더
377
+ with gr.Column(elem_classes="main-header"):
378
+ gr.Markdown("""
379
+ # 🎉 AI 이벤트 공지사항 생성기
380
+ ### 실시간 트렌드 분석으로 완벽한 이벤트 기획을 도와드립니다
381
+ #### 20-40대 여성 타겟 커뮤니티 이벤트 전문 생성기
382
+ """)
383
+
384
+ with gr.Row():
385
+ # 좌측: 입력 영역
386
+ with gr.Column(scale=1, elem_classes="input-section"):
387
+ gr.Markdown("## ⚙️ 기본 설정")
388
+
389
+ with gr.Row():
390
  year_input = gr.Number(
391
  value=2025,
392
  label="📅 연도",
393
  minimum=2024,
394
  maximum=2030,
395
+ step=1
 
396
  )
397
 
398
  month_input = gr.Dropdown(
399
  choices=[f"{i}월" for i in range(1, 13)],
400
  label="📅 이벤트 월 선택",
401
+ value=f"{datetime.now().month}월"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  )
403
+
404
+ analyze_btn = gr.Button(
405
+ "🧠 AI 컨셉 분석 시작",
406
+ variant="secondary",
407
+ size="lg"
408
+ )
409
+
410
+ # 컨셉 선택 영역
411
+ with gr.Group(visible=False) as concept_group:
412
+ gr.Markdown("### 🎨 추천 컨셉 선택")
413
 
414
+ concept_selector = gr.Dropdown(
415
+ label="컨셉을 선택해주세요",
416
+ interactive=True
 
 
 
 
417
  )
418
 
419
+ concept_preview = gr.Textbox(
420
+ label="📊 선택된 컨셉 미리보기",
421
+ lines=8,
422
+ interactive=False
 
 
423
  )
424
 
425
+ gr.Markdown("## 🎯 이벤트 설정")
426
+
427
+ event_type_selector = gr.Dropdown(
428
+ choices=[
429
+ "댓글 달기 이벤트",
430
+ "게시글 작성 이벤트",
431
+ "좋아요/공감 이벤트",
432
+ "출석체크 이벤트",
433
+ "추천인 이벤트",
434
+ "사진 업로드 이벤트",
435
+ "퀴즈/설문 이벤트",
436
+ "직접 입력"
437
+ ],
438
+ label="🎯 이벤트 유형 선택",
439
+ value="댓글 달기 이벤트"
440
+ )
441
+
442
+ custom_event_input = gr.Textbox(
443
+ label="✏️ 커스텀 이벤트 설명",
444
+ placeholder="'직접 입력' 선택시 원하는 이벤트 내용을 자세히 설명해주세요",
445
+ visible=False,
446
+ lines=4
447
+ )
448
+
449
+ generate_btn = gr.Button(
450
+ "✨ 완성된 공지사항 생성하기",
451
+ variant="primary",
452
+ size="lg",
453
+ visible=False
454
+ )
455
+
456
+ # 우측: 결과 출력 영역
457
+ with gr.Column(scale=2, elem_classes="output-section"):
458
+
459
+ with gr.Tabs() as tabs:
460
 
461
+ with gr.TabItem("🧠 AI 컨셉 분석"):
462
+ concept_analysis_output = gr.Markdown(
463
+ value="👆 먼저 좌측에서 연도와 월을 선택한 후 'AI 컨셉 분석 시작' 버튼을 클릭해주세요",
464
+ elem_classes="concept-card"
465
+ )
466
+
467
+ with gr.TabItem("📝 완성된 공지사항"):
468
+ final_notice_output = gr.Textbox(
469
+ label="생성된 이벤트 공지사항",
470
+ lines=35,
471
+ placeholder="컨셉 선택 후 '완성된 공지사항 생성하기' 버튼을 클릭하면 결과가 나타납니다",
472
+ show_copy_button=True,
473
+ max_lines=50
474
+ )
475
 
476
+ # 통계 버튼
477
+ with gr.Row():
478
+ text_stats_output = gr.Textbox(
479
+ label="📊 텍스트 통계",
480
+ lines=1,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  interactive=False,
482
+ visible=False,
483
+ elem_classes="stats-box"
484
  )
485
 
486
+ with gr.Row():
487
+ download_btn = gr.DownloadButton(
488
+ "💾 텍스트 파일로 다운로드",
489
+ size="sm",
490
+ variant="secondary",
 
 
491
  visible=False
492
  )
493
+ regenerate_btn = gr.Button(
494
+ "🔄 다시 생성하기",
495
+ size="sm",
 
 
 
 
 
 
 
 
496
  visible=False
497
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
 
499
+ with gr.TabItem("🎨 컨셉 상세정보"):
500
+ concept_details_output = gr.JSON(
501
+ label="선택된 컨셉의 상세 정보",
502
+ visible=False
503
+ )
504
+
505
+ # 상태 관리
506
+ concepts_state = gr.State([])
507
+ selected_concept_state = gr.State(None)
508
+
509
+ # 이벤트 핸들러 함수들
510
+ def handle_concept_analysis(year, month):
511
+ """AI 컨셉 분석 처리"""
512
+ try:
513
+ month_num = int(month.replace('월', ''))
514
+ concepts = generator.analyze_monthly_concepts(month_num, year)
515
+
516
+ concept_choices = [f"{concept['name']} - {concept['theme']}" for concept in concepts]
517
+
518
+ # 분석 결과 마크다운 생성
519
+ analysis_md = f"# 🎯 {year}년 {month} AI 분석 결과\n\n"
520
+ analysis_md += f"**분석 완료:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
521
+ analysis_md += f"**총 {len(concepts)}개 컨셉 추천**\n\n"
522
+
523
+ for i, concept in enumerate(concepts, 1):
524
+ colors_display = " ".join([f"`{color}`" for color in concept['colors'][:3]])
525
 
526
+ analysis_md += f"""
 
 
 
527
  ## {i}. {concept['name']}
528
+ **🏷️ 테마:** {concept['theme']}
529
+ **🎯 타겟 어필:** {concept['target_appeal']}
530
+ **⭐ 예상 참여도:** {concept['participation_score']}/10점
531
+ **🎪 추천 이벤트:** {concept['event_style']}
532
+ **🎨 색상 팔레트:** {colors_display}
533
+ **💡 선정 이유:** {concept['reason']}
534
 
535
  ---
536
  """
 
 
 
 
 
 
 
 
537
 
538
+ return (
539
+ gr.update(visible=True), # concept_group
540
+ gr.update(choices=concept_choices, value=concept_choices[0] if concept_choices else None), # concept_selector
541
+ gr.update(visible=True), # generate_btn
542
+ analysis_md, # concept_analysis_output
543
+ concepts # concepts_state
544
+ )
 
 
545
 
546
+ except Exception as e:
547
+ error_message = f"컨셉 분석 중 오류가 발생했습니다: {str(e)}"
548
+ return (
549
+ gr.update(visible=False),
550
+ gr.update(choices=[], value=None),
551
+ gr.update(visible=False),
552
+ error_message,
553
+ []
554
+ )
555
+
556
+ def handle_concept_selection(selected_concept_name, concepts_data):
557
+ """컨셉 선택 처리"""
558
+ try:
559
+ if selected_concept_name and concepts_data:
560
+ selected_idx = next(i for i, c in enumerate(concepts_data)
561
+ if f"{c['name']} - {c['theme']}" == selected_concept_name)
562
+ selected_concept = concepts_data[selected_idx]
563
+
564
+ # 미리보기 텍스트 생성
565
+ preview_text = f"""🎨 컨셉명: {selected_concept['name']}
566
+
567
+ 🏷️ 테마: {selected_concept['theme']}
568
+
569
  💬 캐치프레이즈: {selected_concept['catchphrase']}
570
+
571
  🎯 타겟 어필: {selected_concept['target_appeal']}
572
+
573
  ⭐ 예상 참여도: {selected_concept['participation_score']}/10점
574
+ 📊 참여 근거: {selected_concept['participation_reason']}
575
+
576
  🎪 추천 이벤트: {selected_concept['event_style']}
577
+
578
+ 🎨 색상 팔레트: {', '.join(selected_concept['colors'][:3])}
579
+
580
+ 💡 차별화 포인트: {selected_concept['competitive_edge']}"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
 
582
+ return preview_text, selected_concept, gr.update(value=selected_concept, visible=True)
583
+ return "", None, gr.update(visible=False)
584
+ except Exception as e:
585
+ return f"❌ 컨셉 선택 오류: {str(e)}", None, gr.update(visible=False)
586
+
587
+ def handle_event_type_change(event_type):
588
+ """이벤트 유형 변경 처리"""
589
+ return gr.update(visible=(event_type == "직접 입력"))
590
+
591
+ def handle_final_generation(year, month, selected_concept, event_type, custom_event):
592
+ """최종 공지사항 생성 처리"""
593
+ try:
594
+ if not selected_concept:
595
  return (
596
+ "❌ 먼저 컨셉을 선택해주세요!",
 
 
 
 
 
 
 
 
 
 
 
 
597
  gr.update(visible=False),
598
  gr.update(visible=False),
599
  gr.update(visible=False),
600
  ""
601
  )
602
+
603
+ # 공지사항 생성
604
+ notice = generator.generate_event_notice(
605
+ concept_data=selected_concept,
606
+ event_type=event_type,
607
+ custom_event=custom_event
608
+ )
609
+
610
+ # 텍스트 통계
611
+ stats = generator.count_text_stats(notice)
612
+
613
+ # 다운로드 파일 준비
614
+ def prepare_download():
615
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
616
+ filename = f"event_notice_{timestamp}.txt"
617
+
618
+ temp_file = tempfile.NamedTemporaryFile(
619
+ mode='w',
620
+ suffix='.txt',
621
+ delete=False,
622
+ encoding='utf-8'
623
+ )
624
+ temp_file.write(notice)
625
+ temp_file.close()
626
+
627
+ return temp_file.name
628
+
629
+ return (
630
+ notice, # final_notice_output
631
+ gr.update(visible=True, value=stats), # text_stats_output
632
+ gr.update(visible=True), # download_btn
633
+ gr.update(visible=True), # regenerate_btn
634
+ prepare_download() # 다운로드 파일
635
+ )
636
 
637
+ except Exception as e:
638
+ error_message = f" 공지사항 생성 중 오류가 발생했습니다: {str(e)}"
639
+ return (
640
+ error_message,
641
+ gr.update(visible=False),
642
+ gr.update(visible=False),
643
+ gr.update(visible=False),
644
+ ""
645
+ )
646
+
647
+ def handle_regeneration(year, month, selected_concept, event_type, custom_event):
648
+ """공지사항 재생성"""
649
+ return handle_final_generation(year, month, selected_concept, event_type, custom_event)
650
+
651
+ # 이벤트 바인딩
652
+ analyze_btn.click(
653
+ handle_concept_analysis,
654
+ inputs=[year_input, month_input],
655
+ outputs=[concept_group, concept_selector, generate_btn, concept_analysis_output, concepts_state]
656
+ )
657
+
658
+ concept_selector.change(
659
+ handle_concept_selection,
660
+ inputs=[concept_selector, concepts_state],
661
+ outputs=[concept_preview, selected_concept_state, concept_details_output]
662
+ )
663
+
664
+ event_type_selector.change(
665
+ handle_event_type_change,
666
+ inputs=[event_type_selector],
667
+ outputs=[custom_event_input]
668
+ )
669
+
670
+ generate_btn.click(
671
+ handle_final_generation,
672
+ inputs=[year_input, month_input, selected_concept_state, event_type_selector, custom_event_input],
673
+ outputs=[final_notice_output, text_stats_output, download_btn, regenerate_btn, download_btn]
674
+ )
675
+
676
+ regenerate_btn.click(
677
+ handle_regeneration,
678
+ inputs=[year_input, month_input, selected_concept_state, event_type_selector, custom_event_input],
679
+ outputs=[final_notice_output, text_stats_output, download_btn, regenerate_btn, download_btn]
680
+ )
681
+
682
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
 
684
  def main():
685
  """메인 실행 함수"""
686
  print("🎉 이벤트 공지사항 생성기를 시작합니다...")
687
+ print("📚 그라디오 최신 버전으로 실행 중...")
688
 
689
  try:
690
+ # 애플리케이션 생성
691
+ demo = create_app()
 
 
 
692
 
693
  # 서버 실행
694
  print("🚀 서버를 시작합니다...")
 
696
  server_name="0.0.0.0",
697
  server_port=7860,
698
  share=True,
 
699
  show_tips=True,
700
  show_error=True
701
  )
 
706
  traceback.print_exc()
707
 
708
  if __name__ == "__main__":
709
+ main()"""
710
+ 이벤트 공지사항 생성기 - 기본 버전 (MVP)
711
+ """
712
+
713
+ import gradio as gr
714
+ from datetime import datetime
715
+ from typing import List, Dict, Any
716
+
717
+ class BasicEventGenerator:
718
+ """기본 이벤트 생성기"""
719
+
720
+ def __init__(self):
721
+ self.monthly_data = {
722
+ 1: {"holidays": ["신정", "설날"], "trends": ["새해 계획", "다이어트"], "colors": ["#FF6B6B", "#4ECDC4", "#45B7D1"]},
723
+ 2: {"holidays": ["밸런타인데이"], "trends": ["러브", "로맨스"], "colors": ["#FF69B4", "#FFB6C1", "#DC143C"]},
724
+ 3: {"holidays": ["삼일절", "화이트데이"], "trends": ["봄맞이", "벚꽃놀이"], "colors": ["#FFB6C1", "#98FB98", "#87CEEB"]},
725
+ 4: {"holidays": ["만우절"], "trends": ["벚꽃축제", "봄피크닉"], "colors": ["#98FB98", "#F0E68C", "#DDA0DD"]},
726
+ 5: {"holidays": ["어린이날", "어버이날"], "trends": ["가족사랑", "나들이"], "colors": ["#32CD32", "#FFB6C1", "#87CEEB"]},
727
+ 6: {"holidays": ["현충일"], "trends": ["여름준비", "바캉스준비"], "colors": ["#00CED1", "#FFD700", "#FF6347"]},
728
+ 7: {"holidays": ["제헌절"], "trends": ["여름휴가", "바다여행"], "colors": ["#00BFFF", "#FFD700", "#FF6347"]},
729
+ 8: {"holidays": ["광복절"], "trends": ["여름휴가절정", "축제"], "colors": ["#00BFFF", "#FF6347", "#FFD700"]},
730
+ 9: {"holidays": ["추석"], "trends": ["추석준비", "가을패션"], "colors": ["#FF8C00", "#DC143C", "#B8860B"]},
731
+ 10: {"holidays": ["개천절", "한글날"], "trends": ["가을단풍", "독서"], "colors": ["#FF8C00", "#DC143C", "#B8860B"]},
732
+ 11: {"holidays": ["빼빼로데이"], "trends": ["빼빼로데이", "겨울준비"], "colors": ["#8B4513", "#A0522D", "#CD853F"]},
733
+ 12: {"holidays": ["크리스마스"], "trends": ["크리스마스", "연말파티"], "colors": ["#DC143C", "#228B22", "#FFD700"]}
734
+ }
735
+
736
+ def analyze_month_concepts(self, month: int) -> List[Dict[str, Any]]:
737
+ """월별 간단한 컨셉 분석"""
738
+
739
+ month_data = self.monthly_data.get(month, {})
740
+ concepts = []
741
+
742
+ # 컨셉 1: 기념일 기반
743
+ if month_data.get("holidays"):
744
+ holiday = month_data["holidays"][0]
745
+ concepts.append({
746
+ "name": f"{holiday} 특별 이벤트",
747
+ "theme": f"{holiday}를 테마로 한 이벤트",
748
+ "score": 8.5,
749
+ "colors": month_data.get("colors", ["#FF69B4"])
750
+ })
751
+
752
+ # 컨셉 2: 트렌드 기반
753
+ if month_data.get("trends"):
754
+ trend = month_data["trends"][0]
755
+ concepts.append({
756
+ "name": f"{trend} 챌린지",
757
+ "theme": f"{trend} 트렌드 이벤트",
758
+ "score": 7.8,
759
+ "colors": month_data.get("colors", ["#87CEEB"])
760
+ })
761
+
762
+ # 컨셉 3: 커뮤니티 기반
763
+ concepts.append({
764
+ "name": f"{month}월 소통 이벤트",
765
+ "theme": "커뮤니티 멤버 소통 이벤트",
766
+ "score": 7.0,
767
+ "colors": month_data.get("colors", ["#98FB98"])
768
+ })
769
+
770
+ return concepts
771
+
772
+ def generate_notice(self, concept: Dict, event_type: str) -> str:
773
+ """기본 공지사항 생성"""
774
+
775
+ now = datetime.now()
776
+ start_date = f"{now.year}.{now.month}.{now.day}"
777
+ end_date = f"{now.year}.{now.month}.{now.day + 7}"
778
+
779
+ notice = f"""
780
+ 💕 {concept['name']}에 참여하세요!
781
+
782
+ 🎉 {concept['name']} 🎉
783
+
784
+ 📅 이벤트 기간: {start_date} ~ {end_date} 23:59
785
+
786
+ ========================
787
+ ✨ EVENT
788
+ {concept['theme']}을 테마로 한 특별한 이벤트입니다!
789
+ 많은 분들의 적극적인 참여를 기다리고 있어요 💕
790
+ ========================
791
+
792
+ 🎯 참여 방법
793
+ 1️⃣ 이벤트 게시글 확인하기
794
+ 2️⃣ {event_type} 참여하기
795
+ 3️⃣ 참여 완료!
796
+
797
+ =================
798
+ 🎁 당첨혜택
799
+ ✨ 1등 (1명): 스타벅스 5만원 기프트카드
800
+ 🎉 2등 (3명): 베스킨라빈스 쿠폰
801
+ 💝 3등 (10명): 편의점 5천원 상품권
802
+
803
+ 📋 추첨: 참여자 중 무작위 추첨
804
+ ========================
805
+
806
+ 👥 이벤트 대상
807
+ ✅ 커뮤니티 정회원
808
+ ✅ 만 18세 이상
809
+ ✅ 이벤트 기간 내 참여자
810
+ ========================
811
+
812
+ ⚠️ 주의사항
813
+ ※ 부적절한 참여는 제외됩니다
814
+ ※ 중복 계정 참여 불가
815
+ ※ 당첨자는 본인 확인 필요
816
+
817
+ ===========================
818
+ 📞 문의: 1234-5678
819
+ 💬 카카오톡: @event_community
820
+
821
+ 💝 많은 참여 부탁드려요! 감사합니다 💕
822
+ """
823
+
824
+ return notice.strip()
825
+
826
+ def create_interface():
827
+ """그라디오 인터페이스 생성"""
828
+
829
+ generator = BasicEventGenerator()
830
+
831
+ with gr.Blocks(title="이벤트 공지사항 생성기") as demo:
832
+
833
+ gr.Markdown("""
834
+ # 🎉 이벤트 공지사항 생성기
835
+ ### 간편하게 이벤트 공지사항을 만들어보세요!
836
+ """)
837
+
838
+ with gr.Row():
839
+ with gr.Column():
840
+ gr.Markdown("## ⚙️ 설정")
841
+
842
+ month_input = gr.Dropdown(
843
+ choices=[f"{i}월" for i in range(1, 13)],
844
+ label="📅 이벤트 월",
845
+ value=f"{datetime.now().month}월"
846
+ )
847
+
848
+ event_type_input = gr.Dropdown(
849
+ choices=["댓글 달기", "게시글 작성", "좋아요 누르기", "출석체크"],
850
+ label="🎯 이벤트 유형",
851
+ value="댓글 달기"
852
+ )
853
+
854
+ analyze_btn = gr.Button("🧠 컨셉 분석하기", variant="secondary")
855
+
856
+ concept_dropdown = gr.Dropdown(
857
+ label="🎨 컨셉 선택",
858
+ visible=False
859
+ )
860
+
861
+ generate_btn = gr.Button("✨ 공지사항 생성하기", variant="primary", visible=False)
862
+
863
+ with gr.Column():
864
+ gr.Markdown("## 📝 결과")
865
+
866
+ concept_result = gr.Textbox(
867
+ label="컨셉 분석 결과",
868
+ lines=8,
869
+ placeholder="먼저 '컨셉 분석하기' 버튼을 클릭하세요"
870
+ )
871
+
872
+ final_result = gr.Textbox(
873
+ label="생성된 공지사항",
874
+ lines=20,
875
+ placeholder="컨셉을 선택하고 '공지사항 생성하기' 버튼을 클릭하세요",
876
+ show_copy_button=True
877
+ )
878
+
879
+ # 상태 변수
880
+ concepts_state = gr.State([])
881
+ selected_concept_state = gr.State(None)
882
+
883
+ def handle_analyze(month):
884
+ """컨셉 분석 처리"""
885
+ try:
886
+ month_num = int(month.replace('월', ''))
887
+ concepts = generator.analyze_month_concepts(month_num)
888
+
889
+ # 결과 텍스트 생성
890
+ result_text = f"# {month} 추천 컨셉\n\n"
891
+ concept_choices = []
892
+
893
+ for i, concept in enumerate(concepts, 1):
894
+ result_text += f"## {i}. {concept['name']}\n"
895
+ result_text += f"- 테마: {concept['theme']}\n"
896
+ result_text += f"- 예상 점수: {concept['score']}/10점\n\n"
897
+
898
+ concept_choices.append(concept['name'])
899
+
900
+ return (
901
+ result_text,
902
+ gr.update(visible=True, choices=concept_choices, value=concept_choices[0]),
903
+ gr.update(visible=True),
904
+ concepts
905
+ )
906
+ except Exception as e:
907
+ return f"❌ 오류: {str(e)}", gr.update(visible=False), gr.update(visible=False), []
908
+
909
+ def handle_concept_select(concept_name, concepts):
910
+ """컨셉 선택 처리"""
911
+ if concept_name and concepts:
912
+ selected = next((c for c in concepts if c['name'] == concept_name), None)
913
+ return selected
914
+ return None
915
+
916
+ def handle_generate(concept, event_type):
917
+ """공지사항 생성 처리"""
918
+ if concept:
919
+ notice = generator.generate_notice(concept, event_type)
920
+ return notice
921
+ return "❌ 컨셉을 먼저 선택해주세요!"
922
+
923
+ # 이벤트 연결
924
+ analyze_btn.click(
925
+ handle_analyze,
926
+ inputs=[month_input],
927
+ outputs=[concept_result, concept_dropdown, generate_btn, concepts_state]
928
+ )
929
+
930
+ concept_dropdown.change(
931
+ handle_concept_select,
932
+ inputs=[concept_dropdown, concepts_state],
933
+ outputs=[selected_concept_state]
934
+ )
935
+
936
+ generate_btn.click(
937
+ handle_generate,
938
+ inputs=[selected_concept_state, event_type_input],
939
+ outputs=[final_result]
940
+ )
941
+
942
+ return demo
943
+
944
+ if __name__ == "__main__":
945
+ demo = create_interface()
946
+ demo.launch(
947
+ server_name="0.0.0.0",
948
+ server_port=7860,
949
+ share=True
950
+ )