Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 12 |
-
|
| 13 |
-
|
| 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.
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 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.
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
label="레퍼런스 이미지 업로드",
|
| 181 |
-
type="pil",
|
| 182 |
-
height=250,
|
| 183 |
-
info="기존 디자인이나 참고하고 싶은 이미지를 업로드하면 분석해서 반영합니다"
|
| 184 |
)
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
size="lg",
|
| 191 |
-
visible=False
|
| 192 |
)
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
-
with gr.
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 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.
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
color_palette_display = gr.HTML(
|
| 241 |
-
label="추천 컬러 팔레트",
|
| 242 |
visible=False
|
| 243 |
)
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 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 |
-
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
-
|
| 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 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 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 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
[]
|
| 312 |
-
)
|
| 313 |
|
| 314 |
-
|
| 315 |
-
"
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
💬 캐치프레이즈: {selected_concept['catchphrase']}
|
|
|
|
| 328 |
🎯 타겟 어필: {selected_concept['target_appeal']}
|
|
|
|
| 329 |
⭐ 예상 참여도: {selected_concept['participation_score']}/10점
|
|
|
|
|
|
|
| 330 |
🎪 추천 이벤트: {selected_concept['event_style']}
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 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 |
-
|
| 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 |
-
|
| 421 |
-
"
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 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 |
-
|
| 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 |
+
)
|